From 4ada2909566c6966607d3a4d78964902ad9e19a6 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Mon, 10 Aug 2020 16:34:19 +0200 Subject: [PATCH] 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 --- .../keyconfig/keymap_data/blender_default.py | 14 +- .../keymap_data/industry_compatible_data.py | 12 +- .../startup/bl_ui/properties_paint_common.py | 2 + source/blender/editors/gpencil/gpencil_fill.c | 156 +++++++++++++++--- .../blender/editors/gpencil/gpencil_paint.c | 13 +- source/blender/makesdna/DNA_brush_types.h | 2 +- source/blender/makesrna/intern/rna_brush.c | 12 ++ 7 files changed, 168 insertions(+), 43 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index efa4b9b00a0..b945a92020f 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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 diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index eedf07935c8..04721a1704a 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -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 diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 01f9eedd96e..12f02acedc2 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -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) diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index a70bbfc9d48..2855f2cc0d7 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -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; diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 611fe158948..680e7b37d31 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -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", diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index c79ae68678f..036c6c9c9d6 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 11b563dae52..ca90483926f 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -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);