1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 05:05:08 +00:00
serenity/Userland/Applications/PixelPaint/Selection.cpp
Marcus Nilsson 854e16797e PixelPaint: Tighten update rects for Tools
This tightens the update rects for EraseTool, BrushTool And the marching
ants update in Selection. Inflate Selection update rect by 10x10 to
avoid misalignment when zoomed out.
2021-08-08 13:55:39 +02:00

141 lines
4.3 KiB
C++

/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Selection.h"
#include "ImageEditor.h"
#include <LibGfx/Painter.h>
namespace PixelPaint {
constexpr int marching_ant_length = 4;
void Selection::paint(Gfx::Painter& painter)
{
draw_marching_ants(painter, m_mask);
}
Selection::Selection(ImageEditor& 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_editor.image_rect_to_editor_rect(bounding_rect().inflated(10, 10)).to_type<int>());
});
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.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_mask = {};
m_editor.update();
}
void Selection::merge(Mask const& mask, MergeMode mode)
{
switch (mode) {
case MergeMode::Set:
m_mask = mask;
break;
case MergeMode::Add:
m_mask.add(mask);
break;
case MergeMode::Subtract:
m_mask.subtract(mask);
break;
case MergeMode::Intersect:
m_mask.intersect(mask);
break;
default:
VERIFY_NOT_REACHED();
}
}
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);
}
}
}