mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 19:27:35 +00:00
Applications: Move to Userland/Applications/
This commit is contained in:
parent
aa939c4b4b
commit
dc28c07fa5
287 changed files with 1 additions and 1 deletions
169
Userland/Applications/PixelPaint/BrushTool.cpp
Normal file
169
Userland/Applications/PixelPaint/BrushTool.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "BrushTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/Slider.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <utility>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
BrushTool::BrushTool()
|
||||
{
|
||||
}
|
||||
|
||||
BrushTool::~BrushTool()
|
||||
{
|
||||
}
|
||||
|
||||
void BrushTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
m_last_position = event.position();
|
||||
}
|
||||
|
||||
void BrushTool::on_mousemove(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (!(event.buttons() & GUI::MouseButton::Left || event.buttons() & GUI::MouseButton::Right))
|
||||
return;
|
||||
|
||||
draw_line(layer.bitmap(), m_editor->color_for(event), m_last_position, event.position());
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
m_last_position = event.position();
|
||||
m_was_drawing = true;
|
||||
}
|
||||
|
||||
void BrushTool::on_mouseup(Layer&, GUI::MouseEvent&, GUI::MouseEvent&)
|
||||
{
|
||||
if (m_was_drawing) {
|
||||
m_editor->did_complete_action();
|
||||
m_was_drawing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BrushTool::draw_point(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& point)
|
||||
{
|
||||
for (int y = point.y() - m_size; y < point.y() + m_size; y++) {
|
||||
for (int x = point.x() - m_size; x < point.x() + m_size; x++) {
|
||||
auto distance = point.distance_from({ x, y });
|
||||
if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height())
|
||||
continue;
|
||||
if (distance >= m_size)
|
||||
continue;
|
||||
|
||||
auto falloff = (1.0 - (distance / (float)m_size)) * (1.0f / (100 - m_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, const Gfx::Color& color, const Gfx::IntPoint& start, const Gfx::IntPoint& 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<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& size_container = m_properties_widget->add<GUI::Widget>();
|
||||
size_container.set_fixed_height(20);
|
||||
size_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& size_label = size_container.add<GUI::Label>("Size:");
|
||||
size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
size_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& size_slider = size_container.add<GUI::HorizontalSlider>();
|
||||
size_slider.set_fixed_height(20);
|
||||
size_slider.set_range(1, 100);
|
||||
size_slider.set_value(m_size);
|
||||
size_slider.on_change = [this](int value) {
|
||||
m_size = value;
|
||||
};
|
||||
|
||||
auto& hardness_container = m_properties_widget->add<GUI::Widget>();
|
||||
hardness_container.set_fixed_height(20);
|
||||
hardness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& hardness_label = hardness_container.add<GUI::Label>("Hardness:");
|
||||
hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
hardness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& hardness_slider = hardness_container.add<GUI::HorizontalSlider>();
|
||||
hardness_slider.set_fixed_height(20);
|
||||
hardness_slider.set_range(1, 99);
|
||||
hardness_slider.set_value(m_hardness);
|
||||
hardness_slider.on_change = [this](int value) {
|
||||
m_hardness = value;
|
||||
};
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
54
Userland/Applications/PixelPaint/BrushTool.h
Normal file
54
Userland/Applications/PixelPaint/BrushTool.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class BrushTool final : public Tool {
|
||||
public:
|
||||
BrushTool();
|
||||
virtual ~BrushTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
int m_size { 20 };
|
||||
int m_hardness { 80 };
|
||||
bool m_was_drawing { false };
|
||||
Gfx::IntPoint m_last_position;
|
||||
|
||||
void draw_line(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& start, const Gfx::IntPoint& end);
|
||||
void draw_point(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& point);
|
||||
};
|
||||
|
||||
}
|
133
Userland/Applications/PixelPaint/BucketTool.cpp
Normal file
133
Userland/Applications/PixelPaint/BucketTool.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "BucketTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <AK/Queue.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/Slider.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
BucketTool::BucketTool()
|
||||
{
|
||||
}
|
||||
|
||||
BucketTool::~BucketTool()
|
||||
{
|
||||
}
|
||||
|
||||
static float color_distance_squared(const Gfx::Color& lhs, const Gfx::Color& 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, const Gfx::IntPoint& start_position, Color target_color, Color fill_color, int threshold)
|
||||
{
|
||||
ASSERT(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<Gfx::IntPoint> queue;
|
||||
queue.enqueue(start_position);
|
||||
while (!queue.is_empty()) {
|
||||
auto position = queue.dequeue();
|
||||
|
||||
auto pixel_color = bitmap.get_pixel<Gfx::StorageFormat::RGBA32>(position.x(), position.y());
|
||||
if (color_distance_squared(pixel_color, target_color) > threshold_normalized_squared)
|
||||
continue;
|
||||
|
||||
bitmap.set_pixel<Gfx::StorageFormat::RGBA32>(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, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (!layer.rect().contains(event.position()))
|
||||
return;
|
||||
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
auto target_color = layer.bitmap().get_pixel(event.x(), event.y());
|
||||
|
||||
flood_fill(layer.bitmap(), event.position(), target_color, m_editor->color_for(event), m_threshold);
|
||||
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
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<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& threshold_container = m_properties_widget->add<GUI::Widget>();
|
||||
threshold_container.set_fixed_height(20);
|
||||
threshold_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& threshold_label = threshold_container.add<GUI::Label>("Threshold:");
|
||||
threshold_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
threshold_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& threshold_slider = threshold_container.add<GUI::HorizontalSlider>();
|
||||
threshold_slider.set_fixed_height(20);
|
||||
threshold_slider.set_range(0, 100);
|
||||
threshold_slider.set_value(m_threshold);
|
||||
threshold_slider.on_change = [this](int value) {
|
||||
m_threshold = value;
|
||||
};
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
46
Userland/Applications/PixelPaint/BucketTool.h
Normal file
46
Userland/Applications/PixelPaint/BucketTool.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class BucketTool final : public Tool {
|
||||
public:
|
||||
BucketTool();
|
||||
virtual ~BucketTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
int m_threshold { 0 };
|
||||
};
|
||||
|
||||
}
|
27
Userland/Applications/PixelPaint/CMakeLists.txt
Normal file
27
Userland/Applications/PixelPaint/CMakeLists.txt
Normal file
|
@ -0,0 +1,27 @@
|
|||
set(SOURCES
|
||||
BrushTool.cpp
|
||||
BucketTool.cpp
|
||||
CreateNewImageDialog.cpp
|
||||
CreateNewLayerDialog.cpp
|
||||
EllipseTool.cpp
|
||||
EraseTool.cpp
|
||||
Image.cpp
|
||||
ImageEditor.cpp
|
||||
Layer.cpp
|
||||
LayerListWidget.cpp
|
||||
LayerPropertiesWidget.cpp
|
||||
LineTool.cpp
|
||||
main.cpp
|
||||
MoveTool.cpp
|
||||
PaletteWidget.cpp
|
||||
PenTool.cpp
|
||||
PickerTool.cpp
|
||||
RectangleTool.cpp
|
||||
SprayTool.cpp
|
||||
ToolboxWidget.cpp
|
||||
ToolPropertiesWidget.cpp
|
||||
Tool.cpp
|
||||
)
|
||||
|
||||
serenity_app(PixelPaint ICON app-pixel-paint)
|
||||
target_link_libraries(PixelPaint LibGUI LibGfx)
|
91
Userland/Applications/PixelPaint/CreateNewImageDialog.cpp
Normal file
91
Userland/Applications/PixelPaint/CreateNewImageDialog.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "CreateNewImageDialog.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Button.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/SpinBox.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
CreateNewImageDialog::CreateNewImageDialog(GUI::Window* parent_window)
|
||||
: Dialog(parent_window)
|
||||
{
|
||||
set_title("Create new image");
|
||||
resize(200, 200);
|
||||
|
||||
auto& main_widget = set_main_widget<GUI::Widget>();
|
||||
main_widget.set_fill_with_background_color(true);
|
||||
|
||||
auto& layout = main_widget.set_layout<GUI::VerticalBoxLayout>();
|
||||
layout.set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto& name_label = main_widget.add<GUI::Label>("Name:");
|
||||
name_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
m_name_textbox = main_widget.add<GUI::TextBox>();
|
||||
m_name_textbox->on_change = [this] {
|
||||
m_image_name = m_name_textbox->text();
|
||||
};
|
||||
|
||||
auto& width_label = main_widget.add<GUI::Label>("Width:");
|
||||
width_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
auto& width_spinbox = main_widget.add<GUI::SpinBox>();
|
||||
|
||||
auto& height_label = main_widget.add<GUI::Label>("Height:");
|
||||
height_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
auto& height_spinbox = main_widget.add<GUI::SpinBox>();
|
||||
|
||||
auto& button_container = main_widget.add<GUI::Widget>();
|
||||
button_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& ok_button = button_container.add<GUI::Button>("OK");
|
||||
ok_button.on_click = [this](auto) {
|
||||
done(ExecOK);
|
||||
};
|
||||
|
||||
auto& cancel_button = button_container.add<GUI::Button>("Cancel");
|
||||
cancel_button.on_click = [this](auto) {
|
||||
done(ExecCancel);
|
||||
};
|
||||
|
||||
width_spinbox.on_change = [this](int value) {
|
||||
m_image_size.set_width(value);
|
||||
};
|
||||
|
||||
height_spinbox.on_change = [this](int value) {
|
||||
m_image_size.set_height(value);
|
||||
};
|
||||
|
||||
width_spinbox.set_range(0, 16384);
|
||||
height_spinbox.set_range(0, 16384);
|
||||
}
|
||||
|
||||
}
|
49
Userland/Applications/PixelPaint/CreateNewImageDialog.h
Normal file
49
Userland/Applications/PixelPaint/CreateNewImageDialog.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Dialog.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class CreateNewImageDialog final : public GUI::Dialog {
|
||||
C_OBJECT(CreateNewImageDialog)
|
||||
|
||||
public:
|
||||
const Gfx::IntSize& image_size() const { return m_image_size; }
|
||||
const String& image_name() const { return m_image_name; }
|
||||
|
||||
private:
|
||||
CreateNewImageDialog(GUI::Window* parent_window);
|
||||
|
||||
String m_image_name;
|
||||
Gfx::IntSize m_image_size;
|
||||
|
||||
RefPtr<GUI::TextBox> m_name_textbox;
|
||||
};
|
||||
|
||||
}
|
95
Userland/Applications/PixelPaint/CreateNewLayerDialog.cpp
Normal file
95
Userland/Applications/PixelPaint/CreateNewLayerDialog.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "CreateNewLayerDialog.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Button.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/SpinBox.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
CreateNewLayerDialog::CreateNewLayerDialog(const Gfx::IntSize& suggested_size, GUI::Window* parent_window)
|
||||
: Dialog(parent_window)
|
||||
{
|
||||
set_title("Create new layer");
|
||||
set_icon(parent_window->icon());
|
||||
resize(200, 200);
|
||||
|
||||
auto& main_widget = set_main_widget<GUI::Widget>();
|
||||
main_widget.set_fill_with_background_color(true);
|
||||
|
||||
auto& layout = main_widget.set_layout<GUI::VerticalBoxLayout>();
|
||||
layout.set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto& name_label = main_widget.add<GUI::Label>("Name:");
|
||||
name_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
m_name_textbox = main_widget.add<GUI::TextBox>();
|
||||
m_name_textbox->on_change = [this] {
|
||||
m_layer_name = m_name_textbox->text();
|
||||
};
|
||||
|
||||
auto& width_label = main_widget.add<GUI::Label>("Width:");
|
||||
width_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
auto& width_spinbox = main_widget.add<GUI::SpinBox>();
|
||||
|
||||
auto& height_label = main_widget.add<GUI::Label>("Height:");
|
||||
height_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
auto& height_spinbox = main_widget.add<GUI::SpinBox>();
|
||||
|
||||
auto& button_container = main_widget.add<GUI::Widget>();
|
||||
button_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& ok_button = button_container.add<GUI::Button>("OK");
|
||||
ok_button.on_click = [this](auto) {
|
||||
done(ExecOK);
|
||||
};
|
||||
|
||||
auto& cancel_button = button_container.add<GUI::Button>("Cancel");
|
||||
cancel_button.on_click = [this](auto) {
|
||||
done(ExecCancel);
|
||||
};
|
||||
|
||||
width_spinbox.on_change = [this](int value) {
|
||||
m_layer_size.set_width(value);
|
||||
};
|
||||
|
||||
height_spinbox.on_change = [this](int value) {
|
||||
m_layer_size.set_height(value);
|
||||
};
|
||||
|
||||
width_spinbox.set_range(0, 16384);
|
||||
height_spinbox.set_range(0, 16384);
|
||||
|
||||
width_spinbox.set_value(suggested_size.width());
|
||||
height_spinbox.set_value(suggested_size.height());
|
||||
}
|
||||
|
||||
}
|
49
Userland/Applications/PixelPaint/CreateNewLayerDialog.h
Normal file
49
Userland/Applications/PixelPaint/CreateNewLayerDialog.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Dialog.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class CreateNewLayerDialog final : public GUI::Dialog {
|
||||
C_OBJECT(CreateNewLayerDialog);
|
||||
|
||||
public:
|
||||
const Gfx::IntSize& layer_size() const { return m_layer_size; }
|
||||
const String& layer_name() const { return m_layer_name; }
|
||||
|
||||
private:
|
||||
CreateNewLayerDialog(const Gfx::IntSize& suggested_size, GUI::Window* parent_window);
|
||||
|
||||
Gfx::IntSize m_layer_size;
|
||||
String m_layer_name;
|
||||
|
||||
RefPtr<GUI::TextBox> m_name_textbox;
|
||||
};
|
||||
|
||||
}
|
137
Userland/Applications/PixelPaint/EllipseTool.cpp
Normal file
137
Userland/Applications/PixelPaint/EllipseTool.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "EllipseTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
EllipseTool::EllipseTool()
|
||||
{
|
||||
}
|
||||
|
||||
EllipseTool::~EllipseTool()
|
||||
{
|
||||
}
|
||||
|
||||
void EllipseTool::draw_using(GUI::Painter& painter, const Gfx::IntRect& ellipse_intersecting_rect)
|
||||
{
|
||||
switch (m_mode) {
|
||||
case Mode::Outline:
|
||||
painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), m_thickness);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void EllipseTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
if (m_drawing_button != GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_drawing_button = event.button();
|
||||
m_ellipse_start_position = event.position();
|
||||
m_ellipse_end_position = event.position();
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void EllipseTool::on_mouseup(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() == m_drawing_button) {
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
draw_using(painter, Gfx::IntRect::from_two_points(m_ellipse_start_position, m_ellipse_end_position));
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void EllipseTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_ellipse_end_position = event.position();
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void EllipseTool::on_second_paint(const Layer& layer, GUI::PaintEvent& event)
|
||||
{
|
||||
if (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<int>();
|
||||
auto preview_end = m_editor->layer_position_to_editor_position(layer, m_ellipse_end_position).to_type<int>();
|
||||
draw_using(painter, Gfx::IntRect::from_two_points(preview_start, preview_end));
|
||||
}
|
||||
|
||||
void EllipseTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
void EllipseTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_context_menu->add_action(GUI::Action::create("Outline", [this](auto&) {
|
||||
m_mode = Mode::Outline;
|
||||
}));
|
||||
m_context_menu->add_separator();
|
||||
m_thickness_actions.set_exclusive(true);
|
||||
auto insert_action = [&](int size, bool checked = false) {
|
||||
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
|
||||
m_thickness = size;
|
||||
});
|
||||
action->set_checked(checked);
|
||||
m_thickness_actions.add_action(*action);
|
||||
m_context_menu->add_action(move(action));
|
||||
};
|
||||
insert_action(1, true);
|
||||
insert_action(2);
|
||||
insert_action(3);
|
||||
insert_action(4);
|
||||
}
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
}
|
64
Userland/Applications/PixelPaint/EllipseTool.h
Normal file
64
Userland/Applications/PixelPaint/EllipseTool.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class EllipseTool final : public Tool {
|
||||
public:
|
||||
EllipseTool();
|
||||
virtual ~EllipseTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
|
||||
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
|
||||
private:
|
||||
enum class Mode {
|
||||
Outline,
|
||||
// FIXME: Add Mode::Fill
|
||||
};
|
||||
|
||||
void draw_using(GUI::Painter&, const Gfx::IntRect&);
|
||||
|
||||
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
|
||||
Gfx::IntPoint m_ellipse_start_position;
|
||||
Gfx::IntPoint m_ellipse_end_position;
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
int m_thickness { 1 };
|
||||
GUI::ActionGroup m_thickness_actions;
|
||||
Mode m_mode { Mode::Outline };
|
||||
};
|
||||
|
||||
}
|
120
Userland/Applications/PixelPaint/EraseTool.cpp
Normal file
120
Userland/Applications/PixelPaint/EraseTool.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "EraseTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
EraseTool::EraseTool()
|
||||
{
|
||||
}
|
||||
|
||||
EraseTool::~EraseTool()
|
||||
{
|
||||
}
|
||||
|
||||
Gfx::IntRect EraseTool::build_rect(const Gfx::IntPoint& pos, const Gfx::IntRect& widget_rect)
|
||||
{
|
||||
const int base_eraser_size = 10;
|
||||
const int eraser_size = (base_eraser_size * m_thickness);
|
||||
const int eraser_radius = eraser_size / 2;
|
||||
const auto ex = pos.x();
|
||||
const auto ey = pos.y();
|
||||
return Gfx::IntRect(ex - eraser_radius, ey - eraser_radius, eraser_size, eraser_size).intersected(widget_rect);
|
||||
}
|
||||
|
||||
void EraseTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
Gfx::IntRect r = build_rect(event.position(), layer.rect());
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
painter.clear_rect(r, get_color());
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
}
|
||||
|
||||
void EraseTool::on_mousemove(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.buttons() & GUI::MouseButton::Left || event.buttons() & GUI::MouseButton::Right) {
|
||||
Gfx::IntRect r = build_rect(event.position(), layer.rect());
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
painter.clear_rect(r, get_color());
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
}
|
||||
}
|
||||
|
||||
void EraseTool::on_mouseup(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
|
||||
void EraseTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
|
||||
auto eraser_color_toggler = GUI::Action::create_checkable("Use secondary color", [&](auto& action) {
|
||||
m_use_secondary_color = action.is_checked();
|
||||
});
|
||||
eraser_color_toggler->set_checked(m_use_secondary_color);
|
||||
|
||||
m_context_menu->add_action(eraser_color_toggler);
|
||||
m_context_menu->add_separator();
|
||||
|
||||
m_thickness_actions.set_exclusive(true);
|
||||
auto insert_action = [&](int size, bool checked = false) {
|
||||
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
|
||||
m_thickness = size;
|
||||
});
|
||||
action->set_checked(checked);
|
||||
m_thickness_actions.add_action(*action);
|
||||
m_context_menu->add_action(move(action));
|
||||
};
|
||||
insert_action(1, true);
|
||||
insert_action(2);
|
||||
insert_action(3);
|
||||
insert_action(4);
|
||||
}
|
||||
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
Color EraseTool::get_color() const
|
||||
{
|
||||
if (m_use_secondary_color)
|
||||
return m_editor->secondary_color();
|
||||
return Color(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
}
|
56
Userland/Applications/PixelPaint/EraseTool.h
Normal file
56
Userland/Applications/PixelPaint/EraseTool.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class EraseTool final : public Tool {
|
||||
public:
|
||||
EraseTool();
|
||||
virtual ~EraseTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
|
||||
|
||||
private:
|
||||
Gfx::Color get_color() const;
|
||||
Gfx::IntRect build_rect(const Gfx::IntPoint& pos, const Gfx::IntRect& widget_rect);
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
|
||||
bool m_use_secondary_color { false };
|
||||
int m_thickness { 1 };
|
||||
GUI::ActionGroup m_thickness_actions;
|
||||
};
|
||||
|
||||
}
|
191
Userland/Applications/PixelPaint/FilterParams.h
Normal file
191
Userland/Applications/PixelPaint/FilterParams.h
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Button.h>
|
||||
#include <LibGUI/CheckBox.h>
|
||||
#include <LibGUI/Dialog.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
#include <LibGfx/Filters/BoxBlurFilter.h>
|
||||
#include <LibGfx/Filters/GenericConvolutionFilter.h>
|
||||
#include <LibGfx/Filters/LaplacianFilter.h>
|
||||
#include <LibGfx/Filters/SharpenFilter.h>
|
||||
#include <LibGfx/Filters/SpatialGaussianBlurFilter.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
template<typename Filter>
|
||||
struct FilterParameters {
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
class GenericConvolutionFilterInputDialog : public GUI::Dialog {
|
||||
C_OBJECT(GenericConvolutionFilterInputDialog);
|
||||
|
||||
public:
|
||||
const Matrix<N, float>& matrix() const { return m_matrix; }
|
||||
bool should_wrap() const { return m_should_wrap; }
|
||||
|
||||
private:
|
||||
explicit GenericConvolutionFilterInputDialog(GUI::Window* parent_window)
|
||||
: Dialog(parent_window)
|
||||
{
|
||||
// FIXME: Help! Make this GUI less ugly.
|
||||
StringBuilder builder;
|
||||
builder.appendf("%zux%zu", N, N);
|
||||
builder.append(" Convolution");
|
||||
set_title(builder.string_view());
|
||||
|
||||
resize(200, 250);
|
||||
auto& main_widget = set_main_widget<GUI::Frame>();
|
||||
main_widget.set_frame_shape(Gfx::FrameShape::Container);
|
||||
main_widget.set_frame_shadow(Gfx::FrameShadow::Raised);
|
||||
main_widget.set_fill_with_background_color(true);
|
||||
auto& layout = main_widget.template set_layout<GUI::VerticalBoxLayout>();
|
||||
layout.set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
size_t index = 0;
|
||||
size_t columns = N;
|
||||
size_t rows = N;
|
||||
|
||||
for (size_t row = 0; row < rows; ++row) {
|
||||
auto& horizontal_container = main_widget.template add<GUI::Widget>();
|
||||
horizontal_container.template set_layout<GUI::HorizontalBoxLayout>();
|
||||
for (size_t column = 0; column < columns; ++column) {
|
||||
if (index < columns * rows) {
|
||||
auto& textbox = horizontal_container.template add<GUI::TextBox>();
|
||||
textbox.on_change = [&, row = row, column = column] {
|
||||
auto& element = m_matrix.elements()[row][column];
|
||||
char* endptr = nullptr;
|
||||
auto value = strtof(textbox.text().characters(), &endptr);
|
||||
if (endptr != nullptr)
|
||||
element = value;
|
||||
else
|
||||
textbox.set_text("");
|
||||
};
|
||||
} else {
|
||||
horizontal_container.template add<GUI::Widget>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto& norm_checkbox = main_widget.template add<GUI::CheckBox>("Normalize");
|
||||
norm_checkbox.set_checked(false);
|
||||
|
||||
auto& wrap_checkbox = main_widget.template add<GUI::CheckBox>("Wrap");
|
||||
wrap_checkbox.set_checked(m_should_wrap);
|
||||
|
||||
auto& button = main_widget.template add<GUI::Button>("Done");
|
||||
button.on_click = [&](auto) {
|
||||
m_should_wrap = wrap_checkbox.is_checked();
|
||||
if (norm_checkbox.is_checked())
|
||||
normalize(m_matrix);
|
||||
done(ExecOK);
|
||||
};
|
||||
}
|
||||
|
||||
Matrix<N, float> m_matrix {};
|
||||
bool m_should_wrap { false };
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct FilterParameters<Gfx::SpatialGaussianBlurFilter<N>> {
|
||||
static OwnPtr<typename Gfx::SpatialGaussianBlurFilter<N>::Parameters> get()
|
||||
{
|
||||
constexpr static ssize_t offset = N / 2;
|
||||
Matrix<N, float> kernel;
|
||||
auto sigma = 1.0f;
|
||||
auto s = 2.0f * sigma * sigma;
|
||||
|
||||
for (auto x = -offset; x <= offset; x++) {
|
||||
for (auto y = -offset; y <= offset; y++) {
|
||||
auto r = sqrt(x * x + y * y);
|
||||
kernel.elements()[x + offset][y + offset] = (exp(-(r * r) / s)) / (M_PI * s);
|
||||
}
|
||||
}
|
||||
|
||||
normalize(kernel);
|
||||
|
||||
return make<typename Gfx::GenericConvolutionFilter<N>::Parameters>(kernel);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FilterParameters<Gfx::SharpenFilter> {
|
||||
static OwnPtr<Gfx::GenericConvolutionFilter<3>::Parameters> get()
|
||||
{
|
||||
return make<Gfx::GenericConvolutionFilter<3>::Parameters>(Matrix<3, float>(0, -1, 0, -1, 5, -1, 0, -1, 0));
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FilterParameters<Gfx::LaplacianFilter> {
|
||||
static OwnPtr<Gfx::GenericConvolutionFilter<3>::Parameters> get(bool diagonal)
|
||||
{
|
||||
if (diagonal)
|
||||
return make<Gfx::GenericConvolutionFilter<3>::Parameters>(Matrix<3, float>(-1, -1, -1, -1, 8, -1, -1, -1, -1));
|
||||
|
||||
return make<Gfx::GenericConvolutionFilter<3>::Parameters>(Matrix<3, float>(0, -1, 0, -1, 4, -1, 0, -1, 0));
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct FilterParameters<Gfx::GenericConvolutionFilter<N>> {
|
||||
static OwnPtr<typename Gfx::GenericConvolutionFilter<N>::Parameters> get(GUI::Window* parent_window)
|
||||
{
|
||||
auto input = GenericConvolutionFilterInputDialog<N>::construct(parent_window);
|
||||
input->exec();
|
||||
if (input->result() == GUI::Dialog::ExecOK)
|
||||
return make<typename Gfx::GenericConvolutionFilter<N>::Parameters>(input->matrix(), input->should_wrap());
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct FilterParameters<Gfx::BoxBlurFilter<N>> {
|
||||
static OwnPtr<typename Gfx::GenericConvolutionFilter<N>::Parameters> get()
|
||||
{
|
||||
Matrix<N, float> kernel;
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
for (size_t j = 0; j < N; ++j) {
|
||||
kernel.elements()[i][j] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
normalize(kernel);
|
||||
|
||||
return make<typename Gfx::GenericConvolutionFilter<N>::Parameters>(kernel);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
331
Userland/Applications/PixelPaint/Image.cpp
Normal file
331
Userland/Applications/PixelPaint/Image.cpp
Normal file
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Image.h"
|
||||
#include "Layer.h"
|
||||
#include <AK/Base64.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonObjectSerializer.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/BMPWriter.h>
|
||||
#include <LibGfx/ImageDecoder.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//#define PAINT_DEBUG
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
RefPtr<Image> Image::create_with_size(const Gfx::IntSize& size)
|
||||
{
|
||||
if (size.is_empty())
|
||||
return nullptr;
|
||||
|
||||
if (size.width() > 16384 || size.height() > 16384)
|
||||
return nullptr;
|
||||
|
||||
return adopt(*new Image(size));
|
||||
}
|
||||
|
||||
Image::Image(const Gfx::IntSize& size)
|
||||
: m_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
void Image::paint_into(GUI::Painter& painter, const Gfx::IntRect& dest_rect)
|
||||
{
|
||||
float scale = (float)dest_rect.width() / (float)rect().width();
|
||||
Gfx::PainterStateSaver saver(painter);
|
||||
painter.add_clip_rect(dest_rect);
|
||||
for (auto& layer : m_layers) {
|
||||
if (!layer.is_visible())
|
||||
continue;
|
||||
auto target = dest_rect.translated(layer.location().x() * scale, layer.location().y() * scale);
|
||||
target.set_size(layer.size().width() * scale, layer.size().height() * scale);
|
||||
painter.draw_scaled_bitmap(target, layer.bitmap(), layer.rect(), (float)layer.opacity_percent() / 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Image> Image::create_from_file(const String& file_path)
|
||||
{
|
||||
auto file = fopen(file_path.characters(), "r");
|
||||
fseek(file, 0L, SEEK_END);
|
||||
auto length = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
auto buffer = ByteBuffer::create_uninitialized(length);
|
||||
fread(buffer.data(), sizeof(u8), length, file);
|
||||
fclose(file);
|
||||
|
||||
auto json_or_error = JsonValue::from_string(String::copy(buffer));
|
||||
if (!json_or_error.has_value())
|
||||
return nullptr;
|
||||
|
||||
auto json = json_or_error.value().as_object();
|
||||
auto image = create_with_size({ json.get("width").to_i32(), json.get("height").to_i32() });
|
||||
json.get("layers").as_array().for_each([&](JsonValue json_layer) {
|
||||
auto json_layer_object = json_layer.as_object();
|
||||
auto width = json_layer_object.get("width").to_i32();
|
||||
auto height = json_layer_object.get("height").to_i32();
|
||||
auto name = json_layer_object.get("name").as_string();
|
||||
auto layer = Layer::create_with_size(*image, { width, height }, name);
|
||||
layer->set_location({ json_layer_object.get("locationx").to_i32(), json_layer_object.get("locationy").to_i32() });
|
||||
layer->set_opacity_percent(json_layer_object.get("opacity_percent").to_i32());
|
||||
layer->set_visible(json_layer_object.get("visible").as_bool());
|
||||
layer->set_selected(json_layer_object.get("selected").as_bool());
|
||||
|
||||
auto bitmap_base64_encoded = json_layer_object.get("bitmap").as_string();
|
||||
auto bitmap_data = decode_base64(bitmap_base64_encoded);
|
||||
auto image_decoder = Gfx::ImageDecoder::create(bitmap_data);
|
||||
layer->set_bitmap(*image_decoder->bitmap());
|
||||
image->add_layer(*layer);
|
||||
});
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void Image::save(const String& file_path) const
|
||||
{
|
||||
// Build json file
|
||||
StringBuilder builder;
|
||||
JsonObjectSerializer json(builder);
|
||||
json.add("width", m_size.width());
|
||||
json.add("height", m_size.height());
|
||||
{
|
||||
auto json_layers = json.add_array("layers");
|
||||
for (const auto& layer : m_layers) {
|
||||
Gfx::BMPWriter bmp_dumber;
|
||||
auto json_layer = json_layers.add_object();
|
||||
json_layer.add("width", layer.size().width());
|
||||
json_layer.add("height", layer.size().height());
|
||||
json_layer.add("name", layer.name());
|
||||
json_layer.add("locationx", layer.location().x());
|
||||
json_layer.add("locationy", layer.location().y());
|
||||
json_layer.add("opacity_percent", layer.opacity_percent());
|
||||
json_layer.add("visible", layer.is_visible());
|
||||
json_layer.add("selected", layer.is_selected());
|
||||
json_layer.add("bitmap", encode_base64(bmp_dumber.dump(layer.bitmap())));
|
||||
}
|
||||
}
|
||||
json.finish();
|
||||
|
||||
// Write json to disk
|
||||
auto file = fopen(file_path.characters(), "w");
|
||||
auto byte_buffer = builder.to_byte_buffer();
|
||||
fwrite(byte_buffer.data(), sizeof(u8), byte_buffer.size(), file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void Image::export_bmp(const String& file_path)
|
||||
{
|
||||
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, m_size);
|
||||
GUI::Painter painter(*bitmap);
|
||||
paint_into(painter, { 0, 0, m_size.width(), m_size.height() });
|
||||
|
||||
Gfx::BMPWriter dumper;
|
||||
auto bmp = dumper.dump(bitmap);
|
||||
auto file = fopen(file_path.characters(), "wb");
|
||||
fwrite(bmp.data(), sizeof(u8), bmp.size(), file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void Image::add_layer(NonnullRefPtr<Layer> layer)
|
||||
{
|
||||
for (auto& existing_layer : m_layers) {
|
||||
ASSERT(&existing_layer != layer.ptr());
|
||||
}
|
||||
m_layers.append(move(layer));
|
||||
|
||||
for (auto* client : m_clients)
|
||||
client->image_did_add_layer(m_layers.size() - 1);
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
RefPtr<Image> Image::take_snapshot() const
|
||||
{
|
||||
auto snapshot = create_with_size(m_size);
|
||||
for (const auto& layer : m_layers)
|
||||
snapshot->add_layer(*Layer::create_snapshot(*snapshot, layer));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void Image::restore_snapshot(const Image& snapshot)
|
||||
{
|
||||
m_layers.clear();
|
||||
select_layer(nullptr);
|
||||
for (const auto& snapshot_layer : snapshot.m_layers) {
|
||||
auto layer = Layer::create_snapshot(*this, snapshot_layer);
|
||||
if (layer->is_selected())
|
||||
select_layer(layer.ptr());
|
||||
add_layer(*layer);
|
||||
}
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
size_t Image::index_of(const Layer& layer) const
|
||||
{
|
||||
for (size_t i = 0; i < m_layers.size(); ++i) {
|
||||
if (&m_layers.at(i) == &layer)
|
||||
return i;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Image::move_layer_to_back(Layer& layer)
|
||||
{
|
||||
NonnullRefPtr<Layer> protector(layer);
|
||||
auto index = index_of(layer);
|
||||
m_layers.remove(index);
|
||||
m_layers.prepend(layer);
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
void Image::move_layer_to_front(Layer& layer)
|
||||
{
|
||||
NonnullRefPtr<Layer> protector(layer);
|
||||
auto index = index_of(layer);
|
||||
m_layers.remove(index);
|
||||
m_layers.append(layer);
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
void Image::move_layer_down(Layer& layer)
|
||||
{
|
||||
NonnullRefPtr<Layer> protector(layer);
|
||||
auto index = index_of(layer);
|
||||
if (!index)
|
||||
return;
|
||||
m_layers.remove(index);
|
||||
m_layers.insert(index - 1, layer);
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
void Image::move_layer_up(Layer& layer)
|
||||
{
|
||||
NonnullRefPtr<Layer> protector(layer);
|
||||
auto index = index_of(layer);
|
||||
if (index == m_layers.size() - 1)
|
||||
return;
|
||||
m_layers.remove(index);
|
||||
m_layers.insert(index + 1, layer);
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
void Image::change_layer_index(size_t old_index, size_t new_index)
|
||||
{
|
||||
ASSERT(old_index < m_layers.size());
|
||||
ASSERT(new_index < m_layers.size());
|
||||
auto layer = m_layers.take(old_index);
|
||||
m_layers.insert(new_index, move(layer));
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
void Image::did_modify_layer_stack()
|
||||
{
|
||||
for (auto* client : m_clients)
|
||||
client->image_did_modify_layer_stack();
|
||||
|
||||
did_change();
|
||||
}
|
||||
|
||||
void Image::remove_layer(Layer& layer)
|
||||
{
|
||||
NonnullRefPtr<Layer> protector(layer);
|
||||
auto index = index_of(layer);
|
||||
m_layers.remove(index);
|
||||
|
||||
for (auto* client : m_clients)
|
||||
client->image_did_remove_layer(index);
|
||||
|
||||
did_modify_layer_stack();
|
||||
}
|
||||
|
||||
void Image::select_layer(Layer* layer)
|
||||
{
|
||||
for (auto* client : m_clients)
|
||||
client->image_select_layer(layer);
|
||||
}
|
||||
|
||||
void Image::add_client(ImageClient& client)
|
||||
{
|
||||
ASSERT(!m_clients.contains(&client));
|
||||
m_clients.set(&client);
|
||||
}
|
||||
|
||||
void Image::remove_client(ImageClient& client)
|
||||
{
|
||||
ASSERT(m_clients.contains(&client));
|
||||
m_clients.remove(&client);
|
||||
}
|
||||
|
||||
void Image::layer_did_modify_bitmap(Badge<Layer>, const Layer& layer)
|
||||
{
|
||||
auto layer_index = index_of(layer);
|
||||
for (auto* client : m_clients)
|
||||
client->image_did_modify_layer(layer_index);
|
||||
|
||||
did_change();
|
||||
}
|
||||
|
||||
void Image::layer_did_modify_properties(Badge<Layer>, const Layer& layer)
|
||||
{
|
||||
auto layer_index = index_of(layer);
|
||||
for (auto* client : m_clients)
|
||||
client->image_did_modify_layer(layer_index);
|
||||
|
||||
did_change();
|
||||
}
|
||||
|
||||
void Image::did_change()
|
||||
{
|
||||
for (auto* client : m_clients)
|
||||
client->image_did_change();
|
||||
}
|
||||
|
||||
ImageUndoCommand::ImageUndoCommand(Image& image)
|
||||
: m_snapshot(image.take_snapshot())
|
||||
, m_image(image)
|
||||
{
|
||||
}
|
||||
|
||||
void ImageUndoCommand::undo()
|
||||
{
|
||||
m_image.restore_snapshot(*m_snapshot);
|
||||
}
|
||||
|
||||
void ImageUndoCommand::redo()
|
||||
{
|
||||
undo();
|
||||
}
|
||||
|
||||
}
|
114
Userland/Applications/PixelPaint/Image.h
Normal file
114
Userland/Applications/PixelPaint/Image.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/Command.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/Size.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class Layer;
|
||||
|
||||
class ImageClient {
|
||||
public:
|
||||
virtual void image_did_add_layer(size_t) { }
|
||||
virtual void image_did_remove_layer(size_t) { }
|
||||
virtual void image_did_modify_layer(size_t) { }
|
||||
virtual void image_did_modify_layer_stack() { }
|
||||
virtual void image_did_change() { }
|
||||
virtual void image_select_layer(Layer*) { }
|
||||
};
|
||||
|
||||
class Image : public RefCounted<Image> {
|
||||
public:
|
||||
static RefPtr<Image> create_with_size(const Gfx::IntSize&);
|
||||
static RefPtr<Image> create_from_file(const String& file_path);
|
||||
|
||||
size_t layer_count() const { return m_layers.size(); }
|
||||
const Layer& layer(size_t index) const { return m_layers.at(index); }
|
||||
Layer& layer(size_t index) { return m_layers.at(index); }
|
||||
|
||||
const Gfx::IntSize& size() const { return m_size; }
|
||||
Gfx::IntRect rect() const { return { {}, m_size }; }
|
||||
|
||||
void add_layer(NonnullRefPtr<Layer>);
|
||||
RefPtr<Image> take_snapshot() const;
|
||||
void restore_snapshot(const Image&);
|
||||
|
||||
void paint_into(GUI::Painter&, const Gfx::IntRect& dest_rect);
|
||||
void save(const String& file_path) const;
|
||||
void export_bmp(const String& file_path);
|
||||
|
||||
void move_layer_to_front(Layer&);
|
||||
void move_layer_to_back(Layer&);
|
||||
void move_layer_up(Layer&);
|
||||
void move_layer_down(Layer&);
|
||||
void change_layer_index(size_t old_index, size_t new_index);
|
||||
void remove_layer(Layer&);
|
||||
void select_layer(Layer*);
|
||||
|
||||
void add_client(ImageClient&);
|
||||
void remove_client(ImageClient&);
|
||||
|
||||
void layer_did_modify_bitmap(Badge<Layer>, const Layer&);
|
||||
void layer_did_modify_properties(Badge<Layer>, const Layer&);
|
||||
|
||||
size_t index_of(const Layer&) const;
|
||||
|
||||
private:
|
||||
explicit Image(const Gfx::IntSize&);
|
||||
|
||||
void did_change();
|
||||
void did_modify_layer_stack();
|
||||
|
||||
Gfx::IntSize m_size;
|
||||
NonnullRefPtrVector<Layer> m_layers;
|
||||
|
||||
HashTable<ImageClient*> m_clients;
|
||||
};
|
||||
|
||||
class ImageUndoCommand : public GUI::Command {
|
||||
public:
|
||||
ImageUndoCommand(Image& image);
|
||||
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
RefPtr<Image> m_snapshot;
|
||||
Image& m_image;
|
||||
};
|
||||
|
||||
}
|
418
Userland/Applications/PixelPaint/ImageEditor.cpp
Normal file
418
Userland/Applications/PixelPaint/ImageEditor.cpp
Normal file
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ImageEditor.h"
|
||||
#include "Image.h"
|
||||
#include "Layer.h"
|
||||
#include "MoveTool.h"
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/Command.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
ImageEditor::ImageEditor()
|
||||
: m_undo_stack(make<GUI::UndoStack>())
|
||||
{
|
||||
set_focus_policy(GUI::FocusPolicy::StrongFocus);
|
||||
}
|
||||
|
||||
ImageEditor::~ImageEditor()
|
||||
{
|
||||
if (m_image)
|
||||
m_image->remove_client(*this);
|
||||
}
|
||||
|
||||
void ImageEditor::set_image(RefPtr<Image> image)
|
||||
{
|
||||
if (m_image)
|
||||
m_image->remove_client(*this);
|
||||
|
||||
m_image = move(image);
|
||||
m_active_layer = nullptr;
|
||||
m_undo_stack = make<GUI::UndoStack>();
|
||||
m_undo_stack->push(make<ImageUndoCommand>(*m_image));
|
||||
update();
|
||||
relayout();
|
||||
|
||||
if (m_image)
|
||||
m_image->add_client(*this);
|
||||
}
|
||||
|
||||
void ImageEditor::did_complete_action()
|
||||
{
|
||||
if (!m_image)
|
||||
return;
|
||||
m_undo_stack->finalize_current_combo();
|
||||
m_undo_stack->push(make<ImageUndoCommand>(*m_image));
|
||||
}
|
||||
|
||||
bool ImageEditor::undo()
|
||||
{
|
||||
if (!m_image)
|
||||
return false;
|
||||
if (m_undo_stack->can_undo()) {
|
||||
m_undo_stack->undo();
|
||||
layers_did_change();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImageEditor::redo()
|
||||
{
|
||||
if (!m_image)
|
||||
return false;
|
||||
if (m_undo_stack->can_redo()) {
|
||||
m_undo_stack->redo();
|
||||
layers_did_change();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageEditor::paint_event(GUI::PaintEvent& event)
|
||||
{
|
||||
GUI::Frame::paint_event(event);
|
||||
|
||||
GUI::Painter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
painter.add_clip_rect(frame_inner_rect());
|
||||
|
||||
Gfx::StylePainter::paint_transparency_grid(painter, rect(), palette());
|
||||
|
||||
if (m_image) {
|
||||
painter.draw_rect(m_editor_image_rect.inflated(2, 2), Color::Black);
|
||||
m_image->paint_into(painter, m_editor_image_rect);
|
||||
}
|
||||
|
||||
if (m_active_layer) {
|
||||
painter.draw_rect(enclosing_int_rect(image_rect_to_editor_rect(m_active_layer->relative_rect())).inflated(2, 2), Color::Black);
|
||||
}
|
||||
}
|
||||
|
||||
Gfx::FloatRect ImageEditor::layer_rect_to_editor_rect(const Layer& layer, const Gfx::IntRect& layer_rect) const
|
||||
{
|
||||
return image_rect_to_editor_rect(layer_rect.translated(layer.location()));
|
||||
}
|
||||
|
||||
Gfx::FloatRect ImageEditor::image_rect_to_editor_rect(const Gfx::IntRect& image_rect) const
|
||||
{
|
||||
Gfx::FloatRect editor_rect;
|
||||
editor_rect.set_location(image_position_to_editor_position(image_rect.location()));
|
||||
editor_rect.set_width((float)image_rect.width() * m_scale);
|
||||
editor_rect.set_height((float)image_rect.height() * m_scale);
|
||||
return editor_rect;
|
||||
}
|
||||
|
||||
Gfx::FloatRect ImageEditor::editor_rect_to_image_rect(const Gfx::IntRect& editor_rect) const
|
||||
{
|
||||
Gfx::FloatRect image_rect;
|
||||
image_rect.set_location(editor_position_to_image_position(editor_rect.location()));
|
||||
image_rect.set_width((float)editor_rect.width() / m_scale);
|
||||
image_rect.set_height((float)editor_rect.height() / m_scale);
|
||||
return image_rect;
|
||||
}
|
||||
|
||||
Gfx::FloatPoint ImageEditor::layer_position_to_editor_position(const Layer& layer, const Gfx::IntPoint& layer_position) const
|
||||
{
|
||||
return image_position_to_editor_position(layer_position.translated(layer.location()));
|
||||
}
|
||||
|
||||
Gfx::FloatPoint ImageEditor::image_position_to_editor_position(const Gfx::IntPoint& image_position) const
|
||||
{
|
||||
Gfx::FloatPoint editor_position;
|
||||
editor_position.set_x(m_editor_image_rect.x() + ((float)image_position.x() * m_scale));
|
||||
editor_position.set_y(m_editor_image_rect.y() + ((float)image_position.y() * m_scale));
|
||||
return editor_position;
|
||||
}
|
||||
|
||||
Gfx::FloatPoint ImageEditor::editor_position_to_image_position(const Gfx::IntPoint& editor_position) const
|
||||
{
|
||||
Gfx::FloatPoint image_position;
|
||||
image_position.set_x(((float)editor_position.x() - m_editor_image_rect.x()) / m_scale);
|
||||
image_position.set_y(((float)editor_position.y() - m_editor_image_rect.y()) / m_scale);
|
||||
return image_position;
|
||||
}
|
||||
|
||||
void ImageEditor::second_paint_event(GUI::PaintEvent& event)
|
||||
{
|
||||
if (m_active_tool && m_active_layer)
|
||||
m_active_tool->on_second_paint(*m_active_layer, event);
|
||||
}
|
||||
|
||||
GUI::MouseEvent ImageEditor::event_with_pan_and_scale_applied(const GUI::MouseEvent& event) const
|
||||
{
|
||||
auto image_position = editor_position_to_image_position(event.position());
|
||||
return {
|
||||
static_cast<GUI::Event::Type>(event.type()),
|
||||
Gfx::IntPoint(image_position.x(), image_position.y()),
|
||||
event.buttons(),
|
||||
event.button(),
|
||||
event.modifiers(),
|
||||
event.wheel_delta()
|
||||
};
|
||||
}
|
||||
|
||||
GUI::MouseEvent ImageEditor::event_adjusted_for_layer(const GUI::MouseEvent& event, const Layer& layer) const
|
||||
{
|
||||
auto image_position = editor_position_to_image_position(event.position());
|
||||
image_position.move_by(-layer.location().x(), -layer.location().y());
|
||||
return {
|
||||
static_cast<GUI::Event::Type>(event.type()),
|
||||
Gfx::IntPoint(image_position.x(), image_position.y()),
|
||||
event.buttons(),
|
||||
event.button(),
|
||||
event.modifiers(),
|
||||
event.wheel_delta()
|
||||
};
|
||||
}
|
||||
|
||||
void ImageEditor::mousedown_event(GUI::MouseEvent& event)
|
||||
{
|
||||
if (event.button() == GUI::MouseButton::Middle) {
|
||||
m_click_position = event.position();
|
||||
m_saved_pan_origin = m_pan_origin;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_active_tool)
|
||||
return;
|
||||
|
||||
if (is<MoveTool>(*m_active_tool)) {
|
||||
if (auto* other_layer = layer_at_editor_position(event.position())) {
|
||||
set_active_layer(other_layer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_active_layer)
|
||||
return;
|
||||
|
||||
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
|
||||
auto image_event = event_with_pan_and_scale_applied(event);
|
||||
m_active_tool->on_mousedown(*m_active_layer, layer_event, image_event);
|
||||
}
|
||||
|
||||
void ImageEditor::mousemove_event(GUI::MouseEvent& event)
|
||||
{
|
||||
if (event.buttons() & GUI::MouseButton::Middle) {
|
||||
auto delta = event.position() - m_click_position;
|
||||
m_pan_origin = m_saved_pan_origin.translated(
|
||||
-delta.x() / m_scale,
|
||||
-delta.y() / m_scale);
|
||||
|
||||
relayout();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_active_layer || !m_active_tool)
|
||||
return;
|
||||
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
|
||||
auto image_event = event_with_pan_and_scale_applied(event);
|
||||
|
||||
m_active_tool->on_mousemove(*m_active_layer, layer_event, image_event);
|
||||
}
|
||||
|
||||
void ImageEditor::mouseup_event(GUI::MouseEvent& event)
|
||||
{
|
||||
if (!m_active_layer || !m_active_tool)
|
||||
return;
|
||||
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
|
||||
auto image_event = event_with_pan_and_scale_applied(event);
|
||||
m_active_tool->on_mouseup(*m_active_layer, layer_event, image_event);
|
||||
}
|
||||
|
||||
void ImageEditor::mousewheel_event(GUI::MouseEvent& event)
|
||||
{
|
||||
auto old_scale = m_scale;
|
||||
|
||||
m_scale += -event.wheel_delta() * 0.1f;
|
||||
if (m_scale < 0.1f)
|
||||
m_scale = 0.1f;
|
||||
if (m_scale > 100.0f)
|
||||
m_scale = 100.0f;
|
||||
|
||||
auto focus_point = Gfx::FloatPoint(
|
||||
m_pan_origin.x() - ((float)event.x() - (float)width() / 2.0) / old_scale,
|
||||
m_pan_origin.y() - ((float)event.y() - (float)height() / 2.0) / old_scale);
|
||||
|
||||
m_pan_origin = Gfx::FloatPoint(
|
||||
focus_point.x() - m_scale / old_scale * (focus_point.x() - m_pan_origin.x()),
|
||||
focus_point.y() - m_scale / old_scale * (focus_point.y() - m_pan_origin.y()));
|
||||
|
||||
if (old_scale != m_scale)
|
||||
relayout();
|
||||
}
|
||||
|
||||
void ImageEditor::context_menu_event(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_active_layer || !m_active_tool)
|
||||
return;
|
||||
m_active_tool->on_context_menu(*m_active_layer, event);
|
||||
}
|
||||
|
||||
void ImageEditor::resize_event(GUI::ResizeEvent& event)
|
||||
{
|
||||
relayout();
|
||||
GUI::Frame::resize_event(event);
|
||||
}
|
||||
|
||||
void ImageEditor::keydown_event(GUI::KeyEvent& event)
|
||||
{
|
||||
if (m_active_tool)
|
||||
m_active_tool->on_keydown(event);
|
||||
}
|
||||
|
||||
void ImageEditor::keyup_event(GUI::KeyEvent& event)
|
||||
{
|
||||
if (m_active_tool)
|
||||
m_active_tool->on_keyup(event);
|
||||
}
|
||||
|
||||
void ImageEditor::set_active_layer(Layer* layer)
|
||||
{
|
||||
if (m_active_layer == layer)
|
||||
return;
|
||||
m_active_layer = layer;
|
||||
|
||||
if (m_active_layer) {
|
||||
size_t index = 0;
|
||||
for (; index < m_image->layer_count(); ++index) {
|
||||
if (&m_image->layer(index) == layer)
|
||||
break;
|
||||
}
|
||||
if (on_active_layer_change)
|
||||
on_active_layer_change(layer);
|
||||
} else {
|
||||
if (on_active_layer_change)
|
||||
on_active_layer_change({});
|
||||
}
|
||||
|
||||
layers_did_change();
|
||||
}
|
||||
|
||||
void ImageEditor::set_active_tool(Tool* tool)
|
||||
{
|
||||
if (m_active_tool == tool)
|
||||
return;
|
||||
|
||||
if (m_active_tool)
|
||||
m_active_tool->clear();
|
||||
|
||||
m_active_tool = tool;
|
||||
|
||||
if (m_active_tool)
|
||||
m_active_tool->setup(*this);
|
||||
}
|
||||
|
||||
void ImageEditor::layers_did_change()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
Color ImageEditor::color_for(GUI::MouseButton button) const
|
||||
{
|
||||
if (button == GUI::MouseButton::Left)
|
||||
return m_primary_color;
|
||||
if (button == GUI::MouseButton::Right)
|
||||
return m_secondary_color;
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Color ImageEditor::color_for(const GUI::MouseEvent& event) const
|
||||
{
|
||||
if (event.buttons() & GUI::MouseButton::Left)
|
||||
return m_primary_color;
|
||||
if (event.buttons() & GUI::MouseButton::Right)
|
||||
return m_secondary_color;
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void ImageEditor::set_primary_color(Color color)
|
||||
{
|
||||
if (m_primary_color == color)
|
||||
return;
|
||||
m_primary_color = color;
|
||||
if (on_primary_color_change)
|
||||
on_primary_color_change(color);
|
||||
}
|
||||
|
||||
void ImageEditor::set_secondary_color(Color color)
|
||||
{
|
||||
if (m_secondary_color == color)
|
||||
return;
|
||||
m_secondary_color = color;
|
||||
if (on_secondary_color_change)
|
||||
on_secondary_color_change(color);
|
||||
}
|
||||
|
||||
Layer* ImageEditor::layer_at_editor_position(const Gfx::IntPoint& editor_position)
|
||||
{
|
||||
if (!m_image)
|
||||
return nullptr;
|
||||
auto image_position = editor_position_to_image_position(editor_position);
|
||||
for (ssize_t i = m_image->layer_count() - 1; i >= 0; --i) {
|
||||
auto& layer = m_image->layer(i);
|
||||
if (!layer.is_visible())
|
||||
continue;
|
||||
if (layer.relative_rect().contains(Gfx::IntPoint(image_position.x(), image_position.y())))
|
||||
return const_cast<Layer*>(&layer);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ImageEditor::relayout()
|
||||
{
|
||||
if (!image())
|
||||
return;
|
||||
auto& image = *this->image();
|
||||
|
||||
Gfx::IntSize new_size;
|
||||
new_size.set_width(image.size().width() * m_scale);
|
||||
new_size.set_height(image.size().height() * m_scale);
|
||||
m_editor_image_rect.set_size(new_size);
|
||||
|
||||
Gfx::IntPoint new_location;
|
||||
new_location.set_x((width() / 2) - (new_size.width() / 2) - (m_pan_origin.x() * m_scale));
|
||||
new_location.set_y((height() / 2) - (new_size.height() / 2) - (m_pan_origin.y() * m_scale));
|
||||
m_editor_image_rect.set_location(new_location);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ImageEditor::image_did_change()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void ImageEditor::image_select_layer(Layer* layer)
|
||||
{
|
||||
set_active_layer(layer);
|
||||
}
|
||||
|
||||
}
|
125
Userland/Applications/PixelPaint/ImageEditor.h
Normal file
125
Userland/Applications/PixelPaint/ImageEditor.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Image.h"
|
||||
#include <LibGUI/Frame.h>
|
||||
#include <LibGUI/UndoStack.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class Layer;
|
||||
class Tool;
|
||||
|
||||
class ImageEditor final
|
||||
: public GUI::Frame
|
||||
, public ImageClient {
|
||||
C_OBJECT(ImageEditor);
|
||||
|
||||
public:
|
||||
virtual ~ImageEditor() override;
|
||||
|
||||
const Image* image() const { return m_image; }
|
||||
Image* image() { return m_image; }
|
||||
|
||||
void set_image(RefPtr<Image>);
|
||||
|
||||
Layer* active_layer() { return m_active_layer; }
|
||||
void set_active_layer(Layer*);
|
||||
|
||||
Tool* active_tool() { return m_active_tool; }
|
||||
void set_active_tool(Tool*);
|
||||
|
||||
void did_complete_action();
|
||||
bool undo();
|
||||
bool redo();
|
||||
|
||||
void layers_did_change();
|
||||
|
||||
Layer* layer_at_editor_position(const Gfx::IntPoint&);
|
||||
|
||||
Color primary_color() const { return m_primary_color; }
|
||||
void set_primary_color(Color);
|
||||
|
||||
Color secondary_color() const { return m_secondary_color; }
|
||||
void set_secondary_color(Color);
|
||||
|
||||
Color color_for(const GUI::MouseEvent&) const;
|
||||
Color color_for(GUI::MouseButton) const;
|
||||
|
||||
Function<void(Color)> on_primary_color_change;
|
||||
Function<void(Color)> on_secondary_color_change;
|
||||
|
||||
Function<void(Layer*)> on_active_layer_change;
|
||||
|
||||
Gfx::FloatRect layer_rect_to_editor_rect(const Layer&, const Gfx::IntRect&) const;
|
||||
Gfx::FloatRect image_rect_to_editor_rect(const Gfx::IntRect&) const;
|
||||
Gfx::FloatRect editor_rect_to_image_rect(const Gfx::IntRect&) const;
|
||||
Gfx::FloatPoint layer_position_to_editor_position(const Layer&, const Gfx::IntPoint&) const;
|
||||
Gfx::FloatPoint image_position_to_editor_position(const Gfx::IntPoint&) const;
|
||||
Gfx::FloatPoint editor_position_to_image_position(const Gfx::IntPoint&) const;
|
||||
|
||||
private:
|
||||
ImageEditor();
|
||||
|
||||
virtual void paint_event(GUI::PaintEvent&) override;
|
||||
virtual void second_paint_event(GUI::PaintEvent&) override;
|
||||
virtual void mousedown_event(GUI::MouseEvent&) override;
|
||||
virtual void mousemove_event(GUI::MouseEvent&) override;
|
||||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||
virtual void mousewheel_event(GUI::MouseEvent&) override;
|
||||
virtual void keydown_event(GUI::KeyEvent&) override;
|
||||
virtual void keyup_event(GUI::KeyEvent&) override;
|
||||
virtual void context_menu_event(GUI::ContextMenuEvent&) override;
|
||||
virtual void resize_event(GUI::ResizeEvent&) override;
|
||||
|
||||
virtual void image_did_change() override;
|
||||
virtual void image_select_layer(Layer*) override;
|
||||
|
||||
GUI::MouseEvent event_adjusted_for_layer(const GUI::MouseEvent&, const Layer&) const;
|
||||
GUI::MouseEvent event_with_pan_and_scale_applied(const GUI::MouseEvent&) const;
|
||||
|
||||
void relayout();
|
||||
|
||||
RefPtr<Image> m_image;
|
||||
RefPtr<Layer> m_active_layer;
|
||||
OwnPtr<GUI::UndoStack> m_undo_stack;
|
||||
|
||||
Tool* m_active_tool { nullptr };
|
||||
|
||||
Color m_primary_color { Color::Black };
|
||||
Color m_secondary_color { Color::White };
|
||||
|
||||
Gfx::IntRect m_editor_image_rect;
|
||||
float m_scale { 1 };
|
||||
Gfx::FloatPoint m_pan_origin;
|
||||
Gfx::FloatPoint m_saved_pan_origin;
|
||||
Gfx::IntPoint m_click_position;
|
||||
};
|
||||
|
||||
}
|
108
Userland/Applications/PixelPaint/Layer.cpp
Normal file
108
Userland/Applications/PixelPaint/Layer.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Layer.h"
|
||||
#include "Image.h"
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
RefPtr<Layer> Layer::create_with_size(Image& image, const Gfx::IntSize& size, const String& name)
|
||||
{
|
||||
if (size.is_empty())
|
||||
return nullptr;
|
||||
|
||||
if (size.width() > 16384 || size.height() > 16384)
|
||||
return nullptr;
|
||||
|
||||
return adopt(*new Layer(image, size, name));
|
||||
}
|
||||
|
||||
RefPtr<Layer> Layer::create_with_bitmap(Image& image, const Gfx::Bitmap& bitmap, const String& name)
|
||||
{
|
||||
if (bitmap.size().is_empty())
|
||||
return nullptr;
|
||||
|
||||
if (bitmap.size().width() > 16384 || bitmap.size().height() > 16384)
|
||||
return nullptr;
|
||||
|
||||
return adopt(*new Layer(image, bitmap, name));
|
||||
}
|
||||
|
||||
RefPtr<Layer> Layer::create_snapshot(Image& image, const Layer& layer)
|
||||
{
|
||||
auto snapshot = create_with_bitmap(image, *layer.bitmap().clone(), layer.name());
|
||||
snapshot->set_opacity_percent(layer.opacity_percent());
|
||||
snapshot->set_visible(layer.is_visible());
|
||||
snapshot->set_selected(layer.is_selected());
|
||||
snapshot->set_location(layer.location());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
Layer::Layer(Image& image, const Gfx::IntSize& size, const String& name)
|
||||
: m_image(image)
|
||||
, m_name(name)
|
||||
{
|
||||
m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, size);
|
||||
}
|
||||
|
||||
Layer::Layer(Image& image, const Gfx::Bitmap& bitmap, const String& name)
|
||||
: m_image(image)
|
||||
, m_name(name)
|
||||
, m_bitmap(bitmap)
|
||||
{
|
||||
}
|
||||
|
||||
void Layer::did_modify_bitmap(Image& image)
|
||||
{
|
||||
image.layer_did_modify_bitmap({}, *this);
|
||||
}
|
||||
|
||||
void Layer::set_visible(bool visible)
|
||||
{
|
||||
if (m_visible == visible)
|
||||
return;
|
||||
m_visible = visible;
|
||||
m_image.layer_did_modify_properties({}, *this);
|
||||
}
|
||||
|
||||
void Layer::set_opacity_percent(int opacity_percent)
|
||||
{
|
||||
if (m_opacity_percent == opacity_percent)
|
||||
return;
|
||||
m_opacity_percent = opacity_percent;
|
||||
m_image.layer_did_modify_properties({}, *this);
|
||||
}
|
||||
|
||||
void Layer::set_name(const String& name)
|
||||
{
|
||||
if (m_name == name)
|
||||
return;
|
||||
m_name = name;
|
||||
m_image.layer_did_modify_properties({}, *this);
|
||||
}
|
||||
|
||||
}
|
95
Userland/Applications/PixelPaint/Layer.h
Normal file
95
Userland/Applications/PixelPaint/Layer.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class Image;
|
||||
|
||||
class Layer
|
||||
: public RefCounted<Layer>
|
||||
, public Weakable<Layer> {
|
||||
|
||||
AK_MAKE_NONCOPYABLE(Layer);
|
||||
AK_MAKE_NONMOVABLE(Layer);
|
||||
|
||||
public:
|
||||
static RefPtr<Layer> create_with_size(Image&, const Gfx::IntSize&, const String& name);
|
||||
static RefPtr<Layer> create_with_bitmap(Image&, const Gfx::Bitmap&, const String& name);
|
||||
static RefPtr<Layer> create_snapshot(Image&, const Layer&);
|
||||
|
||||
~Layer() { }
|
||||
|
||||
const Gfx::IntPoint& location() const { return m_location; }
|
||||
void set_location(const Gfx::IntPoint& location) { m_location = location; }
|
||||
|
||||
const Gfx::Bitmap& bitmap() const { return *m_bitmap; }
|
||||
Gfx::Bitmap& bitmap() { return *m_bitmap; }
|
||||
Gfx::IntSize size() const { return bitmap().size(); }
|
||||
|
||||
Gfx::IntRect relative_rect() const { return { location(), size() }; }
|
||||
Gfx::IntRect rect() const { return { {}, size() }; }
|
||||
|
||||
const String& name() const { return m_name; }
|
||||
void set_name(const String&);
|
||||
|
||||
void set_bitmap(Gfx::Bitmap& bitmap) { m_bitmap = bitmap; }
|
||||
|
||||
void did_modify_bitmap(Image&);
|
||||
|
||||
void set_selected(bool selected) { m_selected = selected; }
|
||||
bool is_selected() const { return m_selected; }
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool visible);
|
||||
|
||||
int opacity_percent() const { return m_opacity_percent; }
|
||||
void set_opacity_percent(int);
|
||||
|
||||
private:
|
||||
Layer(Image&, const Gfx::IntSize&, const String& name);
|
||||
Layer(Image&, const Gfx::Bitmap&, const String& name);
|
||||
|
||||
Image& m_image;
|
||||
|
||||
String m_name;
|
||||
Gfx::IntPoint m_location;
|
||||
RefPtr<Gfx::Bitmap> m_bitmap;
|
||||
|
||||
bool m_selected { false };
|
||||
bool m_visible { true };
|
||||
|
||||
int m_opacity_percent { 100 };
|
||||
};
|
||||
|
||||
}
|
285
Userland/Applications/PixelPaint/LayerListWidget.cpp
Normal file
285
Userland/Applications/PixelPaint/LayerListWidget.cpp
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "LayerListWidget.h"
|
||||
#include "Image.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
LayerListWidget::LayerListWidget()
|
||||
{
|
||||
}
|
||||
|
||||
LayerListWidget::~LayerListWidget()
|
||||
{
|
||||
if (m_image)
|
||||
m_image->remove_client(*this);
|
||||
}
|
||||
|
||||
void LayerListWidget::set_image(Image* image)
|
||||
{
|
||||
if (m_image == image)
|
||||
return;
|
||||
if (m_image)
|
||||
m_image->remove_client(*this);
|
||||
m_image = image;
|
||||
if (m_image)
|
||||
m_image->add_client(*this);
|
||||
|
||||
rebuild_gadgets();
|
||||
}
|
||||
|
||||
void LayerListWidget::rebuild_gadgets()
|
||||
{
|
||||
m_gadgets.clear();
|
||||
if (m_image) {
|
||||
for (size_t layer_index = 0; layer_index < m_image->layer_count(); ++layer_index) {
|
||||
m_gadgets.append({ layer_index, {}, {}, false, {} });
|
||||
}
|
||||
}
|
||||
relayout_gadgets();
|
||||
}
|
||||
|
||||
void LayerListWidget::resize_event(GUI::ResizeEvent& event)
|
||||
{
|
||||
Widget::resize_event(event);
|
||||
relayout_gadgets();
|
||||
}
|
||||
|
||||
void LayerListWidget::paint_event(GUI::PaintEvent& event)
|
||||
{
|
||||
GUI::Painter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
|
||||
painter.fill_rect(event.rect(), palette().button());
|
||||
|
||||
if (!m_image)
|
||||
return;
|
||||
|
||||
painter.fill_rect(event.rect(), palette().button());
|
||||
|
||||
auto paint_gadget = [&](auto& gadget) {
|
||||
auto& layer = m_image->layer(gadget.layer_index);
|
||||
|
||||
auto adjusted_rect = gadget.rect;
|
||||
|
||||
if (gadget.is_moving) {
|
||||
adjusted_rect.move_by(0, gadget.movement_delta.y());
|
||||
}
|
||||
|
||||
if (gadget.is_moving) {
|
||||
painter.fill_rect(adjusted_rect, palette().selection().lightened(1.5f));
|
||||
} else if (layer.is_selected()) {
|
||||
painter.fill_rect(adjusted_rect, palette().selection());
|
||||
}
|
||||
|
||||
painter.draw_rect(adjusted_rect, Color::Black);
|
||||
|
||||
Gfx::IntRect thumbnail_rect { adjusted_rect.x(), adjusted_rect.y(), adjusted_rect.height(), adjusted_rect.height() };
|
||||
thumbnail_rect.shrink(8, 8);
|
||||
painter.draw_scaled_bitmap(thumbnail_rect, layer.bitmap(), layer.bitmap().rect());
|
||||
|
||||
Gfx::IntRect text_rect { thumbnail_rect.right() + 10, adjusted_rect.y(), adjusted_rect.width(), adjusted_rect.height() };
|
||||
text_rect.intersect(adjusted_rect);
|
||||
|
||||
painter.draw_text(text_rect, layer.name(), Gfx::TextAlignment::CenterLeft, layer.is_selected() ? palette().selection_text() : palette().button_text());
|
||||
};
|
||||
|
||||
for (auto& gadget : m_gadgets) {
|
||||
if (!gadget.is_moving)
|
||||
paint_gadget(gadget);
|
||||
}
|
||||
|
||||
if (m_moving_gadget_index.has_value())
|
||||
paint_gadget(m_gadgets[m_moving_gadget_index.value()]);
|
||||
}
|
||||
|
||||
Optional<size_t> LayerListWidget::gadget_at(const Gfx::IntPoint& position)
|
||||
{
|
||||
for (size_t i = 0; i < m_gadgets.size(); ++i) {
|
||||
if (m_gadgets[i].rect.contains(position))
|
||||
return i;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void LayerListWidget::mousedown_event(GUI::MouseEvent& event)
|
||||
{
|
||||
if (!m_image)
|
||||
return;
|
||||
if (event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
auto gadget_index = gadget_at(event.position());
|
||||
if (!gadget_index.has_value()) {
|
||||
if (on_layer_select)
|
||||
on_layer_select(nullptr);
|
||||
return;
|
||||
}
|
||||
m_moving_gadget_index = gadget_index;
|
||||
m_moving_event_origin = event.position();
|
||||
auto& gadget = m_gadgets[m_moving_gadget_index.value()];
|
||||
auto& layer = m_image->layer(gadget_index.value());
|
||||
set_selected_layer(&layer);
|
||||
gadget.is_moving = true;
|
||||
gadget.movement_delta = {};
|
||||
update();
|
||||
}
|
||||
|
||||
void LayerListWidget::mousemove_event(GUI::MouseEvent& event)
|
||||
{
|
||||
if (!m_image)
|
||||
return;
|
||||
if (!m_moving_gadget_index.has_value())
|
||||
return;
|
||||
|
||||
auto delta = event.position() - m_moving_event_origin;
|
||||
auto& gadget = m_gadgets[m_moving_gadget_index.value()];
|
||||
ASSERT(gadget.is_moving);
|
||||
gadget.movement_delta = delta;
|
||||
relayout_gadgets();
|
||||
}
|
||||
|
||||
void LayerListWidget::mouseup_event(GUI::MouseEvent& event)
|
||||
{
|
||||
if (!m_image)
|
||||
return;
|
||||
if (event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
if (!m_moving_gadget_index.has_value())
|
||||
return;
|
||||
|
||||
size_t old_index = m_moving_gadget_index.value();
|
||||
size_t new_index = hole_index_during_move();
|
||||
if (new_index >= m_image->layer_count())
|
||||
new_index = m_image->layer_count() - 1;
|
||||
|
||||
m_moving_gadget_index = {};
|
||||
m_image->change_layer_index(old_index, new_index);
|
||||
}
|
||||
|
||||
void LayerListWidget::image_did_add_layer(size_t layer_index)
|
||||
{
|
||||
if (m_moving_gadget_index.has_value()) {
|
||||
m_gadgets[m_moving_gadget_index.value()].is_moving = false;
|
||||
m_moving_gadget_index = {};
|
||||
}
|
||||
Gadget gadget { layer_index, {}, {}, false, {} };
|
||||
m_gadgets.insert(layer_index, move(gadget));
|
||||
relayout_gadgets();
|
||||
}
|
||||
|
||||
void LayerListWidget::image_did_remove_layer(size_t layer_index)
|
||||
{
|
||||
if (m_moving_gadget_index.has_value()) {
|
||||
m_gadgets[m_moving_gadget_index.value()].is_moving = false;
|
||||
m_moving_gadget_index = {};
|
||||
}
|
||||
m_gadgets.remove(layer_index);
|
||||
relayout_gadgets();
|
||||
}
|
||||
|
||||
void LayerListWidget::image_did_modify_layer(size_t layer_index)
|
||||
{
|
||||
update(m_gadgets[layer_index].rect);
|
||||
}
|
||||
|
||||
void LayerListWidget::image_did_modify_layer_stack()
|
||||
{
|
||||
rebuild_gadgets();
|
||||
}
|
||||
|
||||
static constexpr int gadget_height = 30;
|
||||
static constexpr int gadget_spacing = 1;
|
||||
static constexpr int vertical_step = gadget_height + gadget_spacing;
|
||||
|
||||
size_t LayerListWidget::hole_index_during_move() const
|
||||
{
|
||||
ASSERT(is_moving_gadget());
|
||||
auto& moving_gadget = m_gadgets[m_moving_gadget_index.value()];
|
||||
int center_y_of_moving_gadget = moving_gadget.rect.translated(0, moving_gadget.movement_delta.y()).center().y();
|
||||
return center_y_of_moving_gadget / vertical_step;
|
||||
}
|
||||
|
||||
void LayerListWidget::select_bottom_layer()
|
||||
{
|
||||
if (!m_image || !m_image->layer_count())
|
||||
return;
|
||||
set_selected_layer(&m_image->layer(0));
|
||||
}
|
||||
|
||||
void LayerListWidget::select_top_layer()
|
||||
{
|
||||
if (!m_image || !m_image->layer_count())
|
||||
return;
|
||||
set_selected_layer(&m_image->layer(m_image->layer_count() - 1));
|
||||
}
|
||||
|
||||
void LayerListWidget::move_selection(int delta)
|
||||
{
|
||||
if (!m_image || !m_image->layer_count())
|
||||
return;
|
||||
int new_layer_index = min(max(0, (int)m_image->layer_count() + delta), (int)m_image->layer_count() - 1);
|
||||
set_selected_layer(&m_image->layer(new_layer_index));
|
||||
}
|
||||
|
||||
void LayerListWidget::relayout_gadgets()
|
||||
{
|
||||
int y = 0;
|
||||
|
||||
Optional<size_t> hole_index;
|
||||
if (is_moving_gadget())
|
||||
hole_index = hole_index_during_move();
|
||||
|
||||
size_t index = 0;
|
||||
for (auto& gadget : m_gadgets) {
|
||||
if (gadget.is_moving)
|
||||
continue;
|
||||
if (hole_index.has_value() && index == hole_index.value())
|
||||
y += vertical_step;
|
||||
gadget.rect = { 0, y, width(), gadget_height };
|
||||
y += vertical_step;
|
||||
++index;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void LayerListWidget::set_selected_layer(Layer* layer)
|
||||
{
|
||||
if (!m_image)
|
||||
return;
|
||||
for (size_t i = 0; i < m_image->layer_count(); ++i)
|
||||
m_image->layer(i).set_selected(layer == &m_image->layer(i));
|
||||
if (on_layer_select)
|
||||
on_layer_select(layer);
|
||||
update();
|
||||
}
|
||||
|
||||
}
|
89
Userland/Applications/PixelPaint/LayerListWidget.h
Normal file
89
Userland/Applications/PixelPaint/LayerListWidget.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Image.h"
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class LayerListWidget final
|
||||
: public GUI::Widget
|
||||
, ImageClient {
|
||||
C_OBJECT(LayerListWidget);
|
||||
|
||||
public:
|
||||
virtual ~LayerListWidget() override;
|
||||
|
||||
void set_image(Image*);
|
||||
|
||||
void set_selected_layer(Layer*);
|
||||
Function<void(Layer*)> on_layer_select;
|
||||
|
||||
void select_bottom_layer();
|
||||
void select_top_layer();
|
||||
void move_selection(int delta);
|
||||
|
||||
private:
|
||||
explicit LayerListWidget();
|
||||
|
||||
virtual void paint_event(GUI::PaintEvent&) override;
|
||||
virtual void mousedown_event(GUI::MouseEvent&) override;
|
||||
virtual void mousemove_event(GUI::MouseEvent&) override;
|
||||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||
virtual void resize_event(GUI::ResizeEvent&) override;
|
||||
|
||||
virtual void image_did_add_layer(size_t) override;
|
||||
virtual void image_did_remove_layer(size_t) override;
|
||||
virtual void image_did_modify_layer(size_t) override;
|
||||
virtual void image_did_modify_layer_stack() override;
|
||||
|
||||
void rebuild_gadgets();
|
||||
void relayout_gadgets();
|
||||
|
||||
size_t hole_index_during_move() const;
|
||||
|
||||
struct Gadget {
|
||||
size_t layer_index { 0 };
|
||||
Gfx::IntRect rect;
|
||||
Gfx::IntRect temporary_rect_during_move;
|
||||
bool is_moving { false };
|
||||
Gfx::IntPoint movement_delta;
|
||||
};
|
||||
|
||||
bool is_moving_gadget() const { return m_moving_gadget_index.has_value(); }
|
||||
|
||||
Optional<size_t> gadget_at(const Gfx::IntPoint&);
|
||||
|
||||
Vector<Gadget> m_gadgets;
|
||||
RefPtr<Image> m_image;
|
||||
|
||||
Optional<size_t> m_moving_gadget_index;
|
||||
Gfx::IntPoint m_moving_event_origin;
|
||||
};
|
||||
|
||||
}
|
107
Userland/Applications/PixelPaint/LayerPropertiesWidget.cpp
Normal file
107
Userland/Applications/PixelPaint/LayerPropertiesWidget.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "LayerPropertiesWidget.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/CheckBox.h>
|
||||
#include <LibGUI/GroupBox.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/OpacitySlider.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
#include <LibGfx/Font.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
LayerPropertiesWidget::LayerPropertiesWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& group_box = add<GUI::GroupBox>("Layer properties");
|
||||
auto& layout = group_box.set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
layout.set_margins({ 10, 20, 10, 10 });
|
||||
|
||||
auto& name_container = group_box.add<GUI::Widget>();
|
||||
name_container.set_fixed_height(20);
|
||||
name_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& name_label = name_container.add<GUI::Label>("Name:");
|
||||
name_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
name_label.set_fixed_size(80, 20);
|
||||
|
||||
m_name_textbox = name_container.add<GUI::TextBox>();
|
||||
m_name_textbox->set_fixed_height(20);
|
||||
m_name_textbox->on_change = [this] {
|
||||
if (m_layer)
|
||||
m_layer->set_name(m_name_textbox->text());
|
||||
};
|
||||
|
||||
auto& opacity_container = group_box.add<GUI::Widget>();
|
||||
opacity_container.set_fixed_height(20);
|
||||
opacity_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& opacity_label = opacity_container.add<GUI::Label>("Opacity:");
|
||||
opacity_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
opacity_label.set_fixed_size(80, 20);
|
||||
|
||||
m_opacity_slider = opacity_container.add<GUI::OpacitySlider>();
|
||||
m_opacity_slider->set_range(0, 100);
|
||||
m_opacity_slider->on_change = [this](int value) {
|
||||
if (m_layer)
|
||||
m_layer->set_opacity_percent(value);
|
||||
};
|
||||
|
||||
m_visibility_checkbox = group_box.add<GUI::CheckBox>("Visible");
|
||||
m_visibility_checkbox->set_fixed_height(20);
|
||||
m_visibility_checkbox->on_checked = [this](bool checked) {
|
||||
if (m_layer)
|
||||
m_layer->set_visible(checked);
|
||||
};
|
||||
}
|
||||
|
||||
LayerPropertiesWidget::~LayerPropertiesWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void LayerPropertiesWidget::set_layer(Layer* layer)
|
||||
{
|
||||
if (m_layer == layer)
|
||||
return;
|
||||
|
||||
if (layer) {
|
||||
m_layer = layer->make_weak_ptr();
|
||||
m_name_textbox->set_text(layer->name());
|
||||
m_opacity_slider->set_value(layer->opacity_percent());
|
||||
m_visibility_checkbox->set_checked(layer->is_visible());
|
||||
set_enabled(true);
|
||||
} else {
|
||||
m_layer = nullptr;
|
||||
set_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
53
Userland/Applications/PixelPaint/LayerPropertiesWidget.h
Normal file
53
Userland/Applications/PixelPaint/LayerPropertiesWidget.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class Layer;
|
||||
|
||||
class LayerPropertiesWidget final : public GUI::Widget {
|
||||
C_OBJECT(LayerPropertiesWidget);
|
||||
|
||||
public:
|
||||
virtual ~LayerPropertiesWidget() override;
|
||||
|
||||
void set_layer(Layer*);
|
||||
|
||||
private:
|
||||
LayerPropertiesWidget();
|
||||
|
||||
RefPtr<GUI::CheckBox> m_visibility_checkbox;
|
||||
RefPtr<GUI::OpacitySlider> m_opacity_slider;
|
||||
RefPtr<GUI::TextBox> m_name_textbox;
|
||||
|
||||
WeakPtr<Layer> m_layer;
|
||||
};
|
||||
|
||||
}
|
156
Userland/Applications/PixelPaint/LineTool.cpp
Normal file
156
Userland/Applications/PixelPaint/LineTool.cpp
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "LineTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
static Gfx::IntPoint constrain_line_angle(const Gfx::IntPoint& start_pos, const Gfx::IntPoint& end_pos, float angle_increment)
|
||||
{
|
||||
float current_angle = atan2(end_pos.y() - start_pos.y(), end_pos.x() - start_pos.x()) + 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 = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
|
||||
|
||||
return { start_pos.x() + (int)(cos(constrained_angle) * line_length),
|
||||
start_pos.y() + (int)(sin(constrained_angle) * line_length) };
|
||||
}
|
||||
|
||||
LineTool::LineTool()
|
||||
{
|
||||
}
|
||||
|
||||
LineTool::~LineTool()
|
||||
{
|
||||
}
|
||||
|
||||
void LineTool::on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent&)
|
||||
{
|
||||
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_line_start_position = layer_event.position();
|
||||
m_line_end_position = layer_event.position();
|
||||
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void LineTool::on_mouseup(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (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->image());
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void LineTool::on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent&)
|
||||
{
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
if (!m_constrain_angle) {
|
||||
m_line_end_position = layer_event.position();
|
||||
} else {
|
||||
const float ANGLE_STEP = M_PI / 8.0f;
|
||||
m_line_end_position = constrain_line_angle(m_line_start_position, layer_event.position(), ANGLE_STEP);
|
||||
}
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void LineTool::on_second_paint(const Layer& layer, GUI::PaintEvent& event)
|
||||
{
|
||||
if (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_line_start_position).to_type<int>();
|
||||
auto preview_end = m_editor->layer_position_to_editor_position(layer, m_line_end_position).to_type<int>();
|
||||
painter.draw_line(preview_start, preview_end, m_editor->color_for(m_drawing_button), m_thickness);
|
||||
}
|
||||
|
||||
void LineTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
|
||||
if (event.key() == Key_Shift) {
|
||||
m_constrain_angle = true;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
void LineTool::on_keyup(GUI::KeyEvent& event)
|
||||
{
|
||||
if (event.key() == Key_Shift) {
|
||||
m_constrain_angle = false;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
void LineTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_thickness_actions.set_exclusive(true);
|
||||
auto insert_action = [&](int size, bool checked = false) {
|
||||
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
|
||||
m_thickness = size;
|
||||
});
|
||||
action->set_checked(checked);
|
||||
m_thickness_actions.add_action(*action);
|
||||
m_context_menu->add_action(move(action));
|
||||
};
|
||||
insert_action(1, true);
|
||||
insert_action(2);
|
||||
insert_action(3);
|
||||
insert_action(4);
|
||||
}
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
}
|
59
Userland/Applications/PixelPaint/LineTool.h
Normal file
59
Userland/Applications/PixelPaint/LineTool.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class LineTool final : public Tool {
|
||||
public:
|
||||
LineTool();
|
||||
virtual ~LineTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
|
||||
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual void on_keyup(GUI::KeyEvent&) override;
|
||||
|
||||
private:
|
||||
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
|
||||
Gfx::IntPoint m_line_start_position;
|
||||
Gfx::IntPoint m_line_end_position;
|
||||
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
GUI::ActionGroup m_thickness_actions;
|
||||
int m_thickness { 1 };
|
||||
bool m_constrain_angle { false };
|
||||
};
|
||||
|
||||
}
|
139
Userland/Applications/PixelPaint/MoveTool.cpp
Normal file
139
Userland/Applications/PixelPaint/MoveTool.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "MoveTool.h"
|
||||
#include "Image.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
MoveTool::MoveTool()
|
||||
{
|
||||
}
|
||||
|
||||
MoveTool::~MoveTool()
|
||||
{
|
||||
}
|
||||
|
||||
void MoveTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent& image_event)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
if (!layer.rect().contains(event.position()))
|
||||
return;
|
||||
m_layer_being_moved = layer;
|
||||
m_event_origin = image_event.position();
|
||||
m_layer_origin = layer.location();
|
||||
m_editor->window()->set_cursor(Gfx::StandardCursor::Move);
|
||||
}
|
||||
|
||||
void MoveTool::on_mousemove(Layer&, GUI::MouseEvent&, GUI::MouseEvent& 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&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
m_layer_being_moved = nullptr;
|
||||
m_editor->window()->set_cursor(Gfx::StandardCursor::None);
|
||||
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.move_by(0, -1);
|
||||
break;
|
||||
case Key_Down:
|
||||
new_location.move_by(0, 1);
|
||||
break;
|
||||
case Key_Left:
|
||||
new_location.move_by(-1, 0);
|
||||
break;
|
||||
case Key_Right:
|
||||
new_location.move_by(1, 0);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
layer->set_location(new_location);
|
||||
m_editor->layers_did_change();
|
||||
}
|
||||
|
||||
void MoveTool::on_context_menu(Layer& layer, GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_context_menu->add_action(GUI::CommonActions::make_move_to_front_action(
|
||||
[this](auto&) {
|
||||
m_editor->image()->move_layer_to_front(*m_context_menu_layer);
|
||||
m_editor->layers_did_change();
|
||||
},
|
||||
m_editor));
|
||||
m_context_menu->add_action(GUI::CommonActions::make_move_to_back_action(
|
||||
[this](auto&) {
|
||||
m_editor->image()->move_layer_to_back(*m_context_menu_layer);
|
||||
m_editor->layers_did_change();
|
||||
},
|
||||
m_editor));
|
||||
m_context_menu->add_separator();
|
||||
m_context_menu->add_action(GUI::Action::create(
|
||||
"Delete layer", Gfx::Bitmap::load_from_file("/res/icons/16x16/delete.png"), [this](auto&) {
|
||||
m_editor->image()->remove_layer(*m_context_menu_layer);
|
||||
// FIXME: This should not be done imperatively here. Perhaps a Image::Client interface that ImageEditor can implement?
|
||||
if (m_editor->active_layer() == m_context_menu_layer)
|
||||
m_editor->set_active_layer(nullptr);
|
||||
m_editor->layers_did_change();
|
||||
},
|
||||
m_editor));
|
||||
}
|
||||
m_context_menu_layer = layer;
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
}
|
52
Userland/Applications/PixelPaint/MoveTool.h
Normal file
52
Userland/Applications/PixelPaint/MoveTool.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class MoveTool final : public Tool {
|
||||
public:
|
||||
MoveTool();
|
||||
virtual ~MoveTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual void on_context_menu(Layer&, GUI::ContextMenuEvent&) override;
|
||||
|
||||
private:
|
||||
RefPtr<Layer> m_layer_being_moved;
|
||||
Gfx::IntPoint m_event_origin;
|
||||
Gfx::IntPoint m_layer_origin;
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
RefPtr<Layer> m_context_menu_layer;
|
||||
};
|
||||
|
||||
}
|
178
Userland/Applications/PixelPaint/PaletteWidget.cpp
Normal file
178
Userland/Applications/PixelPaint/PaletteWidget.cpp
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "PaletteWidget.h"
|
||||
#include "ImageEditor.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/ColorPicker.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ColorWidget : public GUI::Frame {
|
||||
C_OBJECT(ColorWidget);
|
||||
|
||||
public:
|
||||
explicit ColorWidget(Color color, PaletteWidget& palette_widget)
|
||||
: m_palette_widget(palette_widget)
|
||||
, m_color(color)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~ColorWidget() override
|
||||
{
|
||||
}
|
||||
|
||||
virtual void mousedown_event(GUI::MouseEvent& event) override
|
||||
{
|
||||
if (event.modifiers() & KeyModifier::Mod_Ctrl && event.button() == GUI::MouseButton::Left) {
|
||||
auto dialog = GUI::ColorPicker::construct(m_color, window());
|
||||
if (dialog->exec() == GUI::Dialog::ExecOK) {
|
||||
m_color = dialog->color();
|
||||
auto pal = palette();
|
||||
pal.set_color(ColorRole::Background, m_color);
|
||||
set_palette(pal);
|
||||
update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.button() == GUI::MouseButton::Left)
|
||||
m_palette_widget.set_primary_color(m_color);
|
||||
else if (event.button() == GUI::MouseButton::Right)
|
||||
m_palette_widget.set_secondary_color(m_color);
|
||||
}
|
||||
|
||||
private:
|
||||
PaletteWidget& m_palette_widget;
|
||||
Color m_color;
|
||||
};
|
||||
|
||||
PaletteWidget::PaletteWidget(ImageEditor& editor)
|
||||
: m_editor(editor)
|
||||
{
|
||||
set_frame_shape(Gfx::FrameShape::Panel);
|
||||
set_frame_shadow(Gfx::FrameShadow::Raised);
|
||||
set_frame_thickness(0);
|
||||
set_fill_with_background_color(true);
|
||||
|
||||
set_fixed_height(34);
|
||||
|
||||
m_secondary_color_widget = add<GUI::Frame>();
|
||||
m_secondary_color_widget->set_relative_rect({ 2, 2, 60, 31 });
|
||||
m_secondary_color_widget->set_fill_with_background_color(true);
|
||||
set_secondary_color(m_editor.secondary_color());
|
||||
|
||||
m_primary_color_widget = add<GUI::Frame>();
|
||||
Gfx::IntRect rect { 0, 0, 38, 15 };
|
||||
rect.center_within(m_secondary_color_widget->relative_rect());
|
||||
m_primary_color_widget->set_relative_rect(rect);
|
||||
m_primary_color_widget->set_fill_with_background_color(true);
|
||||
set_primary_color(m_editor.primary_color());
|
||||
|
||||
m_editor.on_primary_color_change = [this](Color color) {
|
||||
set_primary_color(color);
|
||||
};
|
||||
|
||||
m_editor.on_secondary_color_change = [this](Color color) {
|
||||
set_secondary_color(color);
|
||||
};
|
||||
|
||||
auto& color_container = add<GUI::Widget>();
|
||||
color_container.set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 32);
|
||||
color_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
color_container.layout()->set_spacing(1);
|
||||
|
||||
auto& top_color_container = color_container.add<GUI::Widget>();
|
||||
top_color_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
top_color_container.layout()->set_spacing(1);
|
||||
|
||||
auto& bottom_color_container = color_container.add<GUI::Widget>();
|
||||
bottom_color_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
bottom_color_container.layout()->set_spacing(1);
|
||||
|
||||
auto add_color_widget = [&](GUI::Widget& container, Color color) {
|
||||
auto& color_widget = container.add<ColorWidget>(color, *this);
|
||||
color_widget.set_fill_with_background_color(true);
|
||||
auto pal = color_widget.palette();
|
||||
pal.set_color(ColorRole::Background, color);
|
||||
color_widget.set_palette(pal);
|
||||
};
|
||||
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x000000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x808080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x800000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x808000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x008000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x008080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x000080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x800080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x808040));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x004040));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x0080ff));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x004080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x8000ff));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x804000));
|
||||
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffffff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xc0c0c0));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff0000));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffff00));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x00ff00));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x00ffff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x0000ff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff00ff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffff80));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x00ff80));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x80ffff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x8080ff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff0080));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff8040));
|
||||
}
|
||||
|
||||
PaletteWidget::~PaletteWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void PaletteWidget::set_primary_color(Color color)
|
||||
{
|
||||
m_editor.set_primary_color(color);
|
||||
auto pal = m_primary_color_widget->palette();
|
||||
pal.set_color(ColorRole::Background, color);
|
||||
m_primary_color_widget->set_palette(pal);
|
||||
m_primary_color_widget->update();
|
||||
}
|
||||
|
||||
void PaletteWidget::set_secondary_color(Color color)
|
||||
{
|
||||
m_editor.set_secondary_color(color);
|
||||
auto pal = m_secondary_color_widget->palette();
|
||||
pal.set_color(ColorRole::Background, color);
|
||||
m_secondary_color_widget->set_palette(pal);
|
||||
m_secondary_color_widget->update();
|
||||
}
|
||||
|
||||
}
|
52
Userland/Applications/PixelPaint/PaletteWidget.h
Normal file
52
Userland/Applications/PixelPaint/PaletteWidget.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Frame.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ImageEditor;
|
||||
|
||||
class PaletteWidget final : public GUI::Frame {
|
||||
C_OBJECT(PaletteWidget);
|
||||
|
||||
public:
|
||||
virtual ~PaletteWidget() override;
|
||||
|
||||
void set_primary_color(Color);
|
||||
void set_secondary_color(Color);
|
||||
|
||||
private:
|
||||
explicit PaletteWidget(ImageEditor&);
|
||||
|
||||
ImageEditor& m_editor;
|
||||
RefPtr<GUI::Frame> m_primary_color_widget;
|
||||
RefPtr<GUI::Frame> m_secondary_color_widget;
|
||||
};
|
||||
|
||||
}
|
128
Userland/Applications/PixelPaint/PenTool.cpp
Normal file
128
Userland/Applications/PixelPaint/PenTool.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "PenTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/Slider.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
PenTool::PenTool()
|
||||
{
|
||||
}
|
||||
|
||||
PenTool::~PenTool()
|
||||
{
|
||||
}
|
||||
|
||||
void PenTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
painter.draw_line(event.position(), event.position(), m_editor->color_for(event), m_thickness);
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
m_last_drawing_event_position = event.position();
|
||||
}
|
||||
|
||||
void PenTool::on_mouseup(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() == GUI::MouseButton::Left || event.button() == GUI::MouseButton::Right) {
|
||||
m_last_drawing_event_position = { -1, -1 };
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void PenTool::on_mousemove(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (!(event.buttons() & GUI::MouseButton::Left || event.buttons() & GUI::MouseButton::Right))
|
||||
return;
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
|
||||
if (m_last_drawing_event_position != Gfx::IntPoint(-1, -1))
|
||||
painter.draw_line(m_last_drawing_event_position, event.position(), m_editor->color_for(event), m_thickness);
|
||||
else
|
||||
painter.draw_line(event.position(), event.position(), m_editor->color_for(event), m_thickness);
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
|
||||
m_last_drawing_event_position = event.position();
|
||||
}
|
||||
|
||||
void PenTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_thickness_actions.set_exclusive(true);
|
||||
auto insert_action = [&](int size, bool checked = false) {
|
||||
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
|
||||
m_thickness = size;
|
||||
});
|
||||
action->set_checked(checked);
|
||||
m_thickness_actions.add_action(*action);
|
||||
m_context_menu->add_action(move(action));
|
||||
};
|
||||
insert_action(1, true);
|
||||
insert_action(2);
|
||||
insert_action(3);
|
||||
insert_action(4);
|
||||
}
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
GUI::Widget* PenTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& thickness_container = m_properties_widget->add<GUI::Widget>();
|
||||
thickness_container.set_fixed_height(20);
|
||||
thickness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& thickness_label = thickness_container.add<GUI::Label>("Thickness:");
|
||||
thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
thickness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& thickness_slider = thickness_container.add<GUI::HorizontalSlider>();
|
||||
thickness_slider.set_fixed_height(20);
|
||||
thickness_slider.set_range(1, 20);
|
||||
thickness_slider.set_value(m_thickness);
|
||||
thickness_slider.on_change = [this](int value) {
|
||||
m_thickness = value;
|
||||
};
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
54
Userland/Applications/PixelPaint/PenTool.h
Normal file
54
Userland/Applications/PixelPaint/PenTool.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class PenTool final : public Tool {
|
||||
public:
|
||||
PenTool();
|
||||
virtual ~PenTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
private:
|
||||
Gfx::IntPoint m_last_drawing_event_position { -1, -1 };
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
int m_thickness { 1 };
|
||||
GUI::ActionGroup m_thickness_actions;
|
||||
};
|
||||
|
||||
}
|
53
Userland/Applications/PixelPaint/PickerTool.cpp
Normal file
53
Userland/Applications/PixelPaint/PickerTool.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "PickerTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
PickerTool::PickerTool()
|
||||
{
|
||||
}
|
||||
|
||||
PickerTool::~PickerTool()
|
||||
{
|
||||
}
|
||||
|
||||
void PickerTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (!layer.rect().contains(event.position()))
|
||||
return;
|
||||
auto color = layer.bitmap().get_pixel(event.position());
|
||||
if (event.button() == GUI::MouseButton::Left)
|
||||
m_editor->set_primary_color(color);
|
||||
else if (event.button() == GUI::MouseButton::Right)
|
||||
m_editor->set_secondary_color(color);
|
||||
}
|
||||
|
||||
}
|
41
Userland/Applications/PixelPaint/PickerTool.h
Normal file
41
Userland/Applications/PixelPaint/PickerTool.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class PickerTool final : public Tool {
|
||||
public:
|
||||
PickerTool();
|
||||
virtual ~PickerTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
};
|
||||
|
||||
}
|
137
Userland/Applications/PixelPaint/RectangleTool.cpp
Normal file
137
Userland/Applications/PixelPaint/RectangleTool.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "RectangleTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
RectangleTool::RectangleTool()
|
||||
{
|
||||
}
|
||||
|
||||
RectangleTool::~RectangleTool()
|
||||
{
|
||||
}
|
||||
|
||||
void RectangleTool::draw_using(GUI::Painter& painter, const Gfx::IntRect& rect)
|
||||
{
|
||||
switch (m_mode) {
|
||||
case Mode::Fill:
|
||||
painter.fill_rect(rect, m_editor->color_for(m_drawing_button));
|
||||
break;
|
||||
case Mode::Outline:
|
||||
painter.draw_rect(rect, m_editor->color_for(m_drawing_button));
|
||||
break;
|
||||
case Mode::Gradient:
|
||||
painter.fill_rect_with_gradient(rect, m_editor->primary_color(), m_editor->secondary_color());
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void RectangleTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
if (m_drawing_button != GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_drawing_button = event.button();
|
||||
m_rectangle_start_position = event.position();
|
||||
m_rectangle_end_position = event.position();
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void RectangleTool::on_mouseup(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (event.button() == m_drawing_button) {
|
||||
GUI::Painter painter(layer.bitmap());
|
||||
auto rect = Gfx::IntRect::from_two_points(m_rectangle_start_position, m_rectangle_end_position);
|
||||
draw_using(painter, rect);
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
layer.did_modify_bitmap(*m_editor->image());
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void RectangleTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_rectangle_end_position = event.position();
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void RectangleTool::on_second_paint(const Layer& layer, GUI::PaintEvent& event)
|
||||
{
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
GUI::Painter painter(*m_editor);
|
||||
painter.add_clip_rect(event.rect());
|
||||
auto rect = Gfx::IntRect::from_two_points(
|
||||
m_editor->layer_position_to_editor_position(layer, m_rectangle_start_position).to_type<int>(),
|
||||
m_editor->layer_position_to_editor_position(layer, m_rectangle_end_position).to_type<int>());
|
||||
draw_using(painter, rect);
|
||||
}
|
||||
|
||||
void RectangleTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
void RectangleTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_context_menu->add_action(GUI::Action::create("Fill", [this](auto&) {
|
||||
m_mode = Mode::Fill;
|
||||
}));
|
||||
m_context_menu->add_action(GUI::Action::create("Outline", [this](auto&) {
|
||||
m_mode = Mode::Outline;
|
||||
}));
|
||||
m_context_menu->add_action(GUI::Action::create("Gradient", [this](auto&) {
|
||||
m_mode = Mode::Gradient;
|
||||
}));
|
||||
}
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
}
|
63
Userland/Applications/PixelPaint/RectangleTool.h
Normal file
63
Userland/Applications/PixelPaint/RectangleTool.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class RectangleTool final : public Tool {
|
||||
public:
|
||||
RectangleTool();
|
||||
virtual ~RectangleTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
|
||||
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
|
||||
private:
|
||||
enum class Mode {
|
||||
Outline,
|
||||
Fill,
|
||||
Gradient,
|
||||
};
|
||||
|
||||
void draw_using(GUI::Painter&, const Gfx::IntRect&);
|
||||
|
||||
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
|
||||
Gfx::IntPoint m_rectangle_start_position;
|
||||
Gfx::IntPoint m_rectangle_end_position;
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
Mode m_mode { Mode::Outline };
|
||||
};
|
||||
|
||||
}
|
176
Userland/Applications/PixelPaint/SprayTool.cpp
Normal file
176
Userland/Applications/PixelPaint/SprayTool.cpp
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "SprayTool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include <AK/Queue.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/Slider.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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);
|
||||
ASSERT(bitmap.bpp() == 32);
|
||||
m_editor->update();
|
||||
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.0f); i++) {
|
||||
double radius = base_radius * nrand();
|
||||
double angle = 2 * M_PI * nrand();
|
||||
const int xpos = m_last_pos.x() + radius * cos(angle);
|
||||
const int ypos = m_last_pos.y() - radius * sin(angle);
|
||||
if (xpos < 0 || xpos >= bitmap.width())
|
||||
continue;
|
||||
if (ypos < 0 || ypos >= bitmap.height())
|
||||
continue;
|
||||
bitmap.set_pixel<Gfx::StorageFormat::RGBA32>(xpos, ypos, m_color);
|
||||
}
|
||||
|
||||
layer->did_modify_bitmap(*m_editor->image());
|
||||
}
|
||||
|
||||
void SprayTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
m_color = m_editor->color_for(event);
|
||||
m_last_pos = event.position();
|
||||
m_timer->start();
|
||||
paint_it();
|
||||
}
|
||||
|
||||
void SprayTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
|
||||
{
|
||||
m_last_pos = event.position();
|
||||
if (m_timer->is_active()) {
|
||||
paint_it();
|
||||
m_timer->restart(m_timer->interval());
|
||||
}
|
||||
}
|
||||
|
||||
void SprayTool::on_mouseup(Layer&, GUI::MouseEvent&, GUI::MouseEvent&)
|
||||
{
|
||||
if (m_timer->is_active()) {
|
||||
m_timer->stop();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void SprayTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_thickness_actions.set_exclusive(true);
|
||||
auto insert_action = [&](int size, bool checked = false) {
|
||||
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
|
||||
m_thickness = size;
|
||||
});
|
||||
action->set_checked(checked);
|
||||
m_thickness_actions.add_action(*action);
|
||||
m_context_menu->add_action(move(action));
|
||||
};
|
||||
insert_action(1, true);
|
||||
insert_action(2);
|
||||
insert_action(3);
|
||||
insert_action(4);
|
||||
}
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
GUI::Widget* SprayTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& thickness_container = m_properties_widget->add<GUI::Widget>();
|
||||
thickness_container.set_fixed_height(20);
|
||||
thickness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& thickness_label = thickness_container.add<GUI::Label>("Thickness:");
|
||||
thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
thickness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& thickness_slider = thickness_container.add<GUI::HorizontalSlider>();
|
||||
thickness_slider.set_fixed_height(20);
|
||||
thickness_slider.set_range(1, 20);
|
||||
thickness_slider.set_value(m_thickness);
|
||||
thickness_slider.on_change = [this](int value) {
|
||||
m_thickness = value;
|
||||
};
|
||||
|
||||
auto& density_container = m_properties_widget->add<GUI::Widget>();
|
||||
density_container.set_fixed_height(20);
|
||||
density_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& density_label = density_container.add<GUI::Label>("Density:");
|
||||
density_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
density_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& density_slider = density_container.add<GUI::HorizontalSlider>();
|
||||
density_slider.set_fixed_height(30);
|
||||
density_slider.set_range(1, 100);
|
||||
density_slider.set_value(m_density);
|
||||
density_slider.on_change = [this](int value) {
|
||||
m_density = value;
|
||||
};
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
60
Userland/Applications/PixelPaint/SprayTool.h
Normal file
60
Userland/Applications/PixelPaint/SprayTool.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class SprayTool final : public Tool {
|
||||
public:
|
||||
SprayTool();
|
||||
virtual ~SprayTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override;
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
private:
|
||||
void paint_it();
|
||||
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
RefPtr<Core::Timer> m_timer;
|
||||
Gfx::IntPoint m_last_pos;
|
||||
Color m_color;
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
GUI::ActionGroup m_thickness_actions;
|
||||
int m_thickness { 10 };
|
||||
int m_density { 40 };
|
||||
};
|
||||
|
||||
}
|
51
Userland/Applications/PixelPaint/Tool.cpp
Normal file
51
Userland/Applications/PixelPaint/Tool.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Tool.h"
|
||||
#include "ImageEditor.h"
|
||||
#include <LibGUI/Action.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
Tool::Tool()
|
||||
{
|
||||
}
|
||||
|
||||
Tool::~Tool()
|
||||
{
|
||||
}
|
||||
|
||||
void Tool::setup(ImageEditor& editor)
|
||||
{
|
||||
m_editor = editor;
|
||||
}
|
||||
|
||||
void Tool::set_action(GUI::Action* action)
|
||||
{
|
||||
m_action = action;
|
||||
}
|
||||
|
||||
}
|
63
Userland/Applications/PixelPaint/Tool.h
Normal file
63
Userland/Applications/PixelPaint/Tool.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ImageEditor;
|
||||
class Layer;
|
||||
|
||||
class Tool {
|
||||
public:
|
||||
virtual ~Tool();
|
||||
|
||||
virtual void on_mousedown(Layer&, GUI::MouseEvent&, GUI::MouseEvent&) { }
|
||||
virtual void on_mousemove(Layer&, GUI::MouseEvent&, GUI::MouseEvent&) { }
|
||||
virtual void on_mouseup(Layer&, GUI::MouseEvent&, GUI::MouseEvent&) { }
|
||||
virtual void on_context_menu(Layer&, GUI::ContextMenuEvent&) { }
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) { }
|
||||
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) { }
|
||||
virtual void on_keydown(GUI::KeyEvent&) { }
|
||||
virtual void on_keyup(GUI::KeyEvent&) { }
|
||||
virtual GUI::Widget* get_properties_widget() { return nullptr; }
|
||||
|
||||
void clear() { m_editor = nullptr; }
|
||||
void setup(ImageEditor&);
|
||||
|
||||
GUI::Action* action() { return m_action; }
|
||||
void set_action(GUI::Action*);
|
||||
|
||||
protected:
|
||||
Tool();
|
||||
WeakPtr<ImageEditor> m_editor;
|
||||
RefPtr<GUI::Action> m_action;
|
||||
};
|
||||
|
||||
}
|
61
Userland/Applications/PixelPaint/ToolPropertiesWidget.cpp
Normal file
61
Userland/Applications/PixelPaint/ToolPropertiesWidget.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ToolPropertiesWidget.h"
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/GroupBox.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
ToolPropertiesWidget::ToolPropertiesWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
m_group_box = add<GUI::GroupBox>("Tool properties");
|
||||
auto& layout = m_group_box->set_layout<GUI::VerticalBoxLayout>();
|
||||
layout.set_margins({ 10, 20, 10, 10 });
|
||||
}
|
||||
|
||||
void ToolPropertiesWidget::set_active_tool(Tool* tool)
|
||||
{
|
||||
if (tool == m_active_tool)
|
||||
return;
|
||||
|
||||
if (m_active_tool_widget != nullptr)
|
||||
m_group_box->remove_child(*m_active_tool_widget);
|
||||
|
||||
m_active_tool = tool;
|
||||
m_active_tool_widget = tool->get_properties_widget();
|
||||
if (m_active_tool_widget != nullptr)
|
||||
m_group_box->add_child(*m_active_tool_widget);
|
||||
}
|
||||
|
||||
ToolPropertiesWidget::~ToolPropertiesWidget()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
54
Userland/Applications/PixelPaint/ToolPropertiesWidget.h
Normal file
54
Userland/Applications/PixelPaint/ToolPropertiesWidget.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class Tool;
|
||||
|
||||
class ToolPropertiesWidget final : public GUI::Widget {
|
||||
C_OBJECT(ToolPropertiesWidget);
|
||||
|
||||
public:
|
||||
virtual ~ToolPropertiesWidget() override;
|
||||
|
||||
void set_active_tool(Tool*);
|
||||
|
||||
private:
|
||||
ToolPropertiesWidget();
|
||||
|
||||
RefPtr<GUI::GroupBox> m_group_box;
|
||||
|
||||
Tool* m_active_tool { nullptr };
|
||||
GUI::Widget* m_active_tool_widget { nullptr };
|
||||
};
|
||||
|
||||
}
|
140
Userland/Applications/PixelPaint/ToolboxWidget.cpp
Normal file
140
Userland/Applications/PixelPaint/ToolboxWidget.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ToolboxWidget.h"
|
||||
#include "BrushTool.h"
|
||||
#include "BucketTool.h"
|
||||
#include "EllipseTool.h"
|
||||
#include "EraseTool.h"
|
||||
#include "LineTool.h"
|
||||
#include "MoveTool.h"
|
||||
#include "PenTool.h"
|
||||
#include "PickerTool.h"
|
||||
#include "RectangleTool.h"
|
||||
#include "SprayTool.h"
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Button.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ToolButton final : public GUI::Button {
|
||||
C_OBJECT(ToolButton)
|
||||
public:
|
||||
ToolButton(ToolboxWidget& toolbox, const String& name, const GUI::Shortcut& shortcut, OwnPtr<Tool> tool)
|
||||
: m_toolbox(toolbox)
|
||||
, m_tool(move(tool))
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(name);
|
||||
builder.append(" (");
|
||||
builder.append(shortcut.to_string());
|
||||
builder.append(")");
|
||||
set_tooltip(builder.to_string());
|
||||
|
||||
m_action = GUI::Action::create_checkable(
|
||||
name, shortcut, [this](auto& action) {
|
||||
if (action.is_checked())
|
||||
m_toolbox.on_tool_selection(m_tool);
|
||||
else
|
||||
m_toolbox.on_tool_selection(nullptr);
|
||||
},
|
||||
toolbox.window());
|
||||
|
||||
m_tool->set_action(m_action);
|
||||
set_action(*m_action);
|
||||
m_toolbox.m_action_group.add_action(*m_action);
|
||||
}
|
||||
|
||||
const Tool& tool() const { return *m_tool; }
|
||||
Tool& tool() { return *m_tool; }
|
||||
|
||||
virtual bool is_uncheckable() const override { return false; }
|
||||
|
||||
virtual void context_menu_event(GUI::ContextMenuEvent& event) override
|
||||
{
|
||||
m_action->activate();
|
||||
m_tool->on_tool_button_contextmenu(event);
|
||||
}
|
||||
|
||||
private:
|
||||
ToolboxWidget& m_toolbox;
|
||||
OwnPtr<Tool> m_tool;
|
||||
RefPtr<GUI::Action> m_action;
|
||||
};
|
||||
|
||||
ToolboxWidget::ToolboxWidget()
|
||||
{
|
||||
set_fill_with_background_color(true);
|
||||
|
||||
set_frame_thickness(1);
|
||||
set_frame_shape(Gfx::FrameShape::Panel);
|
||||
set_frame_shadow(Gfx::FrameShadow::Raised);
|
||||
|
||||
set_fixed_width(48);
|
||||
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
m_action_group.set_exclusive(true);
|
||||
m_action_group.set_unchecking_allowed(false);
|
||||
|
||||
deferred_invoke([this](auto&) {
|
||||
setup_tools();
|
||||
});
|
||||
}
|
||||
|
||||
ToolboxWidget::~ToolboxWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ToolboxWidget::setup_tools()
|
||||
{
|
||||
auto add_tool = [&](const StringView& name, const StringView& icon_name, const GUI::Shortcut& shortcut, NonnullOwnPtr<Tool> tool) -> ToolButton& {
|
||||
m_tools.append(tool.ptr());
|
||||
auto& button = add<ToolButton>(*this, name, shortcut, move(tool));
|
||||
button.set_focus_policy(GUI::FocusPolicy::TabFocus);
|
||||
button.set_fixed_height(32);
|
||||
button.set_checkable(true);
|
||||
button.set_icon(Gfx::Bitmap::load_from_file(String::formatted("/res/icons/pixelpaint/{}.png", icon_name)));
|
||||
return button;
|
||||
};
|
||||
|
||||
add_tool("Move", "move", { 0, Key_M }, make<MoveTool>());
|
||||
add_tool("Pen", "pen", { 0, Key_N }, make<PenTool>());
|
||||
add_tool("Brush", "brush", { 0, Key_P }, make<BrushTool>());
|
||||
add_tool("Bucket Fill", "bucket", { Mod_Shift, Key_B }, make<BucketTool>());
|
||||
add_tool("Spray", "spray", { Mod_Shift, Key_S }, make<SprayTool>());
|
||||
add_tool("Color Picker", "picker", { 0, Key_O }, make<PickerTool>());
|
||||
add_tool("Erase", "eraser", { Mod_Shift, Key_E }, make<EraseTool>());
|
||||
add_tool("Line", "line", { Mod_Ctrl | Mod_Shift, Key_L }, make<LineTool>());
|
||||
add_tool("Rectangle", "rectangle", { Mod_Ctrl | Mod_Shift, Key_R }, make<RectangleTool>());
|
||||
add_tool("Ellipse", "circle", { Mod_Ctrl | Mod_Shift, Key_E }, make<EllipseTool>());
|
||||
}
|
||||
|
||||
}
|
60
Userland/Applications/PixelPaint/ToolboxWidget.h
Normal file
60
Userland/Applications/PixelPaint/ToolboxWidget.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGUI/Frame.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class Tool;
|
||||
|
||||
class ToolboxWidget final : public GUI::Frame {
|
||||
C_OBJECT(ToolboxWidget)
|
||||
public:
|
||||
virtual ~ToolboxWidget() override;
|
||||
|
||||
Function<void(Tool*)> on_tool_selection;
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_tool(Callback callback)
|
||||
{
|
||||
for (auto& tool : m_tools)
|
||||
callback(*tool);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ToolButton;
|
||||
|
||||
void setup_tools();
|
||||
|
||||
explicit ToolboxWidget();
|
||||
GUI::ActionGroup m_action_group;
|
||||
Vector<Tool*> m_tools;
|
||||
};
|
||||
|
||||
}
|
387
Userland/Applications/PixelPaint/main.cpp
Normal file
387
Userland/Applications/PixelPaint/main.cpp
Normal file
|
@ -0,0 +1,387 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "CreateNewImageDialog.h"
|
||||
#include "CreateNewLayerDialog.h"
|
||||
#include "FilterParams.h"
|
||||
#include "Image.h"
|
||||
#include "ImageEditor.h"
|
||||
#include "Layer.h"
|
||||
#include "LayerListWidget.h"
|
||||
#include "LayerPropertiesWidget.h"
|
||||
#include "PaletteWidget.h"
|
||||
#include "Tool.h"
|
||||
#include "ToolPropertiesWidget.h"
|
||||
#include "ToolboxWidget.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Clipboard.h>
|
||||
#include <LibGUI/FilePicker.h>
|
||||
#include <LibGUI/Icon.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/MenuBar.h>
|
||||
#include <LibGUI/MessageBox.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Matrix4x4.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (pledge("stdio thread shared_buffer accept rpath unix wpath cpath fattr", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto app = GUI::Application::construct(argc, argv);
|
||||
|
||||
if (pledge("stdio thread shared_buffer accept rpath wpath cpath", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto app_icon = GUI::Icon::default_icon("app-pixel-paint");
|
||||
|
||||
auto window = GUI::Window::construct();
|
||||
window->set_title("PixelPaint");
|
||||
window->resize(950, 570);
|
||||
window->set_icon(app_icon.bitmap_for_size(16));
|
||||
|
||||
auto& horizontal_container = window->set_main_widget<GUI::Widget>();
|
||||
horizontal_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
horizontal_container.layout()->set_spacing(0);
|
||||
|
||||
auto& toolbox = horizontal_container.add<PixelPaint::ToolboxWidget>();
|
||||
|
||||
auto& vertical_container = horizontal_container.add<GUI::Widget>();
|
||||
vertical_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
vertical_container.layout()->set_spacing(0);
|
||||
|
||||
auto& image_editor = vertical_container.add<PixelPaint::ImageEditor>();
|
||||
image_editor.set_focus(true);
|
||||
|
||||
vertical_container.add<PixelPaint::PaletteWidget>(image_editor);
|
||||
|
||||
auto& right_panel = horizontal_container.add<GUI::Widget>();
|
||||
right_panel.set_fill_with_background_color(true);
|
||||
right_panel.set_fixed_width(230);
|
||||
right_panel.set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& layer_list_widget = right_panel.add<PixelPaint::LayerListWidget>();
|
||||
|
||||
auto& layer_properties_widget = right_panel.add<PixelPaint::LayerPropertiesWidget>();
|
||||
|
||||
auto& tool_properties_widget = right_panel.add<PixelPaint::ToolPropertiesWidget>();
|
||||
|
||||
toolbox.on_tool_selection = [&](auto* tool) {
|
||||
image_editor.set_active_tool(tool);
|
||||
tool_properties_widget.set_active_tool(tool);
|
||||
};
|
||||
|
||||
window->show();
|
||||
|
||||
auto menubar = GUI::MenuBar::construct();
|
||||
auto& app_menu = menubar->add_menu("PixelPaint");
|
||||
|
||||
app_menu.add_action(
|
||||
GUI::Action::create(
|
||||
"New", [&](auto&) {
|
||||
auto dialog = PixelPaint::CreateNewImageDialog::construct(window);
|
||||
if (dialog->exec() == GUI::Dialog::ExecOK) {
|
||||
auto image = PixelPaint::Image::create_with_size(dialog->image_size());
|
||||
auto bg_layer = PixelPaint::Layer::create_with_size(*image, image->size(), "Background");
|
||||
image->add_layer(*bg_layer);
|
||||
bg_layer->bitmap().fill(Color::White);
|
||||
|
||||
image_editor.set_image(image);
|
||||
layer_list_widget.set_image(image);
|
||||
image_editor.set_active_layer(bg_layer);
|
||||
}
|
||||
},
|
||||
window));
|
||||
app_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
|
||||
Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
|
||||
|
||||
if (!open_path.has_value())
|
||||
return;
|
||||
|
||||
auto image = PixelPaint::Image::create_from_file(open_path.value());
|
||||
image_editor.set_image(image);
|
||||
layer_list_widget.set_image(image);
|
||||
}));
|
||||
app_menu.add_action(GUI::CommonActions::make_save_as_action([&](auto&) {
|
||||
if (!image_editor.image())
|
||||
return;
|
||||
|
||||
Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "pp");
|
||||
|
||||
if (!save_path.has_value())
|
||||
return;
|
||||
|
||||
image_editor.image()->save(save_path.value());
|
||||
}));
|
||||
auto& export_submenu = app_menu.add_submenu("Export");
|
||||
export_submenu.add_action(
|
||||
GUI::Action::create(
|
||||
"As BMP", [&](auto&) {
|
||||
if (!image_editor.image())
|
||||
return;
|
||||
|
||||
Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp");
|
||||
|
||||
if (!save_path.has_value())
|
||||
return;
|
||||
|
||||
image_editor.image()->export_bmp(save_path.value());
|
||||
},
|
||||
window));
|
||||
|
||||
app_menu.add_separator();
|
||||
app_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
|
||||
GUI::Application::the()->quit();
|
||||
return;
|
||||
}));
|
||||
|
||||
auto& edit_menu = menubar->add_menu("Edit");
|
||||
auto paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
|
||||
ASSERT(image_editor.image());
|
||||
auto bitmap = GUI::Clipboard::the().bitmap();
|
||||
if (!bitmap)
|
||||
return;
|
||||
|
||||
auto layer = PixelPaint::Layer::create_with_bitmap(*image_editor.image(), *bitmap, "Pasted layer");
|
||||
image_editor.image()->add_layer(layer.release_nonnull());
|
||||
});
|
||||
GUI::Clipboard::the().on_change = [&](auto& mime_type) {
|
||||
paste_action->set_enabled(mime_type == "image/x-serenityos");
|
||||
};
|
||||
paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
|
||||
|
||||
edit_menu.add_action(paste_action);
|
||||
|
||||
auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
|
||||
ASSERT(image_editor.image());
|
||||
image_editor.undo();
|
||||
});
|
||||
edit_menu.add_action(undo_action);
|
||||
|
||||
auto redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
|
||||
ASSERT(image_editor.image());
|
||||
image_editor.redo();
|
||||
});
|
||||
edit_menu.add_action(redo_action);
|
||||
|
||||
auto& tool_menu = menubar->add_menu("Tool");
|
||||
toolbox.for_each_tool([&](auto& tool) {
|
||||
if (tool.action())
|
||||
tool_menu.add_action(*tool.action());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
auto& layer_menu = menubar->add_menu("Layer");
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Create new layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
|
||||
auto dialog = PixelPaint::CreateNewLayerDialog::construct(image_editor.image()->size(), window);
|
||||
if (dialog->exec() == GUI::Dialog::ExecOK) {
|
||||
auto layer = PixelPaint::Layer::create_with_size(*image_editor.image(), dialog->layer_size(), dialog->layer_name());
|
||||
if (!layer) {
|
||||
GUI::MessageBox::show_error(window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
|
||||
return;
|
||||
}
|
||||
image_editor.image()->add_layer(layer.release_nonnull());
|
||||
image_editor.layers_did_change();
|
||||
}
|
||||
},
|
||||
window));
|
||||
|
||||
layer_menu.add_separator();
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Select previous layer", { 0, Key_PageUp }, [&](auto&) {
|
||||
layer_list_widget.move_selection(1);
|
||||
},
|
||||
window));
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Select next layer", { 0, Key_PageDown }, [&](auto&) {
|
||||
layer_list_widget.move_selection(-1);
|
||||
},
|
||||
window));
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Select top layer", { 0, Key_Home }, [&](auto&) {
|
||||
layer_list_widget.select_top_layer();
|
||||
},
|
||||
window));
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Select bottom layer", { 0, Key_End }, [&](auto&) {
|
||||
layer_list_widget.select_bottom_layer();
|
||||
},
|
||||
window));
|
||||
layer_menu.add_separator();
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Move active layer up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
|
||||
auto active_layer = image_editor.active_layer();
|
||||
if (!active_layer)
|
||||
return;
|
||||
image_editor.image()->move_layer_up(*active_layer);
|
||||
},
|
||||
window));
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Move active layer down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
|
||||
auto active_layer = image_editor.active_layer();
|
||||
if (!active_layer)
|
||||
return;
|
||||
image_editor.image()->move_layer_down(*active_layer);
|
||||
},
|
||||
window));
|
||||
layer_menu.add_separator();
|
||||
layer_menu.add_action(GUI::Action::create(
|
||||
"Remove active layer", { Mod_Ctrl, Key_D }, [&](auto&) {
|
||||
auto active_layer = image_editor.active_layer();
|
||||
if (!active_layer)
|
||||
return;
|
||||
image_editor.image()->remove_layer(*active_layer);
|
||||
image_editor.set_active_layer(nullptr);
|
||||
},
|
||||
window));
|
||||
|
||||
auto& filter_menu = menubar->add_menu("Filter");
|
||||
auto& spatial_filters_menu = filter_menu.add_submenu("Spatial");
|
||||
|
||||
auto& edge_detect_submenu = spatial_filters_menu.add_submenu("Edge Detect");
|
||||
edge_detect_submenu.add_action(GUI::Action::create("Laplacian (cardinal)", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::LaplacianFilter filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
edge_detect_submenu.add_action(GUI::Action::create("Laplacian (diagonal)", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::LaplacianFilter filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
auto& blur_submenu = spatial_filters_menu.add_submenu("Blur and Sharpen");
|
||||
blur_submenu.add_action(GUI::Action::create("Gaussian Blur (3x3)", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::SpatialGaussianBlurFilter<3> filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
blur_submenu.add_action(GUI::Action::create("Gaussian Blur (5x5)", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::SpatialGaussianBlurFilter<5> filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
blur_submenu.add_action(GUI::Action::create("Box Blur (3x3)", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::BoxBlurFilter<3> filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
blur_submenu.add_action(GUI::Action::create("Box Blur (5x5)", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::BoxBlurFilter<5> filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
blur_submenu.add_action(GUI::Action::create("Sharpen", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::SharpenFilter filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
spatial_filters_menu.add_separator();
|
||||
spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 Convolution", [&](auto&) {
|
||||
if (auto* layer = image_editor.active_layer()) {
|
||||
Gfx::GenericConvolutionFilter<5> filter;
|
||||
if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(window)) {
|
||||
filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
|
||||
image_editor.did_complete_action();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
auto& help_menu = menubar->add_menu("Help");
|
||||
help_menu.add_action(GUI::CommonActions::make_about_action("PixelPaint", app_icon, window));
|
||||
|
||||
app->set_menubar(move(menubar));
|
||||
|
||||
image_editor.on_active_layer_change = [&](auto* layer) {
|
||||
layer_list_widget.set_selected_layer(layer);
|
||||
layer_properties_widget.set_layer(layer);
|
||||
};
|
||||
|
||||
auto image = PixelPaint::Image::create_with_size({ 640, 480 });
|
||||
|
||||
auto bg_layer = PixelPaint::Layer::create_with_size(*image, { 640, 480 }, "Background");
|
||||
image->add_layer(*bg_layer);
|
||||
bg_layer->bitmap().fill(Color::White);
|
||||
|
||||
auto fg_layer1 = PixelPaint::Layer::create_with_size(*image, { 200, 200 }, "FG Layer 1");
|
||||
fg_layer1->set_location({ 50, 50 });
|
||||
image->add_layer(*fg_layer1);
|
||||
fg_layer1->bitmap().fill(Color::Yellow);
|
||||
|
||||
auto fg_layer2 = PixelPaint::Layer::create_with_size(*image, { 100, 100 }, "FG Layer 2");
|
||||
fg_layer2->set_location({ 300, 300 });
|
||||
image->add_layer(*fg_layer2);
|
||||
fg_layer2->bitmap().fill(Color::Blue);
|
||||
|
||||
layer_list_widget.on_layer_select = [&](auto* layer) {
|
||||
image_editor.set_active_layer(layer);
|
||||
};
|
||||
|
||||
layer_list_widget.set_image(image);
|
||||
|
||||
image_editor.set_image(image);
|
||||
image_editor.set_active_layer(bg_layer);
|
||||
|
||||
return app->exec();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue