Parcourir la source

PixelPaint: Split bitmap() of Layer into {content, display}_bitmap

This is in preparation to support masking of Layers. We now distinguish
between the "display_bitmap" which will be the whole Layer with every
effect applied and the "content_bitmap" which contains the actual
unmodified pixels in the Layer.
Tobias Christiansen il y a 3 ans
Parent
commit
31a9196bfe

+ 1 - 1
Userland/Applications/PixelPaint/FilterGallery.cpp

@@ -65,7 +65,7 @@ FilterGallery::FilterGallery(GUI::Window* parent_window, ImageEditor* editor)
         m_config_widget->add_child(*m_selected_filter_config_widget);
     };
 
-    m_preview_widget->set_bitmap(editor->active_layer()->bitmap().clone().release_value());
+    m_preview_widget->set_bitmap(editor->active_layer()->content_bitmap().clone().release_value());
 
     apply_button->on_click = [this](auto) {
         if (!m_selected_filter) {

+ 1 - 1
Userland/Applications/PixelPaint/Filters/Filter.cpp

@@ -31,7 +31,7 @@ void Filter::apply() const
     if (!m_editor)
         return;
     if (auto* layer = m_editor->active_layer()) {
-        apply(layer->bitmap(), layer->bitmap());
+        apply(layer->content_bitmap(), layer->content_bitmap());
         layer->did_modify_bitmap(layer->rect());
         m_editor->did_complete_action();
     }

+ 19 - 15
Userland/Applications/PixelPaint/Image.cpp

@@ -49,7 +49,7 @@ void Image::paint_into(GUI::Painter& painter, Gfx::IntRect const& dest_rect) con
             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);
+        painter.draw_scaled_bitmap(target, layer.display_bitmap(), layer.rect(), (float)layer.opacity_percent() / 100.0f);
     }
 }
 
@@ -126,7 +126,8 @@ void Image::serialize_as_json(JsonObjectSerializer<StringBuilder>& json) const
             MUST(json_layer.add("opacity_percent", layer.opacity_percent()));
             MUST(json_layer.add("visible", layer.is_visible()));
             MUST(json_layer.add("selected", layer.is_selected()));
-            MUST(json_layer.add("bitmap", encode_base64(bmp_dumber.dump(layer.bitmap()))));
+            // FIXME: Respect mask
+            MUST(json_layer.add("bitmap", encode_base64(bmp_dumber.dump(layer.display_bitmap()))));
         }
     }
 }
@@ -330,7 +331,7 @@ void Image::flatten_all_layers()
 
     auto& bottom_layer = m_layers.at(0);
 
-    GUI::Painter painter(bottom_layer.bitmap());
+    GUI::Painter painter(bottom_layer.content_bitmap());
     paint_into(painter, { 0, 0, m_size.width(), m_size.height() });
 
     for (size_t index = m_layers.size() - 1; index > 0; index--) {
@@ -351,7 +352,7 @@ void Image::merge_visible_layers()
     while (index < m_layers.size()) {
         if (m_layers.at(index).is_visible()) {
             auto& bottom_layer = m_layers.at(index);
-            GUI::Painter painter(bottom_layer.bitmap());
+            GUI::Painter painter(bottom_layer.content_bitmap());
             paint_into(painter, { 0, 0, m_size.width(), m_size.height() });
             select_layer(&bottom_layer);
             index++;
@@ -380,8 +381,8 @@ void Image::merge_active_layer_up(Layer& layer)
     }
 
     auto& layer_above = m_layers.at(layer_index + 1);
-    GUI::Painter painter(layer_above.bitmap());
-    painter.draw_scaled_bitmap(rect(), layer.bitmap(), layer.rect(), (float)layer.opacity_percent() / 100.0f);
+    GUI::Painter painter(layer_above.content_bitmap());
+    painter.draw_scaled_bitmap(rect(), layer.display_bitmap(), layer.rect(), (float)layer.opacity_percent() / 100.0f);
     remove_layer(layer);
     select_layer(&layer_above);
 }
@@ -397,8 +398,8 @@ void Image::merge_active_layer_down(Layer& layer)
     }
 
     auto& layer_below = m_layers.at(layer_index - 1);
-    GUI::Painter painter(layer_below.bitmap());
-    painter.draw_scaled_bitmap(rect(), layer.bitmap(), layer.rect(), (float)layer.opacity_percent() / 100.0f);
+    GUI::Painter painter(layer_below.content_bitmap());
+    painter.draw_scaled_bitmap(rect(), layer.display_bitmap(), layer.rect(), (float)layer.opacity_percent() / 100.0f);
     remove_layer(layer);
     select_layer(&layer_below);
 }
@@ -473,9 +474,10 @@ void ImageUndoCommand::redo()
 void Image::flip(Gfx::Orientation orientation)
 {
     for (auto& layer : m_layers) {
-        auto flipped = layer.bitmap().flipped(orientation).release_value_but_fixme_should_propagate_errors();
-        layer.set_bitmap(*flipped);
+        auto flipped = layer.content_bitmap().flipped(orientation).release_value_but_fixme_should_propagate_errors();
+        layer.set_content_bitmap(*flipped);
         layer.did_modify_bitmap(rect());
+        // FIXME: Respect mask
     }
 
     did_change();
@@ -484,9 +486,10 @@ void Image::flip(Gfx::Orientation orientation)
 void Image::rotate(Gfx::RotationDirection direction)
 {
     for (auto& layer : m_layers) {
-        auto rotated = layer.bitmap().rotated(direction).release_value_but_fixme_should_propagate_errors();
-        layer.set_bitmap(*rotated);
+        auto rotated = layer.content_bitmap().rotated(direction).release_value_but_fixme_should_propagate_errors();
+        layer.set_content_bitmap(*rotated);
         layer.did_modify_bitmap(rect());
+        // FIXME: Respect mask
     }
 
     m_size = { m_size.height(), m_size.width() };
@@ -496,9 +499,10 @@ void Image::rotate(Gfx::RotationDirection direction)
 void Image::crop(Gfx::IntRect const& cropped_rect)
 {
     for (auto& layer : m_layers) {
-        auto cropped = layer.bitmap().cropped(cropped_rect).release_value_but_fixme_should_propagate_errors();
-        layer.set_bitmap(*cropped);
+        auto cropped = layer.content_bitmap().cropped(cropped_rect).release_value_but_fixme_should_propagate_errors();
+        layer.set_content_bitmap(*cropped);
         layer.did_modify_bitmap(rect());
+        // FIXME: Respect mask
     }
 
     m_size = { cropped_rect.width(), cropped_rect.height() };
@@ -512,7 +516,7 @@ Color Image::color_at(Gfx::IntPoint const& point) const
         if (!layer.is_visible() || !layer.rect().contains(point))
             continue;
 
-        auto layer_color = layer.bitmap().get_pixel(point);
+        auto layer_color = layer.display_bitmap().get_pixel(point);
         float layer_opacity = layer.opacity_percent() / 100.0f;
         layer_color.set_alpha((u8)(layer_color.alpha() * layer_opacity));
         color = color.blend(layer_color);

+ 25 - 5
Userland/Applications/PixelPaint/Layer.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -37,7 +38,7 @@ ErrorOr<NonnullRefPtr<Layer>> Layer::try_create_with_bitmap(Image& image, Nonnul
 
 ErrorOr<NonnullRefPtr<Layer>> Layer::try_create_snapshot(Image& image, Layer const& layer)
 {
-    auto bitmap = TRY(layer.bitmap().clone());
+    auto bitmap = TRY(layer.content_bitmap().clone());
     auto snapshot = TRY(try_create_with_bitmap(image, move(bitmap), layer.name()));
 
     /*
@@ -57,13 +58,15 @@ ErrorOr<NonnullRefPtr<Layer>> Layer::try_create_snapshot(Image& image, Layer con
 Layer::Layer(Image& image, NonnullRefPtr<Gfx::Bitmap> bitmap, String name)
     : m_image(image)
     , m_name(move(name))
-    , m_bitmap(move(bitmap))
+    , m_content_bitmap(move(bitmap))
+    , m_cached_display_bitmap(m_content_bitmap)
 {
 }
 
 void Layer::did_modify_bitmap(Gfx::IntRect const& rect)
 {
     m_image.layer_did_modify_bitmap({}, *this, rect);
+    update_cached_bitmap();
 }
 
 void Layer::set_visible(bool visible)
@@ -110,12 +113,12 @@ RefPtr<Gfx::Bitmap> Layer::try_copy_bitmap(Selection const& selection) const
             auto layer_point = image_point - m_location;
             auto result_point = image_point - selection_rect.top_left();
 
-            if (!m_bitmap->physical_rect().contains(layer_point)) {
+            if (!m_content_bitmap->physical_rect().contains(layer_point)) {
                 result->set_pixel(result_point, Gfx::Color::Transparent);
                 continue;
             }
 
-            auto pixel = m_bitmap->get_pixel(layer_point);
+            auto pixel = m_content_bitmap->get_pixel(layer_point);
 
             // Widen to int before multiplying to avoid overflow issues
             auto pixel_alpha = static_cast<int>(pixel.alpha());
@@ -132,11 +135,28 @@ RefPtr<Gfx::Bitmap> Layer::try_copy_bitmap(Selection const& selection) const
 
 void Layer::erase_selection(Selection const& selection)
 {
-    Gfx::Painter painter { bitmap() };
+    Gfx::Painter painter { content_bitmap() };
     auto const image_and_selection_intersection = m_image.rect().intersected(selection.bounding_rect());
     auto const translated_to_layer_space = image_and_selection_intersection.translated(-location());
     painter.clear_rect(translated_to_layer_space, Color::Transparent);
     did_modify_bitmap(translated_to_layer_space);
 }
 
+void Layer::set_content_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap)
+{
+    m_content_bitmap = move(bitmap);
+    update_cached_bitmap();
+}
+
+void Layer::update_cached_bitmap()
+{
+    if (!is_masked()) {
+        if (m_content_bitmap.ptr() == m_cached_display_bitmap.ptr())
+            return;
+
+        m_cached_display_bitmap = m_content_bitmap;
+        return;
+    }
+}
+
 }

+ 11 - 5
Userland/Applications/PixelPaint/Layer.h

@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2022, the SerenityOS developers.
+ * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -35,9 +36,11 @@ public:
     Gfx::IntPoint const& location() const { return m_location; }
     void set_location(Gfx::IntPoint const& location) { m_location = location; }
 
-    Gfx::Bitmap const& bitmap() const { return *m_bitmap; }
-    Gfx::Bitmap& bitmap() { return *m_bitmap; }
-    Gfx::IntSize size() const { return bitmap().size(); }
+    Gfx::Bitmap const& display_bitmap() const { return m_cached_display_bitmap; }
+    Gfx::Bitmap const& content_bitmap() const { return m_content_bitmap; }
+    Gfx::Bitmap& content_bitmap() { return m_content_bitmap; }
+
+    Gfx::IntSize size() const { return content_bitmap().size(); }
 
     Gfx::IntRect relative_rect() const { return { location(), size() }; }
     Gfx::IntRect rect() const { return { {}, size() }; }
@@ -45,7 +48,7 @@ public:
     String const& name() const { return m_name; }
     void set_name(String);
 
-    void set_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap) { m_bitmap = move(bitmap); }
+    void set_content_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap);
 
     void did_modify_bitmap(Gfx::IntRect const& = {});
 
@@ -71,12 +74,15 @@ private:
 
     String m_name;
     Gfx::IntPoint m_location;
-    NonnullRefPtr<Gfx::Bitmap> m_bitmap;
+    NonnullRefPtr<Gfx::Bitmap> m_content_bitmap;
+    NonnullRefPtr<Gfx::Bitmap> m_cached_display_bitmap;
 
     bool m_selected { false };
     bool m_visible { true };
 
     int m_opacity_percent { 100 };
+
+    void update_cached_bitmap();
 };
 
 }

+ 2 - 1
Userland/Applications/PixelPaint/LayerListWidget.cpp

@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
+ * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -128,7 +129,7 @@ void LayerListWidget::paint_event(GUI::PaintEvent& event)
         }
 
         painter.draw_rect(adjusted_rect, palette().color(ColorRole::BaseText));
-        painter.draw_scaled_bitmap(inner_thumbnail_rect, layer.bitmap(), layer.bitmap().rect());
+        painter.draw_scaled_bitmap(inner_thumbnail_rect, layer.display_bitmap(), layer.display_bitmap().rect());
 
         if (layer.is_visible()) {
             painter.draw_text(text_rect, layer.name(), Gfx::TextAlignment::CenterLeft, layer.is_selected() ? palette().selection_text() : palette().button_text());

+ 3 - 3
Userland/Applications/PixelPaint/MainWidget.cpp

@@ -114,7 +114,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
                 auto image = PixelPaint::Image::try_create_with_size(dialog->image_size()).release_value_but_fixme_should_propagate_errors();
                 auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background").release_value_but_fixme_should_propagate_errors();
                 image->add_layer(*bg_layer);
-                bg_layer->bitmap().fill(Color::White);
+                bg_layer->content_bitmap().fill(Color::White);
 
                 auto& editor = create_new_editor(*image);
                 auto image_title = dialog->image_name().trim_whitespace();
@@ -630,7 +630,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
         if (auto* layer = 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);
+                filter.apply(layer->content_bitmap(), layer->rect(), layer->content_bitmap(), layer->rect(), *parameters);
                 layer->did_modify_bitmap(layer->rect());
                 editor->did_complete_action();
             }
@@ -735,7 +735,7 @@ void MainWidget::create_default_image()
 
     auto bg_layer = Layer::try_create_with_size(*image, image->size(), "Background").release_value_but_fixme_should_propagate_errors();
     image->add_layer(*bg_layer);
-    bg_layer->bitmap().fill(Color::White);
+    bg_layer->content_bitmap().fill(Color::White);
 
     m_layer_list_widget->set_image(image);
 

+ 3 - 3
Userland/Applications/PixelPaint/Tools/BrushTool.cpp

@@ -29,7 +29,7 @@ void BrushTool::on_mousedown(Layer* layer, MouseEvent& event)
 
     // Shift+Click draws a line from the last position to current one.
     if (layer_event.shift() && m_has_clicked) {
-        draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position());
+        draw_line(layer->content_bitmap(), color_for(layer_event), m_last_position, layer_event.position());
         auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2);
         layer->did_modify_bitmap(modified_rect);
         m_last_position = layer_event.position();
@@ -39,7 +39,7 @@ void BrushTool::on_mousedown(Layer* layer, MouseEvent& event)
     const int first_draw_opacity = 10;
 
     for (int i = 0; i < first_draw_opacity; ++i)
-        draw_point(layer->bitmap(), color_for(layer_event), layer_event.position());
+        draw_point(layer->content_bitmap(), color_for(layer_event), layer_event.position());
 
     layer->did_modify_bitmap(Gfx::IntRect::centered_on(layer_event.position(), Gfx::IntSize { m_size * 2, m_size * 2 }));
     m_last_position = layer_event.position();
@@ -55,7 +55,7 @@ void BrushTool::on_mousemove(Layer* layer, MouseEvent& event)
     if (!(layer_event.buttons() & GUI::MouseButton::Primary || layer_event.buttons() & GUI::MouseButton::Secondary))
         return;
 
-    draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position());
+    draw_line(layer->content_bitmap(), color_for(layer_event), m_last_position, layer_event.position());
 
     auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2);
 

+ 3 - 3
Userland/Applications/PixelPaint/Tools/BucketTool.cpp

@@ -82,10 +82,10 @@ void BucketTool::on_mousedown(Layer* layer, MouseEvent& event)
     if (!layer->rect().contains(layer_event.position()))
         return;
 
-    GUI::Painter painter(layer->bitmap());
-    auto target_color = layer->bitmap().get_pixel(layer_event.x(), layer_event.y());
+    GUI::Painter painter(layer->content_bitmap());
+    auto target_color = layer->content_bitmap().get_pixel(layer_event.x(), layer_event.y());
 
-    flood_fill(layer->bitmap(), layer_event.position(), target_color, m_editor->color_for(layer_event), m_threshold);
+    flood_fill(layer->content_bitmap(), layer_event.position(), target_color, m_editor->color_for(layer_event), m_threshold);
 
     layer->did_modify_bitmap();
     m_editor->did_complete_action();

+ 1 - 1
Userland/Applications/PixelPaint/Tools/EllipseTool.cpp

@@ -67,7 +67,7 @@ void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event)
         return;
 
     if (event.layer_event().button() == m_drawing_button) {
-        GUI::Painter painter(layer->bitmap());
+        GUI::Painter painter(layer->content_bitmap());
         draw_using(painter, m_ellipse_start_position, m_ellipse_end_position, m_thickness);
         m_drawing_button = GUI::MouseButton::None;
         layer->did_modify_bitmap();

+ 1 - 1
Userland/Applications/PixelPaint/Tools/LineTool.cpp

@@ -60,7 +60,7 @@ void LineTool::on_mouseup(Layer* layer, MouseEvent& event)
 
     auto& layer_event = event.layer_event();
     if (layer_event.button() == m_drawing_button) {
-        GUI::Painter painter(layer->bitmap());
+        GUI::Painter painter(layer->content_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();

+ 1 - 1
Userland/Applications/PixelPaint/Tools/PickerTool.cpp

@@ -24,7 +24,7 @@ void PickerTool::on_mousedown(Layer* layer, MouseEvent& event)
     } else {
         if (!layer || !layer->rect().contains(position))
             return;
-        color = layer->bitmap().get_pixel(position);
+        color = layer->content_bitmap().get_pixel(position);
     }
 
     // We picked a transparent pixel, do nothing.

+ 1 - 1
Userland/Applications/PixelPaint/Tools/RectangleTool.cpp

@@ -70,7 +70,7 @@ void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event)
         return;
 
     if (event.layer_event().button() == m_drawing_button) {
-        GUI::Painter painter(layer->bitmap());
+        GUI::Painter painter(layer->content_bitmap());
         draw_using(painter, m_rectangle_start_position, m_rectangle_end_position, m_thickness);
         m_drawing_button = GUI::MouseButton::None;
         layer->did_modify_bitmap();

+ 1 - 1
Userland/Applications/PixelPaint/Tools/SprayTool.cpp

@@ -40,7 +40,7 @@ void SprayTool::paint_it()
     if (!layer)
         return;
 
-    auto& bitmap = layer->bitmap();
+    auto& bitmap = layer->content_bitmap();
     GUI::Painter painter(bitmap);
     VERIFY(bitmap.bpp() == 32);
     const double minimal_radius = 2;