mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +00:00
Add "Tolerance" parameter for some tools like Magic Wand and Paint Bucket (Trent Gamblin idea).
This commit is contained in:
parent
3ea39bb211
commit
dec4bbc6a4
@ -31,9 +31,12 @@
|
||||
<label text="Angle:" />
|
||||
<slider min="0" max="180" name="brush_angle" cell_align="horizontal" />
|
||||
|
||||
<label text="Opacity:" />
|
||||
<label text="Opacity:" name="opacity_label" />
|
||||
<slider min="0" max="255" name="opacity" cell_align="horizontal" />
|
||||
|
||||
<label text="Tolerance:" name="tolerance_label" />
|
||||
<slider min="0" max="255" name="tolerance" cell_align="horizontal" />
|
||||
|
||||
</grid>
|
||||
<box name="brush_preview_box" /><!-- custom widget -->
|
||||
</box>
|
||||
|
@ -51,6 +51,7 @@ static bool brush_size_slider_change_hook(JWidget widget, void *data);
|
||||
static bool brush_angle_slider_change_hook(JWidget widget, void *data);
|
||||
static bool brush_type_change_hook(JWidget widget, void *data);
|
||||
static bool opacity_slider_change_hook(JWidget widget, void *data);
|
||||
static bool tolerance_slider_change_hook(JWidget widget, void *data);
|
||||
static bool spray_width_slider_change_hook(JWidget widget, void *data);
|
||||
static bool air_speed_slider_change_hook(JWidget widget, void *data);
|
||||
static bool tiled_check_change_hook(JWidget widget, void *data);
|
||||
@ -94,6 +95,7 @@ static void on_current_tool_change()
|
||||
Widget* brush_type = window->findChild("brush_type");
|
||||
Widget* brush_preview = window->findChild("brush_preview");
|
||||
Widget* opacity = window->findChild("opacity");
|
||||
Widget* tolerance = window->findChild("tolerance");
|
||||
Widget* spray_width = window->findChild("spray_width");
|
||||
Widget* air_speed = window->findChild("air_speed");
|
||||
|
||||
@ -106,6 +108,7 @@ static void on_current_tool_change()
|
||||
->getToolSettings(current_tool);
|
||||
|
||||
jslider_set_value(opacity, tool_settings->getOpacity());
|
||||
jslider_set_value(tolerance, tool_settings->getTolerance());
|
||||
jslider_set_value(brush_size, tool_settings->getPen()->getSize());
|
||||
jslider_set_value(brush_angle, tool_settings->getPen()->getAngle());
|
||||
jslider_set_value(spray_width, tool_settings->getSprayWidth());
|
||||
@ -139,7 +142,7 @@ ConfigureTools::ConfigureTools()
|
||||
void ConfigureTools::onExecute(Context* context)
|
||||
{
|
||||
JWidget tiled, tiled_x, tiled_y, snap_to_grid, view_grid, pixel_grid, set_grid;
|
||||
JWidget brush_size, brush_angle, opacity;
|
||||
JWidget brush_size, brush_angle, opacity, tolerance;
|
||||
JWidget spray_width, air_speed;
|
||||
JWidget brush_preview_box;
|
||||
JWidget brush_type_box, brush_type;
|
||||
@ -169,6 +172,7 @@ void ConfigureTools::onExecute(Context* context)
|
||||
"brush_size", &brush_size,
|
||||
"brush_angle", &brush_angle,
|
||||
"opacity", &opacity,
|
||||
"tolerance", &tolerance,
|
||||
"spray_width", &spray_width,
|
||||
"air_speed", &air_speed,
|
||||
"brush_preview_box", &brush_preview_box,
|
||||
@ -244,6 +248,7 @@ void ConfigureTools::onExecute(Context* context)
|
||||
HOOK(brush_angle, JI_SIGNAL_SLIDER_CHANGE, brush_angle_slider_change_hook, brush_preview);
|
||||
HOOK(brush_type, SIGNAL_GROUP_BUTTON_CHANGE, brush_type_change_hook, brush_preview);
|
||||
HOOK(opacity, JI_SIGNAL_SLIDER_CHANGE, opacity_slider_change_hook, 0);
|
||||
HOOK(tolerance, JI_SIGNAL_SLIDER_CHANGE, tolerance_slider_change_hook, 0);
|
||||
HOOK(air_speed, JI_SIGNAL_SLIDER_CHANGE, air_speed_slider_change_hook, 0);
|
||||
HOOK(spray_width, JI_SIGNAL_SLIDER_CHANGE, spray_width_slider_change_hook, 0);
|
||||
HOOK(check_onionskin, JI_SIGNAL_CHECK_CHANGE, onionskin_check_change_hook, 0);
|
||||
@ -382,6 +387,14 @@ static bool opacity_slider_change_hook(JWidget widget, void *data)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool tolerance_slider_change_hook(JWidget widget, void *data)
|
||||
{
|
||||
ISettings* settings = UIContext::instance()->getSettings();
|
||||
Tool* current_tool = settings->getCurrentTool();
|
||||
settings->getToolSettings(current_tool)->setTolerance(jslider_get_value(widget));
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool spray_width_slider_change_hook(JWidget widget, void *data)
|
||||
{
|
||||
ISettings* settings = UIContext::instance()->getSettings();
|
||||
|
@ -42,7 +42,7 @@ double algo_spline_get_tan(double x0, double y0, double x1, double y1,
|
||||
double x2, double y2, double x3, double y3,
|
||||
double in_x);
|
||||
|
||||
void algo_floodfill(Image* image, int x, int y, void* data, AlgoHLine proc);
|
||||
void algo_floodfill(Image* image, int x, int y, int tolerance, void* data, AlgoHLine proc);
|
||||
|
||||
void algo_polygon(int vertices, const int* points, void* data, AlgoHLine proc);
|
||||
|
||||
|
@ -48,19 +48,54 @@ static int flood_count; /* number of flooded segments */
|
||||
|
||||
#define FLOOD_LINE(c) (((FLOODED_LINE *)_scratch_mem) + c)
|
||||
|
||||
static inline bool color_equal_32(ase_uint32 c1, ase_uint32 c2)
|
||||
static inline bool color_equal_32(ase_uint32 c1, ase_uint32 c2, int tolerance)
|
||||
{
|
||||
return (c1 == c2) || (_rgba_geta(c1) == 0 && _rgba_geta(c2) == 0);
|
||||
if (tolerance == 0)
|
||||
return (c1 == c2) || (_rgba_geta(c1) == 0 && _rgba_geta(c2) == 0);
|
||||
else {
|
||||
int r1 = _rgba_getr(c1);
|
||||
int g1 = _rgba_getg(c1);
|
||||
int b1 = _rgba_getb(c1);
|
||||
int a1 = _rgba_geta(c1);
|
||||
int r2 = _rgba_getr(c2);
|
||||
int g2 = _rgba_getg(c2);
|
||||
int b2 = _rgba_getb(c2);
|
||||
int a2 = _rgba_geta(c2);
|
||||
|
||||
if (a1 == 0 && a2 == 0)
|
||||
return true;
|
||||
|
||||
return ((ABS(r1-r2) <= tolerance) &&
|
||||
(ABS(g1-g2) <= tolerance) &&
|
||||
(ABS(b1-b2) <= tolerance) &&
|
||||
(ABS(a1-a2) <= tolerance));
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool color_equal_16(ase_uint16 c1, ase_uint16 c2)
|
||||
static inline bool color_equal_16(ase_uint16 c1, ase_uint16 c2, int tolerance)
|
||||
{
|
||||
return (c1 == c2) || (_graya_geta(c1) == 0 && _graya_geta(c2) == 0);
|
||||
if (tolerance == 0)
|
||||
return (c1 == c2) || (_graya_geta(c1) == 0 && _graya_geta(c2) == 0);
|
||||
else {
|
||||
int k1 = _graya_getv(c1);
|
||||
int a1 = _graya_geta(c1);
|
||||
int k2 = _graya_getv(c2);
|
||||
int a2 = _graya_geta(c2);
|
||||
|
||||
if (a1 == 0 && a2 == 0)
|
||||
return true;
|
||||
|
||||
return ((ABS(k1-k2) <= tolerance) &&
|
||||
(ABS(a1-a2) <= tolerance));
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool color_equal_8(ase_uint8 c1, ase_uint8 c2)
|
||||
static inline bool color_equal_8(ase_uint8 c1, ase_uint8 c2, int tolerance)
|
||||
{
|
||||
return (c1 == c2);
|
||||
if (tolerance == 0)
|
||||
return (c1 == c2);
|
||||
else
|
||||
return ABS(c1-c2) <= tolerance;
|
||||
}
|
||||
|
||||
|
||||
@ -71,7 +106,7 @@ static inline bool color_equal_8(ase_uint8 c1, ase_uint8 c2)
|
||||
* the part of the line which it has dealt with.
|
||||
*/
|
||||
static int flooder (Image *image, int x, int y,
|
||||
int src_color, void *data, AlgoHLine proc)
|
||||
int src_color, int tolerance, void *data, AlgoHLine proc)
|
||||
{
|
||||
FLOODED_LINE *p;
|
||||
int left = 0, right = 0;
|
||||
@ -84,18 +119,18 @@ static int flooder (Image *image, int x, int y,
|
||||
ase_uint32 *address = ((ase_uint32 **)image->line)[y];
|
||||
|
||||
/* check start pixel */
|
||||
if (!color_equal_32((int)*(address+x), src_color))
|
||||
if (!color_equal_32((int)*(address+x), src_color, tolerance))
|
||||
return x+1;
|
||||
|
||||
/* work left from starting point */
|
||||
for (left=x-1; left>=0; left--) {
|
||||
if (!color_equal_32((int)*(address+left), src_color))
|
||||
if (!color_equal_32((int)*(address+left), src_color, tolerance))
|
||||
break;
|
||||
}
|
||||
|
||||
/* work right from starting point */
|
||||
for (right=x+1; right<image->w; right++) {
|
||||
if (!color_equal_32((int)*(address+right), src_color))
|
||||
if (!color_equal_32((int)*(address+right), src_color, tolerance))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -106,18 +141,18 @@ static int flooder (Image *image, int x, int y,
|
||||
ase_uint16 *address = ((ase_uint16 **)image->line)[y];
|
||||
|
||||
/* check start pixel */
|
||||
if (!color_equal_16((int)*(address+x), src_color))
|
||||
if (!color_equal_16((int)*(address+x), src_color, tolerance))
|
||||
return x+1;
|
||||
|
||||
/* work left from starting point */
|
||||
for (left=x-1; left>=0; left--) {
|
||||
if (!color_equal_16((int)*(address+left), src_color))
|
||||
if (!color_equal_16((int)*(address+left), src_color, tolerance))
|
||||
break;
|
||||
}
|
||||
|
||||
/* work right from starting point */
|
||||
for (right=x+1; right<image->w; right++) {
|
||||
if (!color_equal_16((int)*(address+right), src_color))
|
||||
if (!color_equal_16((int)*(address+right), src_color, tolerance))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -128,18 +163,18 @@ static int flooder (Image *image, int x, int y,
|
||||
ase_uint8 *address = ((ase_uint8 **)image->line)[y];
|
||||
|
||||
/* check start pixel */
|
||||
if (!color_equal_8((int)*(address+x), src_color))
|
||||
if (!color_equal_8((int)*(address+x), src_color, tolerance))
|
||||
return x+1;
|
||||
|
||||
/* work left from starting point */
|
||||
for (left=x-1; left>=0; left--) {
|
||||
if (!color_equal_8((int)*(address+left), src_color))
|
||||
if (!color_equal_8((int)*(address+left), src_color, tolerance))
|
||||
break;
|
||||
}
|
||||
|
||||
/* work right from starting point */
|
||||
for (right=x+1; right<image->w; right++) {
|
||||
if (!color_equal_8((int)*(address+right), src_color))
|
||||
if (!color_equal_8((int)*(address+right), src_color, tolerance))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -207,8 +242,8 @@ static int flooder (Image *image, int x, int y,
|
||||
* segments which have already been drawn in order to minimise the required
|
||||
* number of tests.
|
||||
*/
|
||||
static int check_flood_line(Image *image, int y, int left, int right,
|
||||
int src_color, void *data, AlgoHLine proc)
|
||||
static int check_flood_line(Image* image, int y, int left, int right,
|
||||
int src_color, int tolerance, void *data, AlgoHLine proc)
|
||||
{
|
||||
int c;
|
||||
FLOODED_LINE *p;
|
||||
@ -228,7 +263,7 @@ static int check_flood_line(Image *image, int y, int left, int right,
|
||||
c = p->next;
|
||||
|
||||
if (!c) {
|
||||
left = flooder (image, left, y, src_color, data, proc);
|
||||
left = flooder (image, left, y, src_color, tolerance, data, proc);
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@ -243,7 +278,7 @@ static int check_flood_line(Image *image, int y, int left, int right,
|
||||
/* floodfill:
|
||||
* Fills an enclosed area (starting at point x, y) with the specified color.
|
||||
*/
|
||||
void algo_floodfill(Image *image, int x, int y, void *data, AlgoHLine proc)
|
||||
void algo_floodfill(Image* image, int x, int y, int tolerance, void *data, AlgoHLine proc)
|
||||
{
|
||||
int src_color;
|
||||
int c, done;
|
||||
@ -270,7 +305,7 @@ void algo_floodfill(Image *image, int x, int y, void *data, AlgoHLine proc)
|
||||
}
|
||||
|
||||
/* start up the flood algorithm */
|
||||
flooder(image, x, y, src_color, data, proc);
|
||||
flooder(image, x, y, src_color, tolerance, data, proc);
|
||||
|
||||
/* continue as long as there are some segments still to test */
|
||||
do {
|
||||
@ -285,7 +320,7 @@ void algo_floodfill(Image *image, int x, int y, void *data, AlgoHLine proc)
|
||||
if (p->flags & FLOOD_TODO_BELOW) {
|
||||
p->flags &= ~FLOOD_TODO_BELOW;
|
||||
if (check_flood_line(image, p->y+1, p->lpos, p->rpos,
|
||||
src_color, data, proc)) {
|
||||
src_color, tolerance, data, proc)) {
|
||||
done = false;
|
||||
p = FLOOD_LINE(c);
|
||||
}
|
||||
@ -295,7 +330,7 @@ void algo_floodfill(Image *image, int x, int y, void *data, AlgoHLine proc)
|
||||
if (p->flags & FLOOD_TODO_ABOVE) {
|
||||
p->flags &= ~FLOOD_TODO_ABOVE;
|
||||
if (check_flood_line(image, p->y-1, p->lpos, p->rpos,
|
||||
src_color, data, proc)) {
|
||||
src_color, tolerance, data, proc)) {
|
||||
done = false;
|
||||
/* special case shortcut for going backwards */
|
||||
if ((c < image->h) && (c > 0))
|
||||
|
@ -135,7 +135,7 @@ void Pen::regenerate_pen()
|
||||
image_line(m_image, r+x3, r+y3, r+x4, r+y4, 1);
|
||||
image_line(m_image, r+x4, r+y4, r+x1, r+y1, 1);
|
||||
|
||||
algo_floodfill(m_image, r, r, m_image, algo_hline);
|
||||
algo_floodfill(m_image, r, r, 0, m_image, algo_hline);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -97,12 +97,14 @@ public:
|
||||
virtual IPenSettings* getPen() = 0;
|
||||
|
||||
virtual int getOpacity() = 0;
|
||||
virtual int getTolerance() = 0;
|
||||
virtual bool getFilled() = 0;
|
||||
virtual bool getPreviewFilled() = 0;
|
||||
virtual int getSprayWidth() = 0;
|
||||
virtual int getSpraySpeed() = 0;
|
||||
|
||||
virtual void setOpacity(int opacity) = 0;
|
||||
virtual void setTolerance(int tolerance) = 0;
|
||||
virtual void setFilled(bool state) = 0;
|
||||
virtual void setSprayWidth(int width) = 0;
|
||||
virtual void setSpraySpeed(int speed) = 0;
|
||||
|
@ -311,6 +311,7 @@ class UIToolSettingsImpl : public IToolSettings
|
||||
Tool* m_tool;
|
||||
UIPenSettingsImpl m_pen;
|
||||
int m_opacity;
|
||||
int m_tolerance;
|
||||
bool m_filled;
|
||||
bool m_previewFilled;
|
||||
int m_spray_width;
|
||||
@ -325,6 +326,8 @@ public:
|
||||
|
||||
m_opacity = get_config_int(cfg_section.c_str(), "Opacity", 255);
|
||||
m_opacity = MID(0, m_opacity, 255);
|
||||
m_tolerance = get_config_int(cfg_section.c_str(), "Tolerance", 0);
|
||||
m_tolerance = MID(0, m_tolerance, 255);
|
||||
m_filled = false;
|
||||
m_previewFilled = get_config_bool(cfg_section.c_str(), "PreviewFilled", false);
|
||||
m_spray_width = 16;
|
||||
@ -348,6 +351,7 @@ public:
|
||||
std::string cfg_section(getCfgSection());
|
||||
|
||||
set_config_int(cfg_section.c_str(), "Opacity", m_opacity);
|
||||
set_config_int(cfg_section.c_str(), "Tolerance", m_tolerance);
|
||||
set_config_int(cfg_section.c_str(), "PenType", m_pen.getType());
|
||||
set_config_int(cfg_section.c_str(), "PenSize", m_pen.getSize());
|
||||
set_config_int(cfg_section.c_str(), "PenAngle", m_pen.getAngle());
|
||||
@ -364,12 +368,14 @@ public:
|
||||
IPenSettings* getPen() { return &m_pen; }
|
||||
|
||||
int getOpacity() { return m_opacity; }
|
||||
int getTolerance() { return m_tolerance; }
|
||||
bool getFilled() { return m_filled; }
|
||||
bool getPreviewFilled() { return m_previewFilled; }
|
||||
int getSprayWidth() { return m_spray_width; }
|
||||
int getSpraySpeed() { return m_spray_speed; }
|
||||
|
||||
void setOpacity(int opacity) { m_opacity = opacity; }
|
||||
void setTolerance(int tolerance) { m_tolerance = tolerance; }
|
||||
void setFilled(bool state) { m_filled = state; }
|
||||
void setPreviewFilled(bool state) { m_previewFilled = state; }
|
||||
void setSprayWidth(int width) { m_spray_width = width; }
|
||||
|
@ -74,7 +74,7 @@ class FloodFillPointShape : public ToolPointShape
|
||||
public:
|
||||
void transformPoint(IToolLoop* loop, int x, int y)
|
||||
{
|
||||
algo_floodfill(loop->getSrcImage(), x, y, loop, (AlgoHLine)doInkHline);
|
||||
algo_floodfill(loop->getSrcImage(), x, y, loop->getTolerance(), loop, (AlgoHLine)doInkHline);
|
||||
}
|
||||
void getModifiedArea(IToolLoop* loop, int x, int y, Rect& area)
|
||||
{
|
||||
|
@ -276,6 +276,9 @@ public:
|
||||
// Returns the opacity to be used by the ink (ToolInk).
|
||||
virtual int getOpacity() = 0;
|
||||
|
||||
// Returns the tolerance to be used by the ink (ToolInk).
|
||||
virtual int getTolerance() = 0;
|
||||
|
||||
// Returns true if each scanline generated by a ToolPointShape must
|
||||
// be "tiled". See the method ToolPointShape::doInkHline to check
|
||||
// how this member is used. When tiled mode is activated, each
|
||||
|
@ -1784,6 +1784,7 @@ class ToolLoopImpl : public IToolLoop
|
||||
Mask* m_mask;
|
||||
Point m_maskOrigin;
|
||||
int m_opacity;
|
||||
int m_tolerance;
|
||||
Point m_offset;
|
||||
Point m_speed;
|
||||
bool m_canceled;
|
||||
@ -1888,6 +1889,7 @@ public:
|
||||
Point(0, 0));
|
||||
|
||||
m_opacity = settings->getToolSettings(m_tool)->getOpacity();
|
||||
m_tolerance = settings->getToolSettings(m_tool)->getTolerance();
|
||||
m_speed.x = 0;
|
||||
m_speed.y = 0;
|
||||
|
||||
@ -2032,6 +2034,7 @@ public:
|
||||
int getSecondaryColor() { return m_secondary_color; }
|
||||
void setSecondaryColor(int color) { m_secondary_color = color; }
|
||||
int getOpacity() { return m_opacity; }
|
||||
int getTolerance() { return m_tolerance; }
|
||||
TiledMode getTiledMode() { return m_tiled_mode; }
|
||||
bool getFilled() { return m_filled; }
|
||||
bool getPreviewFilled() { return m_previewFilled; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user