Browse Source

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. :^)
Andreas Kling 2 năm trước cách đây
mục cha
commit
34a09bbb54

+ 20 - 0
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;

+ 2 - 0
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:

+ 46 - 0
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
+    };
+}
+
 }

+ 2 - 0
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& = {});

+ 13 - 0
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&) {