1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 02:37:35 +00:00

PixelPaint: Editing mask optimization

This patch introduces a new function "Layer::editin_mask_bounding_rect"
that is used within the LevelsDialog, Luminosity and Colormasking to
process only the area where a mask was applied. Therefore we can
greatly reduce the amount of processed pixels if only a small portion
of the image was masked.
This commit is contained in:
Torstennator 2023-08-21 11:15:49 +02:00 committed by Sam Atkins
parent b3a6ccc45b
commit 28cda85f1f
6 changed files with 55 additions and 12 deletions

View file

@ -10,6 +10,7 @@
#include <LibGUI/Button.h> #include <LibGUI/Button.h>
#include <LibGUI/CheckBox.h> #include <LibGUI/CheckBox.h>
#include <LibGUI/Label.h> #include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Painter.h> #include <LibGUI/Painter.h>
#include <LibGUI/RangeSlider.h> #include <LibGUI/RangeSlider.h>
#include <LibGfx/AntiAliasingPainter.h> #include <LibGfx/AntiAliasingPainter.h>
@ -159,7 +160,7 @@ void ImageMasking::generate_new_mask()
{ {
ensure_reference_mask().release_value_but_fixme_should_propagate_errors(); ensure_reference_mask().release_value_but_fixme_should_propagate_errors();
if (m_reference_mask.is_null()) if (m_reference_mask.is_null() || !m_masked_area.has_value())
return; return;
if (m_masking_type == MaskingType::Luminosity) { if (m_masking_type == MaskingType::Luminosity) {
@ -172,8 +173,8 @@ void ImageMasking::generate_new_mask()
bool has_end_range = max_luminosity_end != max_luminosity_full; bool has_end_range = max_luminosity_end != max_luminosity_full;
Gfx::Color reference_mask_pixel, content_pixel; Gfx::Color reference_mask_pixel, content_pixel;
for (int y = 0; y < m_reference_mask->height(); y++) { for (int y = m_masked_area->top(); y < m_masked_area->bottom(); y++) {
for (int x = 0; x < m_reference_mask->width(); x++) { for (int x = m_masked_area->left(); x < m_masked_area->right(); x++) {
reference_mask_pixel = m_reference_mask->get_pixel(x, y); reference_mask_pixel = m_reference_mask->get_pixel(x, y);
if (!reference_mask_pixel.alpha()) if (!reference_mask_pixel.alpha())
continue; continue;
@ -225,13 +226,13 @@ void ImageMasking::generate_new_mask()
Gfx::Color reference_mask_pixel; Gfx::Color reference_mask_pixel;
Gfx::HSV content_pixel_hsv; Gfx::HSV content_pixel_hsv;
for (int y = 0; y < m_reference_mask->height(); y++) { for (int y = m_masked_area->top(); y <= m_masked_area->bottom(); y++) {
auto reference_scanline = m_reference_mask->scanline(y); auto reference_scanline = m_reference_mask->scanline(y);
auto content_scanline = m_editor->active_layer()->content_bitmap().scanline(y); auto content_scanline = m_editor->active_layer()->content_bitmap().scanline(y);
auto mask_scanline = m_editor->active_layer()->mask_bitmap()->scanline(y); auto mask_scanline = m_editor->active_layer()->mask_bitmap()->scanline(y);
fast_u32_fill(mask_scanline, 0, m_reference_mask->physical_width()); fast_u32_fill(mask_scanline, 0, m_reference_mask->physical_width());
for (int x = 0; x < m_reference_mask->width(); x++) { for (int x = m_masked_area->left(); x < m_masked_area->right(); x++) {
reference_mask_pixel = Color::from_argb(reference_scanline[x]); reference_mask_pixel = Color::from_argb(reference_scanline[x]);
if (!reference_mask_pixel.alpha()) if (!reference_mask_pixel.alpha())
continue; continue;
@ -268,9 +269,12 @@ void ImageMasking::generate_new_mask()
ErrorOr<void> ImageMasking::ensure_reference_mask() ErrorOr<void> ImageMasking::ensure_reference_mask()
{ {
if (m_reference_mask.is_null()) if (m_reference_mask.is_null()) {
m_reference_mask = TRY(m_editor->active_layer()->mask_bitmap()->clone()); m_reference_mask = TRY(m_editor->active_layer()->mask_bitmap()->clone());
m_masked_area = m_editor->active_layer()->editing_mask_bounding_rect();
if (!m_masked_area.has_value())
GUI::MessageBox::show(this, "You have to draw a mask first before you can refine the mask details."sv, "Missing mask content"sv, GUI::MessageBox::Type::Information);
}
return {}; return {};
} }

View file

@ -37,6 +37,7 @@ private:
ImageEditor* m_editor { nullptr }; ImageEditor* m_editor { nullptr };
RefPtr<Gfx::Bitmap> m_reference_mask { nullptr }; RefPtr<Gfx::Bitmap> m_reference_mask { nullptr };
bool m_did_change = false; bool m_did_change = false;
Optional<Gfx::IntRect> m_masked_area;
RefPtr<GUI::RangeSlider> m_full_masking_slider = { nullptr }; RefPtr<GUI::RangeSlider> m_full_masking_slider = { nullptr };
RefPtr<GUI::RangeSlider> m_edge_masking_slider = { nullptr }; RefPtr<GUI::RangeSlider> m_edge_masking_slider = { nullptr };

View file

@ -453,6 +453,39 @@ Optional<Gfx::IntRect> Layer::nonempty_content_bounding_rect() const
}; };
} }
Optional<Gfx::IntRect> Layer::editing_mask_bounding_rect() const
{
if (mask_type() != MaskType::EditingMask)
return {};
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_mask_bitmap->height(); ++y) {
auto scanline = m_mask_bitmap->scanline(y);
for (int x = 0; x < m_mask_bitmap->width(); ++x) {
// Do we have any alpha values?
if (scanline[x] < 0x01000000)
continue;
min_content_x = min(min_content_x.value_or(x), x);
min_content_y = min(min_content_y.value_or(y), y);
max_content_x = max(max_content_x.value_or(x), x);
max_content_y = max(max_content_y.value_or(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
};
}
ErrorOr<NonnullRefPtr<Layer>> Layer::duplicate(DeprecatedString name) ErrorOr<NonnullRefPtr<Layer>> Layer::duplicate(DeprecatedString name)
{ {
auto duplicated_layer = TRY(Layer::create_snapshot(m_image, *this)); auto duplicated_layer = TRY(Layer::create_snapshot(m_image, *this));
@ -461,7 +494,7 @@ ErrorOr<NonnullRefPtr<Layer>> Layer::duplicate(DeprecatedString name)
return duplicated_layer; return duplicated_layer;
} }
Layer::MaskType Layer::mask_type() Layer::MaskType Layer::mask_type() const
{ {
if (m_mask_bitmap.is_null()) if (m_mask_bitmap.is_null())
return MaskType::None; return MaskType::None;

View file

@ -82,6 +82,7 @@ public:
ErrorOr<void> scale(Gfx::IntRect const& new_rect, Gfx::Painter::ScalingMode scaling_mode, NotifyClients notify_clients = NotifyClients::Yes); ErrorOr<void> scale(Gfx::IntRect const& new_rect, Gfx::Painter::ScalingMode scaling_mode, NotifyClients notify_clients = NotifyClients::Yes);
Optional<Gfx::IntRect> nonempty_content_bounding_rect() const; Optional<Gfx::IntRect> nonempty_content_bounding_rect() const;
Optional<Gfx::IntRect> editing_mask_bounding_rect() const;
ErrorOr<void> set_bitmaps(NonnullRefPtr<Gfx::Bitmap> content, RefPtr<Gfx::Bitmap> mask); ErrorOr<void> set_bitmaps(NonnullRefPtr<Gfx::Bitmap> content, RefPtr<Gfx::Bitmap> mask);
@ -103,7 +104,7 @@ public:
void erase_selection(Selection const&); void erase_selection(Selection const&);
bool is_masked() const { return !m_mask_bitmap.is_null(); } bool is_masked() const { return !m_mask_bitmap.is_null(); }
MaskType mask_type(); MaskType mask_type() const;
enum class EditMode { enum class EditMode {
Content, Content,

View file

@ -89,9 +89,10 @@ void LevelsDialog::generate_new_image()
Color new_pixel_color; Color new_pixel_color;
Gfx::StorageFormat storage_format = Gfx::determine_storage_format(m_editor->active_layer()->content_bitmap().format()); Gfx::StorageFormat storage_format = Gfx::determine_storage_format(m_editor->active_layer()->content_bitmap().format());
auto apply_only_on_mask = m_editor->active_layer()->mask_type() == Layer::MaskType::EditingMask; auto apply_only_on_mask = m_editor->active_layer()->mask_type() == Layer::MaskType::EditingMask;
auto relevant_area = m_masked_area.value_or({ 0, 0, m_reference_bitmap->width(), m_reference_bitmap->height() });
for (int x = 0; x < m_reference_bitmap->width(); x++) { for (int y = relevant_area.top(); y < relevant_area.bottom(); y++) {
for (int y = 0; y < m_reference_bitmap->height(); y++) { for (int x = relevant_area.left(); x < relevant_area.right(); x++) {
current_pixel_color = m_reference_bitmap->get_pixel(x, y); current_pixel_color = m_reference_bitmap->get_pixel(x, y);
// Check if we can avoid setting pixels as nothing will change when we don't have a mask at x,y. // Check if we can avoid setting pixels as nothing will change when we don't have a mask at x,y.
@ -123,8 +124,10 @@ void LevelsDialog::generate_new_image()
ErrorOr<void> LevelsDialog::ensure_reference_bitmap() ErrorOr<void> LevelsDialog::ensure_reference_bitmap()
{ {
if (m_reference_bitmap.is_null()) if (m_reference_bitmap.is_null()) {
m_reference_bitmap = TRY(m_editor->active_layer()->content_bitmap().clone()); m_reference_bitmap = TRY(m_editor->active_layer()->content_bitmap().clone());
m_masked_area = m_editor->active_layer()->editing_mask_bounding_rect();
}
return {}; return {};
} }

View file

@ -28,6 +28,7 @@ private:
RefPtr<GUI::ValueSlider> m_gamma_slider = { nullptr }; RefPtr<GUI::ValueSlider> m_gamma_slider = { nullptr };
bool m_did_change = false; bool m_did_change = false;
int m_precomputed_color_correction[256]; int m_precomputed_color_correction[256];
Optional<Gfx::IntRect> m_masked_area;
ErrorOr<void> ensure_reference_bitmap(); ErrorOr<void> ensure_reference_bitmap();
void generate_new_image(); void generate_new_image();