GPencil: Invert Filled area pressing Ctrl key

This feature was suggested in https://blender.community/c/rightclickselect/rggbbc/

When press `Ctrl+LMB`, the filled area is inverted.

{F8749306}

{F8749307}

Filling several areas:

{F8759399}

Differential Revision: https://developer.blender.org/D8477
This commit is contained in:
Antonio Vazquez 2020-08-10 16:34:19 +02:00
parent 3fe2fceb4e
commit 4ada290956
7 changed files with 168 additions and 43 deletions

View File

@ -3364,16 +3364,14 @@ def km_grease_pencil_stroke_paint_fill(params):
# Fill
("gpencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("on_back", False)]}),
("gpencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("on_back", True)]}),
("gpencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("on_back", False)]}),
# If press alternate key, the brush now it's for drawing areas
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True)]}),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True), ("disable_stabilizer", True)]}),
# If press alternative key, the brush now it's for drawing lines
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True), ("disable_fill", True)]}),
# Lasso select
("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True, "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True), ("disable_stabilizer", True), ("disable_fill", True)]}),
])
return keymap

View File

@ -2465,16 +2465,14 @@ def km_grease_pencil_stroke_paint_fill(params):
# Fill
("gpencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("on_back", False)]}),
("gpencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("on_back", True)]}),
("gpencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("on_back", False)]}),
# If press alternate key, the brush now it's for drawing areas
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True)]}),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True), ("disable_stabilizer", True)]}),
# If press alternative key, the brush now it's for drawing lines
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True, "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True), ("disable_fill", True)]}),
# Lasso select
("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None),
{"properties": [("mode", 'DRAW'), ("wait_for_input", False), ("disable_straight", True), ("disable_stabilizer", True), ("disable_fill", True)]}),
])
return keymap

View File

@ -1137,6 +1137,8 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
# FIXME: tools must use their own UI drawing!
elif brush.gpencil_tool == 'FILL':
row = layout.row(align=True)
row.prop(gp_settings, "fill_direction", text="", expand=True)
row = layout.row(align=True)
row.prop(gp_settings, "fill_leak", text="Leak Size")
row = layout.row(align=True)

View File

@ -750,6 +750,92 @@ static void gpencil_set_borders(tGPDfill *tgpf, const bool transparent)
tgpf->ima->id.tag |= LIB_TAG_DOIT;
}
/* Invert image to paint invese area. */
static void gpencil_invert_image(tGPDfill *tgpf)
{
ImBuf *ibuf;
void *lock;
const float fill_col[3][4] = {
{1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}};
ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock);
const int maxpixel = (ibuf->x * ibuf->y) - 1;
for (int v = maxpixel; v != 0; v--) {
float color[4];
get_pixel(ibuf, v, color);
/* Green. */
if (color[1] == 1.0f) {
set_pixel(ibuf, v, fill_col[0]);
}
else if (color[0] == 1.0f) {
set_pixel(ibuf, v, fill_col[1]);
}
else {
set_pixel(ibuf, v, fill_col[2]);
}
}
/* release ibuf */
if (ibuf) {
BKE_image_release_ibuf(tgpf->ima, ibuf, lock);
}
tgpf->ima->id.tag |= LIB_TAG_DOIT;
}
/* Mark and clear processed areas. */
static void gpencil_erase_processed_area(tGPDfill *tgpf)
{
ImBuf *ibuf;
void *lock;
const float blue_col[4] = {0.0f, 0.0f, 1.0f, 1.0f};
const float clear_col[4] = {1.0f, 0.0f, 0.0f, 1.0f};
tGPspoint *point2D;
if (tgpf->sbuffer_used == 0) {
return;
}
ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock);
point2D = (tGPspoint *)tgpf->sbuffer;
/* First set in blue the perimeter. */
for (int i = 0; i < tgpf->sbuffer_used && point2D; i++, point2D++) {
int image_idx = ibuf->x * (int)point2D->y + (int)point2D->x;
set_pixel(ibuf, image_idx, blue_col);
}
/* Second, clean by lines any pixel between blue pixels. */
float rgba[4];
for (int idy = 0; idy < ibuf->y; idy++) {
bool clear = false;
for (int idx = 0; idx < ibuf->x; idx++) {
int image_idx = ibuf->x * idy + idx;
get_pixel(ibuf, image_idx, rgba);
/* Blue. */
if (rgba[2] == 1.0f) {
clear = true;
}
/* Red. */
else if (rgba[0] == 1.0f) {
clear = false;
}
if (clear) {
set_pixel(ibuf, image_idx, clear_col);
}
}
}
/* release ibuf */
if (ibuf) {
BKE_image_release_ibuf(tgpf->ima, ibuf, lock);
}
tgpf->ima->id.tag |= LIB_TAG_DOIT;
}
/* Naive dilate
*
* Expand green areas into enclosing red areas.
@ -761,7 +847,7 @@ static void gpencil_set_borders(tGPDfill *tgpf, const bool transparent)
* XXXX
* -----------
*/
static void dilate(ImBuf *ibuf)
static void dilate_shape(ImBuf *ibuf)
{
BLI_Stack *stack = BLI_stack_new(sizeof(int), __func__);
const float green[4] = {0.0f, 1.0f, 0.0f, 1.0f};
@ -860,7 +946,7 @@ static void dilate(ImBuf *ibuf)
* This is a Blender customized version of the general algorithm described
* in https://en.wikipedia.org/wiki/Moore_neighborhood
*/
static void gpencil_get_outline_points(tGPDfill *tgpf)
static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate)
{
ImBuf *ibuf;
float rgba[4];
@ -892,8 +978,10 @@ static void gpencil_get_outline_points(tGPDfill *tgpf)
ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock);
int imagesize = ibuf->x * ibuf->y;
/* dilate */
dilate(ibuf);
/* Dilate. */
if (dilate) {
dilate_shape(ibuf);
}
/* find the initial point to start outline analysis */
for (int idx = imagesize - 1; idx != 0; idx--) {
@ -1027,12 +1115,12 @@ static void gpencil_get_depth_array(tGPDfill *tgpf)
}
/* create array of points using stack as source */
static void gpencil_points_from_stack(tGPDfill *tgpf)
static int gpencil_points_from_stack(tGPDfill *tgpf)
{
tGPspoint *point2D;
int totpoints = BLI_stack_count(tgpf->stack);
if (totpoints == 0) {
return;
return 0;
}
tgpf->sbuffer_used = (short)totpoints;
@ -1050,6 +1138,8 @@ static void gpencil_points_from_stack(tGPDfill *tgpf)
point2D->time = 0.0f;
point2D++;
}
return totpoints;
}
/* create a grease pencil stroke using points in buffer */
@ -1461,6 +1551,10 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
tGPDfill *tgpf = op->customdata;
Scene *scene = tgpf->scene;
Brush *brush = tgpf->brush;
BrushGpencilSettings *brush_settings = brush->gpencil_settings;
const bool is_brush_inv = brush_settings->fill_direction == BRUSH_DIR_IN;
const bool is_inverted = (is_brush_inv && !event->ctrl) || (!is_brush_inv && event->ctrl);
int estate = OPERATOR_PASS_THROUGH; /* default exit state - pass through */
@ -1489,6 +1583,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
tgpf->active_cfra = CFRA;
/* render screen to temp image */
int totpoints = 1;
if (gpencil_render_offscreen(tgpf)) {
/* Set red borders to create a external limit. */
@ -1497,31 +1592,46 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
/* apply boundary fill */
gpencil_boundaryfill_area(tgpf);
/* Invert direction if press Ctrl. */
if (is_inverted) {
gpencil_invert_image(tgpf);
}
/* Clean borders to avoid infinite loops. */
gpencil_set_borders(tgpf, false);
/* analyze outline */
gpencil_get_outline_points(tgpf);
while (totpoints > 0) {
/* analyze outline */
gpencil_get_outline_points(tgpf, (totpoints == 1) ? true : false);
/* create array of points from stack */
gpencil_points_from_stack(tgpf);
/* create array of points from stack */
totpoints = gpencil_points_from_stack(tgpf);
/* create z-depth array for reproject */
gpencil_get_depth_array(tgpf);
/* create z-depth array for reproject */
gpencil_get_depth_array(tgpf);
/* create stroke and reproject */
gpencil_stroke_from_buffer(tgpf);
/* create stroke and reproject */
gpencil_stroke_from_buffer(tgpf);
if (is_inverted) {
gpencil_erase_processed_area(tgpf);
}
else {
/* Exit of the loop. */
totpoints = 0;
}
/* free temp stack data */
if (tgpf->stack) {
BLI_stack_free(tgpf->stack);
}
/* Free memory. */
MEM_SAFE_FREE(tgpf->sbuffer);
MEM_SAFE_FREE(tgpf->depth_arr);
}
}
/* free temp stack data */
if (tgpf->stack) {
BLI_stack_free(tgpf->stack);
}
/* Free memory. */
MEM_SAFE_FREE(tgpf->sbuffer);
MEM_SAFE_FREE(tgpf->depth_arr);
/* restore size */
tgpf->region->winx = (short)tgpf->bwinx;
tgpf->region->winy = (short)tgpf->bwiny;

View File

@ -250,7 +250,8 @@ typedef struct tGPsdata {
short shift;
/** size in pixels for uv calculation */
float totpixlen;
/** Special mode for fill brush. */
bool disable_stabilizer;
/* guide */
tGPguide guide;
@ -374,7 +375,7 @@ static bool gpencil_stroke_filtermval(tGPsdata *p, const float mval[2], const fl
return true;
}
/* if lazy mouse, check minimum distance */
if (GPENCIL_LAZY_MODE(brush, p->shift)) {
if (GPENCIL_LAZY_MODE(brush, p->shift) && (!p->disable_stabilizer)) {
brush->gpencil_settings->flag |= GP_BRUSH_STABILIZE_MOUSE_TEMP;
if ((dx * dx + dy * dy) > (brush->smooth_stroke_radius * brush->smooth_stroke_radius)) {
return true;
@ -1886,6 +1887,7 @@ static bool gpencil_session_initdata(bContext *C, wmOperator *op, tGPsdata *p)
p->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
p->win = CTX_wm_window(C);
p->disable_fill = RNA_boolean_get(op->ptr, "disable_fill");
p->disable_stabilizer = RNA_boolean_get(op->ptr, "disable_stabilizer");
unit_m4(p->imat);
unit_m4(p->mat);
@ -2681,7 +2683,7 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra
else if (gpencil_stroke_filtermval(p, p->mval, p->mvalo)) {
/* if lazy mouse, interpolate the last and current mouse positions */
if (GPENCIL_LAZY_MODE(p->brush, p->shift)) {
if (GPENCIL_LAZY_MODE(p->brush, p->shift) && (!p->disable_stabilizer)) {
float now_mouse[2];
float last_mouse[2];
copy_v2_v2(now_mouse, p->mval);
@ -3443,7 +3445,7 @@ static void gpencil_add_fake_points(const wmEvent *event, tGPsdata *p)
{
Brush *brush = p->brush;
/* Lazy mode do not use fake events. */
if (GPENCIL_LAZY_MODE(brush, p->shift)) {
if (GPENCIL_LAZY_MODE(brush, p->shift) && (!p->disable_stabilizer)) {
return;
}
@ -3891,6 +3893,9 @@ void GPENCIL_OT_draw(wmOperatorType *ot)
"Disable fill to use stroke as fill boundary");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "disable_stabilizer", false, "No Stabilizer", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
/* guides */
prop = RNA_def_float(ot->srna,
"guide_last_angle",

View File

@ -68,7 +68,7 @@ typedef struct BrushGpencilSettings {
short draw_subdivide;
/** Layers used for fill. */
short fill_layer_mode;
char _pad[2];
short fill_direction;
/** Factor for transparency. */
float fill_threshold;

View File

@ -252,6 +252,12 @@ static EnumPropertyItem rna_enum_gpencil_fill_layers_modes_items[] = {
{GP_FILL_GPLMODE_ALL_BELOW, "ALL_BELOW", 0, "All Below", "All layers below active"},
{0, NULL, 0, NULL, NULL}};
static EnumPropertyItem rna_enum_gpencil_fill_direction_items[] = {
{0, "NORMAL", ICON_ADD, "Normal", "Fill internal area"},
{BRUSH_DIR_IN, "INVERT", ICON_REMOVE, "Inverted", "Fill inverted area"},
{0, NULL, 0, NULL, NULL},
};
static EnumPropertyItem rna_enum_gpencil_brush_modes_items[] = {
{GP_BRUSH_MODE_ACTIVE, "ACTIVE", 0, "Active", "Use current mode"},
{GP_BRUSH_MODE_MATERIAL, "MATERIAL", 0, "Material", "Use always material mode"},
@ -1661,6 +1667,12 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Layer Mode", "Layers used as boundaries");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "fill_direction", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "fill_direction");
RNA_def_property_enum_items(prop, rna_enum_gpencil_fill_direction_items);
RNA_def_property_ui_text(prop, "Direction", "Direction of the fill");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
prop = RNA_def_property(srna, "brush_draw_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "brush_draw_mode");
RNA_def_property_enum_items(prop, rna_enum_gpencil_brush_modes_items);