Skip to content

Commit

Permalink
Add kernel shape choice when altering selection
Browse files Browse the repository at this point in the history
Letting you pick between a round and a square kernel, the latter being
useful for pixel art in particular.
  • Loading branch information
askmeaboutlo0m committed Oct 3, 2024
1 parent 7084cbb commit 05e549f
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 67 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Unreleased Version 2.2.2-pre
* Fix: Save and export images according to the current view mode. Thanks incoheart for reporting.
* Server Fix: Don't get announcement refreshes stuck in an infinite loop. Thanks Meru for reporting.
* Fix: Make MyPaint brushes not ignore the first stroke made with them. This would also sometimes lead to a blank preview.
* Feature: Allow choosing between a round and square expansion/shrinking kernel when altering selections. Thanks Bigcheese and MorrowShore for suggesting.

2024-08-09 Version 2.2.2-beta.3
* Fix: Use more accurate timers for performance profiles if the platform supports it.
Expand Down
42 changes: 41 additions & 1 deletion src/desktop/dialogs/selectionalterdialog.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
extern "C" {
#include <dpengine/flood_fill.h>
}
#include "desktop/dialogs/selectionalterdialog.h"
#include "desktop/utils/widgetutils.h"
#include "desktop/widgets/groupedtoolbutton.h"
Expand Down Expand Up @@ -58,6 +61,36 @@ SelectionAlterDialog::SelectionAlterDialog(QWidget *parent)
QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), this,
&SelectionAlterDialog::updateControls);

expandLayout->addSpacing(3);

widgets::GroupedToolButton *roundButton =
new widgets::GroupedToolButton(widgets::GroupedToolButton::GroupLeft);
roundButton->setIcon(QIcon::fromTheme("drawpile_round"));
roundButton->setStatusTip(tr("Round expansion kernel"));
roundButton->setToolTip(roundButton->statusTip());
roundButton->setCheckable(true);
roundButton->setChecked(
getIntProperty(parent, PROP_KERNEL) !=
int(DP_FLOOD_FILL_KERNEL_SQUARE));
expandLayout->addWidget(roundButton);

widgets::GroupedToolButton *squareButton =
new widgets::GroupedToolButton(widgets::GroupedToolButton::GroupRight);
squareButton->setIcon(QIcon::fromTheme("drawpile_square"));
squareButton->setStatusTip(tr("Square expansion kernel"));
squareButton->setToolTip(squareButton->statusTip());
squareButton->setCheckable(true);
squareButton->setChecked(!roundButton->isChecked());
expandLayout->addWidget(squareButton);

m_kernelGroup = new QButtonGroup(this);
m_kernelGroup->addButton(roundButton, int(DP_FLOOD_FILL_KERNEL_GAUSSIAN));
m_kernelGroup->addButton(squareButton, int(DP_FLOOD_FILL_KERNEL_SQUARE));
connect(
m_kernelGroup,
QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), this,
&SelectionAlterDialog::updateControls);

layout->addLayout(expandLayout);

m_featherSpinner = new QSpinBox;
Expand Down Expand Up @@ -121,6 +154,10 @@ void SelectionAlterDialog::updateControls()
m_fromEdgeBox->setText(tr("Feather from canvas edge"));
}

for(QAbstractButton *kernelButton : m_kernelGroup->buttons()) {
kernelButton->setEnabled(haveExpand);
}

QPushButton *okButton = m_buttons->button(QDialogButtonBox::Ok);
if(okButton) {
okButton->setEnabled(haveExpand || haveFeather);
Expand All @@ -131,16 +168,19 @@ void SelectionAlterDialog::emitAlterSelectionRequested()
{
bool shrink = m_expandGroup->checkedId() != 0;
int expand = m_expandSpinner->value();
int kernel = m_kernelGroup->checkedId();
int feather = m_featherSpinner->value();
bool fromEdge = m_fromEdgeBox->isChecked();
QWidget *pw = parentWidget();
if(pw) {
pw->setProperty(PROP_SHRINK, shrink);
pw->setProperty(PROP_EXPAND, expand);
pw->setProperty(PROP_KERNEL, kernel);
pw->setProperty(PROP_FEATHER, feather);
pw->setProperty(PROP_FROM_EDGE, fromEdge);
}
emit alterSelectionRequested(expand * (shrink ? -1 : 1), feather, fromEdge);
emit alterSelectionRequested(
expand * (shrink ? -1 : 1), kernel, feather, fromEdge);
}

}
5 changes: 4 additions & 1 deletion src/desktop/dialogs/selectionalterdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ class SelectionAlterDialog final : public QDialog {
SelectionAlterDialog(QWidget *parent = nullptr);

signals:
void alterSelectionRequested(int expand, int feather, bool fromEdge);
void
alterSelectionRequested(int expand, int kernel, int feather, bool fromEdge);

private:
static constexpr char PROP_SHRINK[] = "SelectionAlterDialog_shrink";
static constexpr char PROP_EXPAND[] = "SelectionAlterDialog_expand";
static constexpr char PROP_KERNEL[] = "SelectionAlterDialog_kernel";
static constexpr char PROP_FEATHER[] = "SelectionAlterDialog_feather";
static constexpr char PROP_FROM_EDGE[] = "SelectionAlterDialog_fromEdge";

Expand All @@ -32,6 +34,7 @@ class SelectionAlterDialog final : public QDialog {

QButtonGroup *m_expandGroup;
QSpinBox *m_expandSpinner;
QButtonGroup *m_kernelGroup;
QSpinBox *m_featherSpinner;
QCheckBox *m_fromEdgeBox;
QDialogButtonBox *m_buttons;
Expand Down
6 changes: 4 additions & 2 deletions src/desktop/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3757,13 +3757,15 @@ void MainWindow::showAlterSelectionDialog()
}
}

void MainWindow::alterSelection(int expand, int feather, bool fromEdge)
void MainWindow::alterSelection(
int expand, int kernel, int feather, bool fromEdge)
{
canvas::CanvasModel *canvas = m_doc->canvas();
if(canvas) {
SelectionAlteration *sa = new SelectionAlteration(
canvas->paintEngine()->viewCanvasState(), m_doc->client()->myId(),
canvas::CanvasModel::MAIN_SELECTION_ID, expand, feather, fromEdge);
canvas::CanvasModel::MAIN_SELECTION_ID, expand, kernel, feather,
fromEdge);
sa->setAutoDelete(true);
connect(
sa, &SelectionAlteration::success, m_doc, &Document::selectMask);
Expand Down
2 changes: 1 addition & 1 deletion src/desktop/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ private slots:
void showBrushSettingsDialogPreset();

void showAlterSelectionDialog();
void alterSelection(int expand, int feather, bool fromEdge);
void alterSelection(int expand, int kernel, int feather, bool fromEdge);
void changeUndoDepthLimit();

void updateDevToolsActions();
Expand Down
95 changes: 68 additions & 27 deletions src/drawdance/libengine/dpengine/flood_fill.c
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,29 @@ static void apply_expansion_kernel(float *mask, int mask_width, float value,
}
}

static void apply_square_expansion(float *mask, int mask_width, float value,
int width, int height, int expand,
int feather_radius, int expand_min_x,
int expand_min_y, int x0, int y0)
{
int start_x0 = x0 - expand;
int start_y0 = y0 - expand;
int start_x = DP_max_int(start_x0, 0);
int start_y = DP_max_int(start_y0, 0);
int end_x = DP_min_int(x0 + expand, width - 1);
int end_y = DP_min_int(y0 + expand, height - 1);
for (int y = start_y; y <= end_y; ++y) {
for (int x = start_x; x <= end_x; ++x) {
int mx = x - expand_min_x + feather_radius;
int my = y - expand_min_y + feather_radius;
int index = my * mask_width + mx;
if (value == 1.0f || mask[index] < value) {
mask[index] = value;
}
}
}
}

static float erode_mask_pixel(const float *in_mask, int mask_width,
const unsigned char *kernel, int shrink,
int diameter, int x0, int y0)
Expand Down Expand Up @@ -593,8 +616,9 @@ static void feather_mask(DP_FillContext *c, float *mask, float *tmp, int width,

static float *make_mask(DP_FillContext *c,
float (*get_output)(void *, int, int), int expand,
int feather_radius, bool from_edge, int *out_img_x,
int *out_img_y, int *out_img_width, int *out_img_height)
DP_FloodFillKernel kernel_shape, int feather_radius,
bool from_edge, int *out_img_x, int *out_img_y,
int *out_img_width, int *out_img_height)
{
int min_x = c->min_x, min_y = c->min_y;
int max_x = c->max_x, max_y = c->max_y;
Expand Down Expand Up @@ -629,23 +653,40 @@ static float *make_mask(DP_FillContext *c,
}
}
else if (expand > 0) {
unsigned char *kernel = generate_expansion_kernel(expand);
for (int y = min_y; y <= max_y; ++y) {
if (is_cancelled(c)) {
DP_free(kernel);
return mask;
if (kernel_shape == DP_FLOOD_FILL_KERNEL_SQUARE) {
for (int y = min_y; y <= max_y; ++y) {
if (is_cancelled(c)) {
return mask;
}
for (int x = min_x; x <= max_x; ++x) {
float value = get_output(c, x, y);
if (value > 0.0f) {
apply_square_expansion(
mask, img_width, value, c->width, c->height, expand,
feather_radius, expand_min_x, expand_min_y, x, y);
}
}
}
for (int x = min_x; x <= max_x; ++x) {
float value = get_output(c, x, y);
if (value > 0.0f) {
apply_expansion_kernel(mask, img_width, value, kernel,
c->width, c->height, expand,
feather_radius, expand_min_x,
expand_min_y, x, y);
}
else {
unsigned char *kernel = generate_expansion_kernel(expand);
for (int y = min_y; y <= max_y; ++y) {
if (is_cancelled(c)) {
DP_free(kernel);
return mask;
}
for (int x = min_x; x <= max_x; ++x) {
float value = get_output(c, x, y);
if (value > 0.0f) {
apply_expansion_kernel(mask, img_width, value, kernel,
c->width, c->height, expand,
feather_radius, expand_min_x,
expand_min_y, x, y);
}
}
}
DP_free(kernel);
}
DP_free(kernel);
}
else {
int shrink = -expand;
Expand Down Expand Up @@ -794,14 +835,13 @@ static DP_FloodFillResult finish_fill(DP_FillContext *c, float *mask, int img_x,
return DP_FLOOD_FILL_SUCCESS;
}

DP_FloodFillResult
DP_flood_fill(DP_CanvasState *cs, unsigned int context_id, int selection_id,
int x, int y, DP_UPixelFloat fill_color, double tolerance,
int layer_id, int size, int gap, int expand, int feather_radius,
bool from_edge, bool continuous, DP_ViewMode view_mode,
int active_layer_id, int active_frame_index, DP_Image **out_img,
int *out_x, int *out_y, DP_FloodFillShouldCancelFn should_cancel,
void *user)
DP_FloodFillResult DP_flood_fill(
DP_CanvasState *cs, unsigned int context_id, int selection_id, int x, int y,
DP_UPixelFloat fill_color, double tolerance, int layer_id, int size,
int gap, int expand, DP_FloodFillKernel kernel_shape, int feather_radius,
bool from_edge, bool continuous, DP_ViewMode view_mode, int active_layer_id,
int active_frame_index, DP_Image **out_img, int *out_x, int *out_y,
DP_FloodFillShouldCancelFn should_cancel, void *user)
{
DP_ASSERT(cs);

Expand Down Expand Up @@ -938,8 +978,8 @@ DP_flood_fill(DP_CanvasState *cs, unsigned int context_id, int selection_id,

int img_x, img_y, img_width, img_height;
float *mask = make_mask(&c.parent, get_flood_mask_value, expand,
DP_max_int(feather_radius, 0), from_edge, &img_x,
&img_y, &img_width, &img_height);
kernel_shape, DP_max_int(feather_radius, 0),
from_edge, &img_x, &img_y, &img_width, &img_height);
DP_free(c.output);
if (is_cancelled(&c.parent)) {
DP_free(mask);
Expand Down Expand Up @@ -1011,7 +1051,8 @@ static float get_selection_mask_value(void *user, int x, int y)

DP_FloodFillResult
DP_selection_fill(DP_CanvasState *cs, unsigned int context_id, int selection_id,
DP_UPixelFloat fill_color, int expand, int feather_radius,
DP_UPixelFloat fill_color, int expand,
DP_FloodFillKernel kernel_shape, int feather_radius,
bool from_edge, DP_Image **out_img, int *out_x, int *out_y,
DP_FloodFillShouldCancelFn should_cancel, void *user)
{
Expand All @@ -1030,7 +1071,7 @@ DP_selection_fill(DP_CanvasState *cs, unsigned int context_id, int selection_id,
}

int img_x, img_y, img_width, img_height;
float *mask = make_mask(&c, get_selection_mask_value, expand,
float *mask = make_mask(&c, get_selection_mask_value, expand, kernel_shape,
DP_max_int(feather_radius, 0), from_edge, &img_x,
&img_y, &img_width, &img_height);
if (is_cancelled(&c)) {
Expand Down
23 changes: 14 additions & 9 deletions src/drawdance/libengine/dpengine/flood_fill.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
typedef struct DP_CanvasState DP_CanvasState;
typedef struct DP_Image DP_Image;

typedef enum DP_FloodFillKernel {
DP_FLOOD_FILL_KERNEL_GAUSSIAN,
DP_FLOOD_FILL_KERNEL_SQUARE,
} DP_FloodFillKernel;

typedef enum DP_FloodFillResult {
DP_FLOOD_FILL_SUCCESS,
DP_FLOOD_FILL_OUT_OF_BOUNDS,
Expand All @@ -38,18 +43,18 @@ typedef enum DP_FloodFillResult {

typedef bool (*DP_FloodFillShouldCancelFn)(void *user);

DP_FloodFillResult
DP_flood_fill(DP_CanvasState *cs, unsigned int context_id, int selection_id,
int x, int y, DP_UPixelFloat fill_color, double tolerance,
int layer_id, int size, int gap, int expand, int feather_radius,
bool from_edge, bool continuous, DP_ViewMode view_mode,
int active_layer_id, int active_frame_index, DP_Image **out_img,
int *out_x, int *out_y, DP_FloodFillShouldCancelFn should_cancel,
void *user);
DP_FloodFillResult DP_flood_fill(
DP_CanvasState *cs, unsigned int context_id, int selection_id, int x, int y,
DP_UPixelFloat fill_color, double tolerance, int layer_id, int size,
int gap, int expand, DP_FloodFillKernel kernel_shape, int feather_radius,
bool from_edge, bool continuous, DP_ViewMode view_mode, int active_layer_id,
int active_frame_index, DP_Image **out_img, int *out_x, int *out_y,
DP_FloodFillShouldCancelFn should_cancel, void *user);

DP_FloodFillResult
DP_selection_fill(DP_CanvasState *cs, unsigned int context_id, int selection_id,
DP_UPixelFloat fill_color, int expand, int feather_radius,
DP_UPixelFloat fill_color, int expand,
DP_FloodFillKernel kernel_shape, int feather_radius,
bool from_edge, DP_Image **out_img, int *out_x, int *out_y,
DP_FloodFillShouldCancelFn should_cancel, void *user);

Expand Down
21 changes: 11 additions & 10 deletions src/libclient/drawdance/canvasstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,16 +323,17 @@ bool CanvasState::selectionExists(unsigned int contextId, int selectionId) const
DP_FloodFillResult CanvasState::floodFill(
unsigned int contextId, int selectionId, int x, int y,
const QColor &fillColor, double tolerance, int layerId, int sizeLimit,
int gap, int expand, int featherRadius, bool fromEdge, bool continuous,
DP_ViewMode viewMode, int activeLayerId, int activeFrameIndex,
const QAtomicInt &cancel, QImage &outImg, int &outX, int &outY) const
int gap, int expand, DP_FloodFillKernel kernel, int featherRadius,
bool fromEdge, bool continuous, DP_ViewMode viewMode, int activeLayerId,
int activeFrameIndex, const QAtomicInt &cancel, QImage &outImg, int &outX,
int &outY) const
{
DP_UPixelFloat fillPixel = DP_upixel_float_from_color(fillColor.rgba());
DP_Image *img;
DP_FloodFillResult result = DP_flood_fill(
m_data, contextId, selectionId, x, y, fillPixel, tolerance, layerId,
sizeLimit, gap, expand, featherRadius, fromEdge, continuous, viewMode,
activeLayerId, activeFrameIndex, &img, &outX, &outY,
sizeLimit, gap, expand, kernel, featherRadius, fromEdge, continuous,
viewMode, activeLayerId, activeFrameIndex, &img, &outX, &outY,
shouldCancelFloodFill, const_cast<QAtomicInt *>(&cancel));
if(result == DP_FLOOD_FILL_SUCCESS) {
outImg = wrapImage(img);
Expand All @@ -342,15 +343,15 @@ DP_FloodFillResult CanvasState::floodFill(

DP_FloodFillResult CanvasState::selectionFill(
unsigned int contextId, int selectionId, const QColor &fillColor,
int expand, int featherRadius, bool fromEdge, const QAtomicInt &cancel,
QImage &outImg, int &outX, int &outY) const
int expand, DP_FloodFillKernel kernel, int featherRadius, bool fromEdge,
const QAtomicInt &cancel, QImage &outImg, int &outX, int &outY) const
{
DP_UPixelFloat fillPixel = DP_upixel_float_from_color(fillColor.rgba());
DP_Image *img;
DP_FloodFillResult result = DP_selection_fill(
m_data, contextId, selectionId, fillPixel, expand, featherRadius,
fromEdge, &img, &outX, &outY, shouldCancelFloodFill,
const_cast<QAtomicInt *>(&cancel));
m_data, contextId, selectionId, fillPixel, expand,
DP_FloodFillKernel(kernel), featherRadius, fromEdge, &img, &outX, &outY,
shouldCancelFloodFill, const_cast<QAtomicInt *>(&cancel));
if(result == DP_FLOOD_FILL_SUCCESS) {
outImg = wrapImage(img);
}
Expand Down
11 changes: 6 additions & 5 deletions src/libclient/drawdance/canvasstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ class CanvasState final {
DP_FloodFillResult floodFill(
unsigned int contextId, int selectionId, int x, int y,
const QColor &fillColor, double tolerance, int layerId, int sizeLimit,
int gap, int expand, int featherRadius, bool fromEdge, bool continuous,
DP_ViewMode viewMode, int activeLayerId, int activeFrameIndex,
const QAtomicInt &cancel, QImage &outImg, int &outX, int &outY) const;
int gap, int expand, DP_FloodFillKernel kernel, int featherRadius,
bool fromEdge, bool continuous, DP_ViewMode viewMode, int activeLayerId,
int activeFrameIndex, const QAtomicInt &cancel, QImage &outImg,
int &outX, int &outY) const;

DP_FloodFillResult selectionFill(
unsigned int contextId, int selectionId, const QColor &fillColor,
int expand, int featherRadius, bool fromEdge, const QAtomicInt &cancel,
QImage &outImg, int &outX, int &outY) const;
int expand, DP_FloodFillKernel kernel, int featherRadius, bool fromEdge,
const QAtomicInt &cancel, QImage &outImg, int &outX, int &outY) const;

drawdance::CanvasState makeBackwardCompatible() const;

Expand Down
Loading

0 comments on commit 05e549f

Please sign in to comment.