PixelPaint: Use Mask internally in Selection

While the external API has not changed, this will allow us to have
non-rectangular selections in the future.
This commit is contained in:
Davipb 2021-06-20 10:48:43 -03:00 committed by Andreas Kling
parent 0828c75e57
commit d922e35579
Notes: sideshowbarker 2024-07-18 11:55:59 +09:00
3 changed files with 80 additions and 21 deletions

View file

@ -42,6 +42,7 @@ public:
Layer* layer_at_editor_position(Gfx::IntPoint const&);
float scale() const { return m_scale; }
void scale_centered_on_position(Gfx::IntPoint const&, float);
void reset_scale_and_position();
void scale_by(float);

View file

@ -14,7 +14,7 @@ constexpr int marching_ant_length = 4;
void Selection::paint(Gfx::Painter& painter)
{
draw_marching_ants(painter, m_editor.image_rect_to_editor_rect(m_rect).to_type<int>());
draw_marching_ants(painter, m_mask);
}
Selection::Selection(ImageEditor& editor)
@ -31,38 +31,91 @@ Selection::Selection(ImageEditor& editor)
void Selection::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const
{
int offset = m_marching_ants_offset;
auto draw_pixel = [&](int x, int y) {
if ((offset % (marching_ant_length * 2)) < marching_ant_length) {
painter.set_pixel(x, y, Color::Black);
} else {
painter.set_pixel(x, y, Color::White);
}
offset++;
};
// Top line
for (int x = rect.left(); x <= rect.right(); ++x)
draw_pixel(x, rect.top());
draw_marching_ants_pixel(painter, x, rect.top());
// Right line
for (int y = rect.top() + 1; y <= rect.bottom(); ++y)
draw_pixel(rect.right(), y);
draw_marching_ants_pixel(painter, rect.right(), y);
// Bottom line
for (int x = rect.right() - 1; x >= rect.left(); --x)
draw_pixel(x, rect.bottom());
draw_marching_ants_pixel(painter, x, rect.bottom());
// Left line
for (int y = rect.bottom() - 1; y > rect.top(); --y)
draw_pixel(rect.left(), y);
draw_marching_ants_pixel(painter, rect.left(), y);
}
void Selection::draw_marching_ants(Gfx::Painter& painter, Mask const& mask) const
{
// If the zoom is < 100%, we can skip pixels to save a lot of time drawing the ants
int step = max(1, (int)floorf(1.0f / m_editor.scale()));
// Only check the visible selection area when drawing for performance
auto rect = m_editor.rect();
rect = Gfx::enclosing_int_rect(m_editor.editor_rect_to_image_rect(rect));
rect.inflate(step * 2, step * 2); // prevent borders from having visible ants if the selection extends beyond it
// Scan the image horizontally to find vertical borders
for (int y = rect.top(); y <= rect.bottom(); y += step) {
bool previous_selected = false;
for (int x = rect.left(); x <= rect.right(); x += step) {
bool this_selected = mask.get(x, y) > 0;
if (this_selected != previous_selected) {
Gfx::IntRect image_pixel { x, y, 1, 1 };
auto pixel = m_editor.image_rect_to_editor_rect(image_pixel).to_type<int>();
auto end = max(pixel.top(), pixel.bottom()); // for when the zoom is < 100%
for (int pixel_y = pixel.top(); pixel_y <= end; pixel_y++) {
draw_marching_ants_pixel(painter, pixel.left(), pixel_y);
}
}
previous_selected = this_selected;
}
}
// Scan the image vertically to find horizontal borders
for (int x = rect.left(); x <= rect.right(); x += step) {
bool previous_selected = false;
for (int y = rect.top(); y <= rect.bottom(); y += step) {
bool this_selected = mask.get(x, y) > 0;
if (this_selected != previous_selected) {
Gfx::IntRect image_pixel { x, y, 1, 1 };
auto pixel = m_editor.image_rect_to_editor_rect(image_pixel).to_type<int>();
auto end = max(pixel.left(), pixel.right()); // for when the zoom is < 100%
for (int pixel_x = pixel.left(); pixel_x <= end; pixel_x++) {
draw_marching_ants_pixel(painter, pixel_x, pixel.top());
}
}
previous_selected = this_selected;
}
}
}
void Selection::clear()
{
m_rect = {};
m_mask = {};
m_editor.update();
}
void Selection::draw_marching_ants_pixel(Gfx::Painter& painter, int x, int y) const
{
int pattern_index = x + y + m_marching_ants_offset;
if (pattern_index % (marching_ant_length * 2) < marching_ant_length) {
painter.set_pixel(x, y, Color::Black);
} else {
painter.set_pixel(x, y, Color::White);
}
}
}

View file

@ -9,6 +9,8 @@
#include <LibCore/Timer.h>
#include <LibGfx/Rect.h>
#include "Mask.h"
namespace PixelPaint {
class ImageEditor;
@ -18,24 +20,27 @@ class Selection {
public:
explicit Selection(ImageEditor&);
bool is_empty() const { return m_rect.is_empty(); }
bool is_empty() const { return m_mask.is_null(); }
void clear();
void set(Gfx::IntRect const& rect) { m_rect = rect; }
Gfx::IntRect bounding_rect() const { return m_rect; }
void set(Gfx::IntRect const& rect) { m_mask = Mask::full(rect); }
Gfx::IntRect bounding_rect() const { return m_mask.bounding_rect(); }
void paint(Gfx::Painter&);
void draw_marching_ants(Gfx::Painter&, Gfx::IntRect const&) const;
void draw_marching_ants(Gfx::Painter&, Mask const&) const;
void begin_interactive_selection() { m_in_interactive_selection = true; }
void end_interactive_selection() { m_in_interactive_selection = false; }
private:
ImageEditor& m_editor;
Gfx::IntRect m_rect;
Mask m_mask;
RefPtr<Core::Timer> m_marching_ants_timer;
int m_marching_ants_offset { 0 };
bool m_in_interactive_selection { false };
void draw_marching_ants_pixel(Gfx::Painter&, int x, int y) const;
};
}