mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 13:07:46 +00:00
PixelPaint: Add Image>Resize Image... dialog. (Front end)
This commit is contained in:
parent
abaecb878b
commit
02399d4775
11 changed files with 302 additions and 0 deletions
|
@ -8,6 +8,7 @@ serenity_component(
|
|||
compile_gml(PixelPaintWindow.gml PixelPaintWindowGML.h pixel_paint_window_gml)
|
||||
compile_gml(EditGuideDialog.gml EditGuideDialogGML.h edit_guide_dialog_gml)
|
||||
compile_gml(FilterGallery.gml FilterGalleryGML.h filter_gallery_gml)
|
||||
compile_gml(ResizeImageDialog.gml ResizeImageDialogGML.h resize_image_dialog_gml)
|
||||
|
||||
set(SOURCES
|
||||
CreateNewImageDialog.cpp
|
||||
|
@ -43,6 +44,8 @@ set(SOURCES
|
|||
PaletteWidget.cpp
|
||||
PixelPaintWindowGML.h
|
||||
ProjectLoader.cpp
|
||||
ResizeImageDialog.cpp
|
||||
ResizeImageDialogGML.h
|
||||
Selection.cpp
|
||||
ToolPropertiesWidget.cpp
|
||||
ToolboxWidget.cpp
|
||||
|
|
|
@ -26,6 +26,7 @@ ErrorOr<IconBag> IconBag::try_create()
|
|||
icon_bag.clear_guides = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/pixelpaint/clear-guides.png"));
|
||||
icon_bag.edit_flip_vertical = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-flip-vertical.png"));
|
||||
icon_bag.edit_flip_horizontal = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-flip-horizontal.png"));
|
||||
icon_bag.resize_image = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/selection-move.png"));
|
||||
icon_bag.crop = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/pixelpaint/crop.png"));
|
||||
icon_bag.new_layer = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/new-layer.png"));
|
||||
icon_bag.previous_layer = TRY(Gfx::Bitmap::try_load_from_file("/res/icons/pixelpaint/previous-layer.png"));
|
||||
|
|
|
@ -27,6 +27,7 @@ struct IconBag final {
|
|||
RefPtr<Gfx::Bitmap> clear_guides { nullptr };
|
||||
RefPtr<Gfx::Bitmap> edit_flip_vertical { nullptr };
|
||||
RefPtr<Gfx::Bitmap> edit_flip_horizontal { nullptr };
|
||||
RefPtr<Gfx::Bitmap> resize_image { nullptr };
|
||||
RefPtr<Gfx::Bitmap> crop { nullptr };
|
||||
RefPtr<Gfx::Bitmap> new_layer { nullptr };
|
||||
RefPtr<Gfx::Bitmap> previous_layer { nullptr };
|
||||
|
|
|
@ -527,6 +527,17 @@ void Image::crop(Gfx::IntRect const& cropped_rect)
|
|||
did_change_rect(cropped_rect);
|
||||
}
|
||||
|
||||
void Image::resize(Gfx::IntSize const& new_size, Gfx::Painter::ScalingMode scaling_mode)
|
||||
{
|
||||
for (auto& layer : m_layers) {
|
||||
layer.resize(new_size, scaling_mode);
|
||||
}
|
||||
|
||||
m_size = { new_size.width(), new_size.height() };
|
||||
did_change_rect();
|
||||
|
||||
}
|
||||
|
||||
Color Image::color_at(Gfx::IntPoint const& point) const
|
||||
{
|
||||
Color color;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibGUI/Forward.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/Size.h>
|
||||
|
||||
|
@ -96,6 +97,7 @@ public:
|
|||
void flip(Gfx::Orientation orientation);
|
||||
void rotate(Gfx::RotationDirection direction);
|
||||
void crop(Gfx::IntRect const& rect);
|
||||
void resize(Gfx::IntSize const& new_size, Gfx::Painter::ScalingMode scaling_mode);
|
||||
|
||||
Color color_at(Gfx::IntPoint const& point) const;
|
||||
|
||||
|
|
|
@ -180,6 +180,31 @@ void Layer::crop(Gfx::IntRect const& rect)
|
|||
did_modify_bitmap();
|
||||
}
|
||||
|
||||
void Layer::resize(Gfx::IntSize const& new_size, Gfx::Painter::ScalingMode scaling_mode)
|
||||
{
|
||||
const Gfx::IntRect old_rect(Gfx::IntPoint(0, 0), size());
|
||||
const Gfx::IntRect new_rect(Gfx::IntPoint(0, 0), new_size);
|
||||
|
||||
{
|
||||
auto resized = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, new_size).release_value_but_fixme_should_propagate_errors();
|
||||
Gfx::Painter painter(resized);
|
||||
|
||||
painter.draw_scaled_bitmap(new_rect, *m_content_bitmap, old_rect, 1.0f, scaling_mode);
|
||||
|
||||
m_content_bitmap = move(resized);
|
||||
}
|
||||
|
||||
if (m_mask_bitmap) {
|
||||
auto resized = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, new_size).release_value_but_fixme_should_propagate_errors();
|
||||
Gfx::Painter painter(resized);
|
||||
|
||||
painter.draw_scaled_bitmap(new_rect, *m_mask_bitmap, old_rect, 1.0f, scaling_mode);
|
||||
m_mask_bitmap = move(resized);
|
||||
}
|
||||
|
||||
did_modify_bitmap();
|
||||
}
|
||||
|
||||
void Layer::update_cached_bitmap()
|
||||
{
|
||||
if (!is_masked()) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <AK/String.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
|
@ -56,6 +57,7 @@ public:
|
|||
void flip(Gfx::Orientation orientation);
|
||||
void rotate(Gfx::RotationDirection direction);
|
||||
void crop(Gfx::IntRect const& rect);
|
||||
void resize(Gfx::IntSize const& new_size, Gfx::Painter::ScalingMode scaling_mode);
|
||||
|
||||
ErrorOr<void> try_set_bitmaps(NonnullRefPtr<Gfx::Bitmap> content, RefPtr<Gfx::Bitmap> mask);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "EditGuideDialog.h"
|
||||
#include "FilterGallery.h"
|
||||
#include "FilterParams.h"
|
||||
#include "ResizeImageDialog.h"
|
||||
#include <Applications/PixelPaint/PixelPaintWindowGML.h>
|
||||
#include <LibConfig/Client.h>
|
||||
#include <LibCore/File.h>
|
||||
|
@ -499,6 +500,14 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
|||
editor->image().rotate(Gfx::RotationDirection::Clockwise);
|
||||
}));
|
||||
m_image_menu->add_separator();
|
||||
m_image_menu->add_action(GUI::Action::create(
|
||||
"&Resize Image...", { Mod_Ctrl | Mod_Shift, Key_R }, g_icon_bag.resize_image, [&](auto&) {
|
||||
auto* editor = current_image_editor();
|
||||
VERIFY(editor);
|
||||
auto dialog = PixelPaint::ResizeImageDialog::construct(editor->image().size(), &window);
|
||||
if (dialog->exec() == GUI::Dialog::ExecResult::OK)
|
||||
editor->image().resize(dialog->desired_size(), dialog->scaling_mode());
|
||||
}));
|
||||
m_image_menu->add_action(GUI::Action::create(
|
||||
"&Crop To Selection", g_icon_bag.crop, [&](auto&) {
|
||||
auto* editor = current_image_editor();
|
||||
|
|
111
Userland/Applications/PixelPaint/ResizeImageDialog.cpp
Normal file
111
Userland/Applications/PixelPaint/ResizeImageDialog.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andrew Smith <andrew@alsmith.net>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ResizeImageDialog.h"
|
||||
#include <Applications/PixelPaint/ResizeImageDialogGML.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Button.h>
|
||||
#include <LibGUI/CheckBox.h>
|
||||
#include <LibGUI/ComboBox.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/RadioButton.h>
|
||||
#include <LibGUI/SpinBox.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
ResizeImageDialog::ResizeImageDialog(Gfx::IntSize const& suggested_size, GUI::Window* parent_window)
|
||||
: Dialog(parent_window)
|
||||
{
|
||||
m_desired_size.set_width(max(1, suggested_size.width()));
|
||||
m_desired_size.set_height(max(1, suggested_size.height()));
|
||||
m_starting_aspect_ratio = m_desired_size.width() / static_cast<float>(m_desired_size.height());
|
||||
|
||||
set_title("Resize Image");
|
||||
resize(260, 210);
|
||||
set_icon(parent_window->icon());
|
||||
|
||||
auto& main_widget = set_main_widget<GUI::Widget>();
|
||||
if (!main_widget.load_from_gml(resize_image_dialog_gml))
|
||||
VERIFY_NOT_REACHED();
|
||||
|
||||
auto width_spinbox = main_widget.find_descendant_of_type_named<GUI::SpinBox>("width_spinbox");
|
||||
auto height_spinbox = main_widget.find_descendant_of_type_named<GUI::SpinBox>("height_spinbox");
|
||||
auto keep_aspect_ratio_checkbox = main_widget.find_descendant_of_type_named<GUI::CheckBox>("keep_aspect_ratio_checkbox");
|
||||
|
||||
VERIFY(width_spinbox);
|
||||
VERIFY(height_spinbox);
|
||||
VERIFY(keep_aspect_ratio_checkbox);
|
||||
|
||||
width_spinbox->set_value(m_desired_size.width());
|
||||
width_spinbox->on_change = [this, height_spinbox, keep_aspect_ratio_checkbox](int value) {
|
||||
if (keep_aspect_ratio_checkbox->is_checked()) {
|
||||
int desired_height = static_cast<int>(roundf(value / m_starting_aspect_ratio));
|
||||
height_spinbox->set_value(desired_height, GUI::AllowCallback::No);
|
||||
m_desired_size.set_height(height_spinbox->value());
|
||||
}
|
||||
m_desired_size.set_width(value);
|
||||
};
|
||||
width_spinbox->on_return_pressed = [this]() {
|
||||
done(ExecResult::OK);
|
||||
};
|
||||
|
||||
height_spinbox->set_value(m_desired_size.height());
|
||||
height_spinbox->on_change = [this, width_spinbox, keep_aspect_ratio_checkbox](int value) {
|
||||
if (keep_aspect_ratio_checkbox->is_checked()) {
|
||||
int desired_width = static_cast<int>(roundf(value * m_starting_aspect_ratio));
|
||||
width_spinbox->set_value(desired_width, GUI::AllowCallback::No);
|
||||
m_desired_size.set_width(width_spinbox->value());
|
||||
}
|
||||
m_desired_size.set_height(value);
|
||||
};
|
||||
height_spinbox->on_return_pressed = [this]() {
|
||||
done(ExecResult::OK);
|
||||
};
|
||||
|
||||
keep_aspect_ratio_checkbox->on_checked = [this, height_spinbox](bool is_checked) {
|
||||
if (is_checked) {
|
||||
int desired_height = static_cast<int>(roundf(m_desired_size.width() / m_starting_aspect_ratio));
|
||||
height_spinbox->set_value(desired_height, GUI::AllowCallback::No);
|
||||
m_desired_size.set_height(height_spinbox->value());
|
||||
}
|
||||
};
|
||||
|
||||
auto nearest_neighbor_radio = main_widget.find_descendant_of_type_named<GUI::RadioButton>("nearest_neighbor_radio");
|
||||
auto bilinear_radio = main_widget.find_descendant_of_type_named<GUI::RadioButton>("bilinear_radio");
|
||||
|
||||
VERIFY(nearest_neighbor_radio);
|
||||
VERIFY(bilinear_radio);
|
||||
|
||||
m_scaling_mode = Gfx::Painter::ScalingMode::NearestNeighbor;
|
||||
if (bilinear_radio->is_checked()) {
|
||||
m_scaling_mode = Gfx::Painter::ScalingMode::BilinearBlend;
|
||||
}
|
||||
|
||||
nearest_neighbor_radio->on_checked = [this](bool is_checked) {
|
||||
if (is_checked)
|
||||
m_scaling_mode = Gfx::Painter::ScalingMode::NearestNeighbor;
|
||||
};
|
||||
bilinear_radio->on_checked = [this](bool is_checked) {
|
||||
if (is_checked)
|
||||
m_scaling_mode = Gfx::Painter::ScalingMode::BilinearBlend;
|
||||
};
|
||||
|
||||
auto ok_button = main_widget.find_descendant_of_type_named<GUI::Button>("ok_button");
|
||||
auto cancel_button = main_widget.find_descendant_of_type_named<GUI::Button>("cancel_button");
|
||||
|
||||
VERIFY(ok_button);
|
||||
VERIFY(cancel_button);
|
||||
|
||||
ok_button->on_click = [this](auto) {
|
||||
done(ExecResult::OK);
|
||||
};
|
||||
|
||||
cancel_button->on_click = [this](auto) {
|
||||
done(ExecResult::Cancel);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
108
Userland/Applications/PixelPaint/ResizeImageDialog.gml
Normal file
108
Userland/Applications/PixelPaint/ResizeImageDialog.gml
Normal file
|
@ -0,0 +1,108 @@
|
|||
@GUI::Widget {
|
||||
fill_with_background_color: true
|
||||
min_width: 260
|
||||
min_height: 210
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
|
||||
@GUI::GroupBox {
|
||||
title: "Size (px)"
|
||||
shrink_to_fit: true
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
fixed_height: 24
|
||||
|
||||
@GUI::Label {
|
||||
text: "Width:"
|
||||
fixed_width: 60
|
||||
text_alignment: "CenterRight"
|
||||
}
|
||||
|
||||
@GUI::SpinBox {
|
||||
name: "width_spinbox"
|
||||
min: 1
|
||||
max: 16384
|
||||
min_width: 140
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
fixed_height: 24
|
||||
|
||||
@GUI::Label {
|
||||
text: "Height:"
|
||||
fixed_width: 60
|
||||
text_alignment: "CenterRight"
|
||||
}
|
||||
|
||||
@GUI::SpinBox {
|
||||
name: "height_spinbox"
|
||||
min: 1
|
||||
max: 16384
|
||||
min_width: 140
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
fixed_height: 24
|
||||
min_width: 140
|
||||
|
||||
@GUI::Widget {
|
||||
fixed_width: 60
|
||||
}
|
||||
|
||||
@GUI::CheckBox {
|
||||
name: "keep_aspect_ratio_checkbox"
|
||||
text: "Keep aspect ratio"
|
||||
checked: true
|
||||
autosize: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::GroupBox {
|
||||
title: "Scaling Mode"
|
||||
shrink_to_fit: true
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
|
||||
@GUI::RadioButton {
|
||||
name: "nearest_neighbor_radio"
|
||||
text: "Nearest neighbor"
|
||||
checked: true
|
||||
autosize: true
|
||||
}
|
||||
|
||||
@GUI::RadioButton {
|
||||
name: "bilinear_radio"
|
||||
text: "Bilinear"
|
||||
autosize: true
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
|
||||
@GUI::Widget {}
|
||||
|
||||
@GUI::Button {
|
||||
name: "ok_button"
|
||||
text: "OK"
|
||||
max_width: 75
|
||||
}
|
||||
|
||||
@GUI::Button {
|
||||
name: "cancel_button"
|
||||
text: "Cancel"
|
||||
max_width: 75
|
||||
}
|
||||
}
|
||||
}
|
29
Userland/Applications/PixelPaint/ResizeImageDialog.h
Normal file
29
Userland/Applications/PixelPaint/ResizeImageDialog.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andrew Smith <andrew@alsmith.net>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Dialog.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ResizeImageDialog final : public GUI::Dialog {
|
||||
C_OBJECT(ResizeImageDialog);
|
||||
|
||||
public:
|
||||
Gfx::IntSize const& desired_size() const { return m_desired_size; }
|
||||
Gfx::Painter::ScalingMode scaling_mode() const { return m_scaling_mode; }
|
||||
|
||||
private:
|
||||
ResizeImageDialog(Gfx::IntSize const& starting_size, GUI::Window* parent_window);
|
||||
|
||||
Gfx::IntSize m_desired_size;
|
||||
Gfx::Painter::ScalingMode m_scaling_mode;
|
||||
float m_starting_aspect_ratio;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue