1
Fork 0
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:
Andreas Kling 2021-01-12 12:05:23 +01:00
parent aa939c4b4b
commit dc28c07fa5
287 changed files with 1 additions and 1 deletions

View 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();
}
}

View 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);
};
}

View 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();
}
}

View 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 };
};
}

View 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)

View 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);
}
}

View 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;
};
}

View 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());
}
}

View 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;
};
}

View 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());
}
}

View 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 };
};
}

View 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);
}
}

View 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;
};
}

View 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);
}
};
}

View 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();
}
}

View 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;
};
}

View 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);
}
}

View 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;
};
}

View 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);
}
}

View 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 };
};
}

View 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();
}
}

View 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;
};
}

View 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);
}
}
}

View 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;
};
}

View 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());
}
}

View 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 };
};
}

View 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());
}
}

View 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;
};
}

View 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();
}
}

View 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;
};
}

View 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();
}
}

View 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;
};
}

View 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);
}
}

View 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;
};
}

View 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());
}
}

View 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 };
};
}

View 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();
}
}

View 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 };
};
}

View 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;
}
}

View 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;
};
}

View 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()
{
}
}

View 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 };
};
}

View 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>());
}
}

View 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;
};
}

View 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();
}