diff options
author | Andreas Kling <kling@serenityos.org> | 2022-08-23 21:33:52 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-08-23 22:39:27 +0200 |
commit | 34a09bbb5410b866e35bb03fcb489a61f4b262b3 (patch) | |
tree | e69f7aeeab726ae14637019f38012cde7a2a5050 /Userland/Applications/PixelPaint | |
parent | 5ded6904d8e9b1453548b196f39d09dc20ba3e2b (diff) | |
download | serenity-34a09bbb5410b866e35bb03fcb489a61f4b262b3.zip |
PixelPaint: Add simple "Crop Image to Content" feature
This command finds the smallest non-empty content bounding rect
by looking for the outermost non-transparent pixels in the image,
and then crops the image to that rect.
It's implemented in a pretty naive way, but it's a start. :^)
Diffstat (limited to 'Userland/Applications/PixelPaint')
-rw-r--r-- | Userland/Applications/PixelPaint/Image.cpp | 20 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/Image.h | 2 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/Layer.cpp | 46 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/Layer.h | 2 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/MainWidget.cpp | 13 |
5 files changed, 83 insertions, 0 deletions
diff --git a/Userland/Applications/PixelPaint/Image.cpp b/Userland/Applications/PixelPaint/Image.cpp index 5832fefdd7..98bc5760a2 100644 --- a/Userland/Applications/PixelPaint/Image.cpp +++ b/Userland/Applications/PixelPaint/Image.cpp @@ -530,6 +530,26 @@ void Image::crop(Gfx::IntRect const& cropped_rect) did_change_rect(cropped_rect); } +Optional<Gfx::IntRect> Image::nonempty_content_bounding_rect() const +{ + if (m_layers.is_empty()) + return {}; + + Optional<Gfx::IntRect> bounding_rect; + for (auto& layer : m_layers) { + auto layer_content_rect_in_layer_coordinates = layer.nonempty_content_bounding_rect(); + if (!layer_content_rect_in_layer_coordinates.has_value()) + continue; + auto layer_content_rect_in_image_coordinates = layer_content_rect_in_layer_coordinates.value().translated(layer.location()); + if (!bounding_rect.has_value()) + bounding_rect = layer_content_rect_in_image_coordinates; + else + bounding_rect = bounding_rect->united(layer_content_rect_in_image_coordinates); + } + + return bounding_rect; +} + void Image::resize(Gfx::IntSize const& new_size, Gfx::Painter::ScalingMode scaling_mode) { float scale_x = 1.0f; diff --git a/Userland/Applications/PixelPaint/Image.h b/Userland/Applications/PixelPaint/Image.h index 91ca62612b..c5b5dd49e8 100644 --- a/Userland/Applications/PixelPaint/Image.h +++ b/Userland/Applications/PixelPaint/Image.h @@ -99,6 +99,8 @@ public: void crop(Gfx::IntRect const& rect); void resize(Gfx::IntSize const& new_size, Gfx::Painter::ScalingMode scaling_mode); + Optional<Gfx::IntRect> nonempty_content_bounding_rect() const; + Color color_at(Gfx::IntPoint const& point) const; private: diff --git a/Userland/Applications/PixelPaint/Layer.cpp b/Userland/Applications/PixelPaint/Layer.cpp index df74c14bc0..307d724a30 100644 --- a/Userland/Applications/PixelPaint/Layer.cpp +++ b/Userland/Applications/PixelPaint/Layer.cpp @@ -269,4 +269,50 @@ void Layer::set_edit_mode(Layer::EditMode mode) m_edit_mode = mode; } +Optional<Gfx::IntRect> Layer::nonempty_content_bounding_rect() const +{ + Optional<int> min_content_y; + Optional<int> min_content_x; + Optional<int> max_content_y; + Optional<int> max_content_x; + + for (int y = 0; y < m_content_bitmap->height(); ++y) { + for (int x = 0; x < m_content_bitmap->width(); ++x) { + auto color = m_content_bitmap->get_pixel(x, y); + if (color.alpha() == 0) + continue; + + if (!min_content_x.has_value()) + min_content_x = x; + else + min_content_x = min(*min_content_x, x); + + if (!min_content_y.has_value()) + min_content_y = y; + else + min_content_y = min(*min_content_y, y); + + if (!max_content_x.has_value()) + max_content_x = x; + else + max_content_x = max(*max_content_x, x); + + if (!max_content_y.has_value()) + max_content_y = y; + else + max_content_y = max(*max_content_y, y); + } + } + + if (!min_content_x.has_value()) + return {}; + + return Gfx::IntRect { + *min_content_x, + *min_content_y, + *max_content_x - *min_content_x + 1, + *max_content_y - *min_content_y + 1 + }; +} + } diff --git a/Userland/Applications/PixelPaint/Layer.h b/Userland/Applications/PixelPaint/Layer.h index ab24d66cb3..a2996e24c0 100644 --- a/Userland/Applications/PixelPaint/Layer.h +++ b/Userland/Applications/PixelPaint/Layer.h @@ -61,6 +61,8 @@ public: void resize(Gfx::IntRect const& new_rect, Gfx::Painter::ScalingMode scaling_mode); void resize(Gfx::IntSize const& new_size, Gfx::IntPoint const& new_location, Gfx::Painter::ScalingMode scaling_mode); + Optional<Gfx::IntRect> nonempty_content_bounding_rect() const; + ErrorOr<void> try_set_bitmaps(NonnullRefPtr<Gfx::Bitmap> content, RefPtr<Gfx::Bitmap> mask); void did_modify_bitmap(Gfx::IntRect const& = {}); diff --git a/Userland/Applications/PixelPaint/MainWidget.cpp b/Userland/Applications/PixelPaint/MainWidget.cpp index 958f8dcb2d..f3de3a0f3d 100644 --- a/Userland/Applications/PixelPaint/MainWidget.cpp +++ b/Userland/Applications/PixelPaint/MainWidget.cpp @@ -560,6 +560,19 @@ void MainWidget::initialize_menubar(GUI::Window& window) editor->did_complete_action("Crop Image to Selection"sv); })); + m_image_menu->add_action(GUI::Action::create( + "&Crop Image to Content", g_icon_bag.crop, [&](auto&) { + auto* editor = current_image_editor(); + VERIFY(editor); + + auto content_bounding_rect = editor->image().nonempty_content_bounding_rect(); + if (!content_bounding_rect.has_value()) + return; + + editor->image().crop(content_bounding_rect.value()); + editor->did_complete_action("Crop Image to Content"sv); + })); + m_layer_menu = window.add_menu("&Layer"); m_layer_menu->add_action(GUI::Action::create( "New &Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, g_icon_bag.new_layer, [&](auto&) { |