mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:17:34 +00:00
PixelPaint: Move "marching ants" painting logic to ImageEditor
Since this code needs to look at a bunch of ImageEditor state anyway, it makes more sense for it to live in ImageEditor.
This commit is contained in:
parent
e6d860f2fe
commit
67596d9546
5 changed files with 116 additions and 108 deletions
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
namespace PixelPaint {
|
namespace PixelPaint {
|
||||||
|
|
||||||
|
constexpr int marching_ant_length = 4;
|
||||||
|
|
||||||
ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
||||||
: m_image(move(image))
|
: m_image(move(image))
|
||||||
, m_title("Untitled")
|
, m_title("Untitled")
|
||||||
|
@ -41,6 +43,14 @@ ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
||||||
|
|
||||||
m_show_rulers = Config::read_bool("PixelPaint"sv, "Rulers"sv, "Show"sv, true);
|
m_show_rulers = Config::read_bool("PixelPaint"sv, "Rulers"sv, "Show"sv, true);
|
||||||
m_show_guides = Config::read_bool("PixelPaint"sv, "Guides"sv, "Show"sv, true);
|
m_show_guides = Config::read_bool("PixelPaint"sv, "Guides"sv, "Show"sv, true);
|
||||||
|
|
||||||
|
m_marching_ants_timer = Core::Timer::create_repeating(80, [this] {
|
||||||
|
++m_marching_ants_offset;
|
||||||
|
m_marching_ants_offset %= (marching_ant_length * 2);
|
||||||
|
if (!m_selection.is_empty() || m_selection.in_interactive_selection())
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
m_marching_ants_timer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageEditor::~ImageEditor()
|
ImageEditor::~ImageEditor()
|
||||||
|
@ -158,8 +168,7 @@ void ImageEditor::paint_event(GUI::PaintEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_selection.is_empty())
|
paint_selection(painter);
|
||||||
m_selection.paint(painter);
|
|
||||||
|
|
||||||
if (m_show_rulers) {
|
if (m_show_rulers) {
|
||||||
auto const ruler_bg_color = palette().color(Gfx::ColorRole::InactiveSelection);
|
auto const ruler_bg_color = palette().color(Gfx::ColorRole::InactiveSelection);
|
||||||
|
@ -651,4 +660,95 @@ void ImageEditor::set_loaded_from_image(bool loaded_from_image)
|
||||||
m_loaded_from_image = loaded_from_image;
|
m_loaded_from_image = loaded_from_image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageEditor::paint_selection(Gfx::Painter& painter)
|
||||||
|
{
|
||||||
|
if (m_selection.is_empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
draw_marching_ants(painter, m_selection.mask());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const
|
||||||
|
{
|
||||||
|
// Top line
|
||||||
|
for (int x = rect.left(); x <= rect.right(); ++x)
|
||||||
|
draw_marching_ants_pixel(painter, x, rect.top());
|
||||||
|
|
||||||
|
// Right line
|
||||||
|
for (int y = rect.top() + 1; y <= rect.bottom(); ++y)
|
||||||
|
draw_marching_ants_pixel(painter, rect.right(), y);
|
||||||
|
|
||||||
|
// Bottom line
|
||||||
|
for (int x = rect.right() - 1; x >= rect.left(); --x)
|
||||||
|
draw_marching_ants_pixel(painter, x, rect.bottom());
|
||||||
|
|
||||||
|
// Left line
|
||||||
|
for (int y = rect.bottom() - 1; y > rect.top(); --y)
|
||||||
|
draw_marching_ants_pixel(painter, rect.left(), y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageEditor::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 / scale()));
|
||||||
|
|
||||||
|
// Only check the visible selection area when drawing for performance
|
||||||
|
auto rect = this->rect();
|
||||||
|
rect = Gfx::enclosing_int_rect(frame_to_content_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 = content_to_frame_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 = content_to_frame_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 ImageEditor::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,9 @@ public:
|
||||||
|
|
||||||
bool is_modified();
|
bool is_modified();
|
||||||
|
|
||||||
|
void draw_marching_ants(Gfx::Painter&, Gfx::IntRect const&) const;
|
||||||
|
void draw_marching_ants(Gfx::Painter&, Mask const&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ImageEditor(NonnullRefPtr<Image>);
|
explicit ImageEditor(NonnullRefPtr<Image>);
|
||||||
|
|
||||||
|
@ -140,6 +143,8 @@ private:
|
||||||
Gfx::IntRect mouse_indicator_rect_x() const;
|
Gfx::IntRect mouse_indicator_rect_x() const;
|
||||||
Gfx::IntRect mouse_indicator_rect_y() const;
|
Gfx::IntRect mouse_indicator_rect_y() const;
|
||||||
|
|
||||||
|
void paint_selection(Gfx::Painter&);
|
||||||
|
|
||||||
NonnullRefPtr<Image> m_image;
|
NonnullRefPtr<Image> m_image;
|
||||||
RefPtr<Layer> m_active_layer;
|
RefPtr<Layer> m_active_layer;
|
||||||
GUI::UndoStack m_undo_stack;
|
GUI::UndoStack m_undo_stack;
|
||||||
|
@ -171,6 +176,11 @@ private:
|
||||||
Selection m_selection;
|
Selection m_selection;
|
||||||
|
|
||||||
bool m_loaded_from_image { true };
|
bool m_loaded_from_image { true };
|
||||||
|
|
||||||
|
RefPtr<Core::Timer> m_marching_ants_timer;
|
||||||
|
int m_marching_ants_offset { 0 };
|
||||||
|
|
||||||
|
void draw_marching_ants_pixel(Gfx::Painter&, int x, int y) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,95 +10,9 @@
|
||||||
|
|
||||||
namespace PixelPaint {
|
namespace PixelPaint {
|
||||||
|
|
||||||
constexpr int marching_ant_length = 4;
|
|
||||||
|
|
||||||
void Selection::paint(Gfx::Painter& painter)
|
|
||||||
{
|
|
||||||
draw_marching_ants(painter, m_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Selection(ImageEditor& editor)
|
Selection::Selection(ImageEditor& editor)
|
||||||
: m_editor(editor)
|
: m_editor(editor)
|
||||||
{
|
{
|
||||||
m_marching_ants_timer = Core::Timer::create_repeating(80, [this] {
|
|
||||||
++m_marching_ants_offset;
|
|
||||||
m_marching_ants_offset %= (marching_ant_length * 2);
|
|
||||||
if (!is_empty() || m_in_interactive_selection)
|
|
||||||
m_editor.update();
|
|
||||||
});
|
|
||||||
m_marching_ants_timer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Selection::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const
|
|
||||||
{
|
|
||||||
// Top line
|
|
||||||
for (int x = rect.left(); x <= rect.right(); ++x)
|
|
||||||
draw_marching_ants_pixel(painter, x, rect.top());
|
|
||||||
|
|
||||||
// Right line
|
|
||||||
for (int y = rect.top() + 1; y <= rect.bottom(); ++y)
|
|
||||||
draw_marching_ants_pixel(painter, rect.right(), y);
|
|
||||||
|
|
||||||
// Bottom line
|
|
||||||
for (int x = rect.right() - 1; x >= rect.left(); --x)
|
|
||||||
draw_marching_ants_pixel(painter, x, rect.bottom());
|
|
||||||
|
|
||||||
// Left line
|
|
||||||
for (int y = rect.bottom() - 1; y > rect.top(); --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.frame_to_content_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.content_to_frame_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.content_to_frame_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()
|
void Selection::clear()
|
||||||
|
@ -127,15 +41,4 @@ void Selection::merge(Mask const& mask, MergeMode mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,22 +40,17 @@ public:
|
||||||
[[nodiscard]] u8 get_selection_alpha(int x, int y) const { return m_mask.get(x, y); }
|
[[nodiscard]] u8 get_selection_alpha(int x, int y) const { return m_mask.get(x, y); }
|
||||||
[[nodiscard]] u8 get_selection_alpha(Gfx::IntPoint const& point) const { return get_selection_alpha(point.x(), point.y()); }
|
[[nodiscard]] u8 get_selection_alpha(Gfx::IntPoint const& point) const { return get_selection_alpha(point.x(), point.y()); }
|
||||||
|
|
||||||
void paint(Gfx::Painter&);
|
Mask const& mask() const { return m_mask; }
|
||||||
|
|
||||||
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 begin_interactive_selection() { m_in_interactive_selection = true; }
|
||||||
void end_interactive_selection() { m_in_interactive_selection = false; }
|
void end_interactive_selection() { m_in_interactive_selection = false; }
|
||||||
|
|
||||||
|
bool in_interactive_selection() { return m_in_interactive_selection; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ImageEditor& m_editor;
|
ImageEditor& m_editor;
|
||||||
Mask m_mask;
|
Mask m_mask;
|
||||||
RefPtr<Core::Timer> m_marching_ants_timer;
|
|
||||||
int m_marching_ants_offset { 0 };
|
|
||||||
bool m_in_interactive_selection { false };
|
bool m_in_interactive_selection { false };
|
||||||
|
|
||||||
void draw_marching_ants_pixel(Gfx::Painter&, int x, int y) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ void RectangleSelectTool::on_second_paint(Layer const*, GUI::PaintEvent& event)
|
||||||
auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end);
|
auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end);
|
||||||
auto rect_in_editor = m_editor->content_to_frame_rect(rect_in_image);
|
auto rect_in_editor = m_editor->content_to_frame_rect(rect_in_image);
|
||||||
|
|
||||||
m_editor->selection().draw_marching_ants(painter, rect_in_editor.to_type<int>());
|
m_editor->draw_marching_ants(painter, rect_in_editor.to_rounded<int>());
|
||||||
}
|
}
|
||||||
|
|
||||||
GUI::Widget* RectangleSelectTool::get_properties_widget()
|
GUI::Widget* RectangleSelectTool::get_properties_widget()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue