mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:07:34 +00:00
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.
This commit is contained in:
parent
b6510f8e76
commit
31a9196bfe
14 changed files with 73 additions and 42 deletions
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue