From 1352d81b174726639bbfb6f7aa32dbadf188a8dd Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Mon, 8 Feb 2021 16:28:42 +0100 Subject: GPencil: Fill tool refactor and Multiframe in Draw mode This commit is a refactor of the fill tool to solve several problems we had since the first version of the tool. Changes: * The filling speed has been improved for each step of the process with the optimization of each algorithm/function. * New `AutoFit` option to fill areas outside of the viewport. When enable, the total size of the frame is calculated to fit the filling area. * New support multiframe filling. Now it is possible to fill multiple similar frames in one go. * New `Stroke Extension` option to create temporary closing strokes. These strokes can be displayed and adjusted dynamically using wheel mouse or PageUp/Down keys. * Parameter `Resolution` now is named `Precision` and has been moved to topbar. * `Resolution` now has decimals and can be lower than 1 to allow quick filling in storyboarding workflows. Maximum value has been set as 5. * Parameter `Simplify` has been moved to Advanced panel. * Improved fill outline detection. In some cases, the outline penetrated the area to be filled with unexpected results. * Fixes some corner case bugs with infinite loops. As a result of this refactor, also these new functionalities has been added. * New support for multiframe in `Draw` mode. Any drawing in active frame is duplicated to all selected frame. * New multiframe display mode. Keyframes before or after of the active frame are displayed using onion colors. This can be disable using Onion overlay options. --- source/blender/editors/gpencil/drawgpencil.c | 11 +- source/blender/editors/gpencil/gpencil_data.c | 10 +- source/blender/editors/gpencil/gpencil_edit.c | 115 ++- source/blender/editors/gpencil/gpencil_fill.c | 1078 ++++++++++++++------ source/blender/editors/gpencil/gpencil_paint.c | 77 +- source/blender/editors/gpencil/gpencil_primitive.c | 6 + source/blender/editors/gpencil/gpencil_utils.c | 14 +- 7 files changed, 943 insertions(+), 368 deletions(-) (limited to 'source/blender/editors/gpencil') diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c index 4e2951c3571..751f8333aaa 100644 --- a/source/blender/editors/gpencil/drawgpencil.c +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -171,7 +171,8 @@ static void gpencil_draw_stroke_3d(tGPDdraw *tgpw, int totpoints = tgpw->gps->totpoints; const float viewport[2] = {(float)tgpw->winx, (float)tgpw->winy}; - float curpressure = points[0].pressure; + const float min_thickness = 0.05f; + float fpt[3]; /* if cyclic needs more vertex */ @@ -205,7 +206,6 @@ static void gpencil_draw_stroke_3d(tGPDdraw *tgpw, immUniform1i("fill_stroke", (int)tgpw->is_fill_stroke); /* draw stroke curve */ - GPU_line_width(max_ff(curpressure * thickness, 1.0f)); immBeginAtMost(GPU_PRIM_LINE_STRIP_ADJ, totpoints + cyclic_add + 2); const bGPDspoint *pt = points; @@ -215,18 +215,19 @@ static void gpencil_draw_stroke_3d(tGPDdraw *tgpw, gpencil_set_point_varying_color(points, ink, attr_id.color, (bool)tgpw->is_fill_stroke); if ((cyclic) && (totpoints > 2)) { - immAttr1f(attr_id.thickness, max_ff((points + totpoints - 1)->pressure * thickness, 1.0f)); + immAttr1f(attr_id.thickness, + max_ff((points + totpoints - 1)->pressure * thickness, min_thickness)); mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + totpoints - 1)->x); } else { - immAttr1f(attr_id.thickness, max_ff((points + 1)->pressure * thickness, 1.0f)); + immAttr1f(attr_id.thickness, max_ff((points + 1)->pressure * thickness, min_thickness)); mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + 1)->x); } immVertex3fv(attr_id.pos, fpt); } /* set point */ gpencil_set_point_varying_color(pt, ink, attr_id.color, (bool)tgpw->is_fill_stroke); - immAttr1f(attr_id.thickness, max_ff(pt->pressure * thickness, 1.0f)); + immAttr1f(attr_id.thickness, max_ff(pt->pressure * thickness, min_thickness)); mul_v3_m4v3(fpt, tgpw->diff_mat, &pt->x); immVertex3fv(attr_id.pos, fpt); } diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index e297a806895..3be913f342d 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -466,9 +466,15 @@ static int gpencil_layer_copy_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* make copy of layer, and add it immediately after the existing layer */ + /* Make copy of layer, and add it immediately after or before the existing layer. */ new_layer = BKE_gpencil_layer_duplicate(gpl, true, dup_strokes); - BLI_insertlinkafter(&gpd->layers, gpl, new_layer); + if (dup_strokes) { + BLI_insertlinkafter(&gpd->layers, gpl, new_layer); + } + else { + /* For empty strokes is better add below. */ + BLI_insertlinkbefore(&gpd->layers, gpl, new_layer); + } /* ensure new layer has a unique name, and is now the active layer */ BLI_uniquename(&gpd->layers, diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index b5269bbfacf..aeff2acb04d 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -4948,11 +4948,14 @@ static int gpencil_cutter_lasso_select(bContext *C, GPencilTestFn is_inside_fn, void *user_data) { + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *obact = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); ScrArea *area = CTX_wm_area(C); ToolSettings *ts = CTX_data_tool_settings(C); const float scale = ts->gp_sculpt.isect_threshold; const bool flat_caps = RNA_boolean_get(op->ptr, "flat_caps"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bGPDspoint *pt; GP_SpaceConversion gsc = {NULL}; @@ -4979,57 +4982,87 @@ static int gpencil_cutter_lasso_select(bContext *C, } CTX_DATA_END; - /* select points */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - int tot_inside = 0; - const int oldtot = gps->totpoints; - for (int i = 0; i < gps->totpoints; i++) { - pt = &gps->points[i]; - if ((pt->flag & GP_SPOINT_SELECT) || (pt->flag & GP_SPOINT_TAG)) { - continue; - } - /* convert point coords to screen-space */ - const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data); - if (is_inside) { - tot_inside++; - changed = true; - pt->flag |= GP_SPOINT_SELECT; - gps->flag |= GP_STROKE_SELECT; - float r_hita[3], r_hitb[3]; - if (gps->totpoints > 1) { - ED_gpencil_select_stroke_segment(gpd, gpl, gps, pt, true, true, scale, r_hita, r_hitb); + /* Select points */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if ((gpl->flag & GP_LAYER_LOCKED) || ((gpl->flag & GP_LAYER_HIDE))) { + continue; + } + + float diff_mat[4][4]; + BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat); + + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; } - /* avoid infinite loops */ - if (gps->totpoints > oldtot) { + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } /* check if the color is editable */ + if (ED_gpencil_stroke_material_editable(obact, gpl, gps) == false) { + continue; + } + int tot_inside = 0; + const int oldtot = gps->totpoints; + for (int i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if ((pt->flag & GP_SPOINT_SELECT) || (pt->flag & GP_SPOINT_TAG)) { + continue; + } + /* convert point coords to screen-space */ + const bool is_inside = is_inside_fn(gps, pt, &gsc, diff_mat, user_data); + if (is_inside) { + tot_inside++; + changed = true; + pt->flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; + float r_hita[3], r_hitb[3]; + if (gps->totpoints > 1) { + ED_gpencil_select_stroke_segment( + gpd, gpl, gps, pt, true, true, scale, r_hita, r_hitb); + } + /* avoid infinite loops */ + if (gps->totpoints > oldtot) { + break; + } + } + } + /* if mark all points inside lasso set to remove all stroke */ + if ((tot_inside == oldtot) || ((tot_inside == 1) && (oldtot == 2))) { + for (int i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + pt->flag |= GP_SPOINT_SELECT; + } + } + } + /* if not multiedit, exit loop. */ + if (!is_multiedit) { break; } } } - /* if mark all points inside lasso set to remove all stroke */ - if ((tot_inside == oldtot) || ((tot_inside == 1) && (oldtot == 2))) { - for (int i = 0; i < gps->totpoints; i++) { - pt = &gps->points[i]; - pt->flag |= GP_SPOINT_SELECT; - } - } } - GP_EDITABLE_STROKES_END(gpstroke_iter); - /* dissolve selected points */ + /* Dissolve selected points. */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - if (gpl->flag & GP_LAYER_LOCKED) { - continue; - } - - bGPDframe *gpf = gpl->actframe; - if (gpf == NULL) { - continue; - } - LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - if (gps->flag & GP_STROKE_SELECT) { - gpencil_cutter_dissolve(gpd, gpl, gps, flat_caps); + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + bGPDframe *gpf_act = gpl->actframe; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + gpl->actframe = gpf; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_SELECT) { + gpencil_cutter_dissolve(gpd, gpl, gps, flat_caps); + } + } + /* if not multiedit, exit loop. */ + if (!is_multiedit) { + break; } } + gpl->actframe = gpf_act; } /* updates */ diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index e3e73d5deb5..f595d0f5b35 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -26,6 +26,7 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_math.h" #include "BLI_stack.h" #include "BLI_utildefines.h" @@ -83,6 +84,15 @@ #define LEAK_VERT 1 #define MIN_WINDOW_SIZE 128 +/* Set to 1 to debug filling internal image. By default, the value must be 0. */ +#define FILL_DEBUG 0 + +/* Duplicated: etempFlags */ +const enum { + GP_DRAWFILLS_NOSTATUS = (1 << 0), /* don't draw status info */ + GP_DRAWFILLS_ONLY3D = (1 << 1), /* only draw 3d-strokes */ +}; + /* Temporary fill operation data (op->customdata) */ typedef struct tGPDfill { bContext *C; @@ -112,6 +122,8 @@ typedef struct tGPDfill { struct bGPDlayer *gpl; /** frame */ struct bGPDframe *gpf; + /** Temp mouse position stroke. */ + struct bGPDstroke *gps_mouse; /** flags */ short flag; @@ -119,9 +131,12 @@ typedef struct tGPDfill { short oldkey; /** send to back stroke */ bool on_back; - + /** Flag for render mode */ + bool is_render; + /** Flag to check something was done. */ + bool done; /** mouse fill center position */ - int center[2]; + int mouse[2]; /** windows width */ int sizex; /** window height */ @@ -163,8 +178,178 @@ typedef struct tGPDfill { int bwiny; rcti brect; + /* Space Conversion Data */ + GP_SpaceConversion gsc; + + /** Zoom factor. */ + float zoom; + + /** Factor of extension. */ + float fill_extend_fac; + } tGPDfill; +bool skip_layer_check(short fill_layer_mode, int gpl_active_index, int gpl_index); +static void gpencil_draw_boundary_lines(const struct bContext *UNUSED(C), struct tGPDfill *tgpf); + +/* Delete any temporary stroke. */ +static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_frames) +{ + LISTBASE_FOREACH (bGPDlayer *, gpl, &tgpf->gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + + bGPDframe *init_gpf = (all_frames) ? gpl->frames.first : + BKE_gpencil_layer_frame_get( + gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV); + if (init_gpf == NULL) { + continue; + } + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + /* free stroke */ + if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) { + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + } + if (!all_frames) { + break; + } + } + } +} + +static void extrapolate_points_by_length(bGPDspoint *a, + bGPDspoint *b, + float length, + float r_point[3]) +{ + float ab[3]; + sub_v3_v3v3(ab, &b->x, &a->x); + normalize_v3(ab); + mul_v3_fl(ab, length); + add_v3_v3v3(r_point, &b->x, ab); +} + +/* Loop all layers create stroke extensions. */ +static void gpencil_create_extensions(tGPDfill *tgpf) +{ + Object *ob = tgpf->ob; + bGPdata *gpd = tgpf->gpd; + Brush *brush = tgpf->brush; + BrushGpencilSettings *brush_settings = brush->gpencil_settings; + + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd); + BLI_assert(gpl_active != NULL); + + const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); + BLI_assert(gpl_active_index >= 0); + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + + /* Decide if the strokes of layers are included or not depending on the layer mode. */ + const int gpl_index = BLI_findindex(&gpd->layers, gpl); + bool skip = skip_layer_check(brush_settings->fill_layer_mode, gpl_active_index, gpl_index); + if (skip) { + continue; + } + + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV); + if (gpf == NULL) { + continue; + } + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* Check if stroke can be drawn. */ + if ((gps->points == NULL) || (gps->totpoints < 2)) { + continue; + } + if (gps->flag & (GP_STROKE_NOFILL | GP_STROKE_TAG)) { + continue; + } + /* Check if the color is visible. */ + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE)) { + continue; + } + + /* Extend start. */ + bGPDspoint *pt0 = &gps->points[1]; + bGPDspoint *pt1 = &gps->points[0]; + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x); + + /* Extend end. */ + pt0 = &gps->points[gps->totpoints - 2]; + pt1 = &gps->points[gps->totpoints - 1]; + gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x); + } + } +} + +static void gpencil_update_extend(tGPDfill *tgpf) +{ + gpencil_delete_temp_stroke_extension(tgpf, false); + + if (tgpf->fill_extend_fac > 0.0f) { + gpencil_create_extensions(tgpf); + } + WM_event_add_notifier(tgpf->C, NC_GPENCIL | NA_EDITED, NULL); +} + +static bool gpencil_stroke_is_drawable(tGPDfill *tgpf, bGPDstroke *gps) +{ + if (tgpf->is_render) { + return true; + } + + const bool show_help = (tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) != 0; + const bool show_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) != 0; + const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG); + + if ((!show_help) && (show_extend)) { + if (!is_extend) { + return false; + } + } + + if ((show_help) && (!show_extend)) { + if (is_extend) { + return false; + } + } + + return true; +} + /* draw a given stroke using same thickness and color for all points */ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, bGPDstroke *gps, @@ -172,7 +357,8 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, const bool cyclic, const float ink[4], const int flag, - const float thershold) + const float thershold, + const float thickness) { bGPDspoint *points = gps->points; @@ -182,9 +368,19 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, int totpoints = gps->totpoints; float fpt[3]; float col[4]; + const float extend_col[4] = {0.0f, 1.0f, 1.0f, 1.0f}; + const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG); - copy_v4_v4(col, ink); + if (!gpencil_stroke_is_drawable(tgpf, gps)) { + return; + } + if ((is_extend) && (!tgpf->is_render)) { + copy_v4_v4(col, extend_col); + } + else { + copy_v4_v4(col, ink); + } /* if cyclic needs more vertex */ int cyclic_add = (cyclic) ? 1 : 0; @@ -195,7 +391,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); /* draw stroke curve */ - GPU_line_width(1.0f); + GPU_line_width((!is_extend) ? thickness : thickness * 2.0f); immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints + cyclic_add); const bGPDspoint *pt = points; @@ -226,15 +422,77 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, immUnbindProgram(); } -/* loop all layers */ -static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) +static void draw_mouse_position(tGPDfill *tgpf) { - /* duplicated: etempFlags */ - enum { - GP_DRAWFILLS_NOSTATUS = (1 << 0), /* don't draw status info */ - GP_DRAWFILLS_ONLY3D = (1 << 1), /* only draw 3d-strokes */ - }; + if (tgpf->gps_mouse == NULL) { + return; + } + uchar mouse_color[4] = {0, 0, 255, 255}; + + bGPDspoint *pt = &tgpf->gps_mouse->points[0]; + float point_size = (tgpf->zoom == 1.0f) ? 4.0f * tgpf->fill_factor : + (0.5f * tgpf->zoom) + tgpf->fill_factor; + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); + + /* Draw mouse click position in Blue. */ + immBindBuiltinProgram(GPU_SHADER_3D_POINT_FIXED_SIZE_VARYING_COLOR); + GPU_point_size(point_size); + immBegin(GPU_PRIM_POINTS, 1); + immAttr4ubv(col, mouse_color); + immVertex3fv(pos, &pt->x); + immEnd(); + immUnbindProgram(); +} + +/* Helper: Check if must skip the layer */ +bool skip_layer_check(short fill_layer_mode, int gpl_active_index, int gpl_index) +{ + bool skip = false; + + switch (fill_layer_mode) { + case GP_FILL_GPLMODE_ACTIVE: { + if (gpl_index != gpl_active_index) { + skip = true; + } + break; + } + case GP_FILL_GPLMODE_ABOVE: { + if (gpl_index != gpl_active_index + 1) { + skip = true; + } + break; + } + case GP_FILL_GPLMODE_BELOW: { + if (gpl_index != gpl_active_index - 1) { + skip = true; + } + break; + } + case GP_FILL_GPLMODE_ALL_ABOVE: { + if (gpl_index <= gpl_active_index) { + skip = true; + } + break; + } + case GP_FILL_GPLMODE_ALL_BELOW: { + if (gpl_index >= gpl_active_index) { + skip = true; + } + break; + } + case GP_FILL_GPLMODE_VISIBLE: + default: + break; + } + + return skip; +} +/* Loop all layers to draw strokes. */ +static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) +{ Object *ob = tgpf->ob; bGPdata *gpd = tgpf->gpd; Brush *brush = tgpf->brush; @@ -248,8 +506,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) tgpw.gpd = gpd; tgpw.offsx = 0; tgpw.offsy = 0; - tgpw.winx = tgpf->region->winx; - tgpw.winy = tgpf->region->winy; + tgpw.winx = tgpf->sizex; + tgpw.winy = tgpf->sizey; tgpw.dflag = 0; tgpw.disable_fill = 1; tgpw.dflag |= (GP_DRAWFILLS_ONLY3D | GP_DRAWFILLS_NOSTATUS); @@ -262,6 +520,9 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); BLI_assert(gpl_active_index >= 0); + /* Draw blue point where click with mouse. */ + draw_mouse_position(tgpf); + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* do not draw layer if hidden */ if (gpl->flag & GP_LAYER_HIDE) { @@ -273,43 +534,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) /* Decide if the strokes of layers are included or not depending on the layer mode. * Cannot skip the layer because it can use boundary strokes and must be used. */ - bool skip = false; const int gpl_index = BLI_findindex(&gpd->layers, gpl); - switch (brush_settings->fill_layer_mode) { - case GP_FILL_GPLMODE_ACTIVE: { - if (gpl_index != gpl_active_index) { - skip = true; - } - break; - } - case GP_FILL_GPLMODE_ABOVE: { - if (gpl_index != gpl_active_index + 1) { - skip = true; - } - break; - } - case GP_FILL_GPLMODE_BELOW: { - if (gpl_index != gpl_active_index - 1) { - skip = true; - } - break; - } - case GP_FILL_GPLMODE_ALL_ABOVE: { - if (gpl_index <= gpl_active_index) { - skip = true; - } - break; - } - case GP_FILL_GPLMODE_ALL_BELOW: { - if (gpl_index >= gpl_active_index) { - skip = true; - } - break; - } - case GP_FILL_GPLMODE_VISIBLE: - default: - break; - } + bool skip = skip_layer_check(brush_settings->fill_layer_mode, gpl_active_index, gpl_index); /* if active layer and no keyframe, create a new one */ if (gpl == tgpf->gpl) { @@ -352,17 +578,19 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) tgpw.gpf = gpf; tgpw.t_gpf = gpf; - /* reduce thickness to avoid gaps */ tgpw.is_fill_stroke = (tgpf->fill_draw_mode == GP_FILL_DMODE_CONTROL) ? false : true; + /* Reduce thickness to avoid gaps. */ tgpw.lthick = gpl->line_change; tgpw.opacity = 1.0; copy_v4_v4(tgpw.tintcolor, ink); tgpw.onion = true; tgpw.custonion = true; - /* normal strokes */ + /* Normal strokes. */ if (ELEM(tgpf->fill_draw_mode, GP_FILL_DMODE_STROKE, GP_FILL_DMODE_BOTH)) { - ED_gpencil_draw_fill(&tgpw); + if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0)) { + ED_gpencil_draw_fill(&tgpw); + } } /* 3D Lines with basic shapes and invisible lines */ @@ -373,7 +601,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) gps->flag & GP_STROKE_CYCLIC, ink, tgpf->flag, - tgpf->fill_threshold); + tgpf->fill_threshold, + 1.0f); } } } @@ -392,8 +621,8 @@ static bool gpencil_render_offscreen(tGPDfill *tgpf) } /* set temporary new size */ - tgpf->bwinx = tgpf->region->winx; - tgpf->bwiny = tgpf->region->winy; + tgpf->bwinx = tgpf->region->sizex; + tgpf->bwiny = tgpf->region->sizey; tgpf->brect = tgpf->region->winrct; /* resize region */ @@ -408,12 +637,6 @@ static bool gpencil_render_offscreen(tGPDfill *tgpf) tgpf->sizex = (int)tgpf->region->winx; tgpf->sizey = (int)tgpf->region->winy; - /* adjust center */ - float center[2]; - center[0] = (float)tgpf->center[0] * ((float)tgpf->region->winx / (float)tgpf->bwinx); - center[1] = (float)tgpf->center[1] * ((float)tgpf->region->winy / (float)tgpf->bwiny); - round_v2i_v2fl(tgpf->center, center); - char err_out[256] = "unknown"; GPUOffScreen *offscreen = GPU_offscreen_create(tgpf->sizex, tgpf->sizey, true, false, err_out); if (offscreen == NULL) { @@ -422,7 +645,7 @@ static bool gpencil_render_offscreen(tGPDfill *tgpf) } GPU_offscreen_bind(offscreen, true); - uint flag = IB_rect | IB_rectfloat; + uint flag = IB_rectfloat; ImBuf *ibuf = IMB_allocImBuf(tgpf->sizex, tgpf->sizey, 32, flag); rctf viewplane; @@ -437,6 +660,21 @@ static bool gpencil_render_offscreen(tGPDfill *tgpf) &clip_start, &clip_end, NULL); + + /* Rescale viewplane to fit all strokes. */ + float width = viewplane.xmax - viewplane.xmin; + float height = viewplane.ymax - viewplane.ymin; + + float width_new = width * tgpf->zoom; + float height_new = height * tgpf->zoom; + float scale_x = (width_new - width) / 2.0f; + float scale_y = (height_new - height) / 2.0f; + + viewplane.xmin -= scale_x; + viewplane.xmax += scale_x; + viewplane.ymin -= scale_y; + viewplane.ymax += scale_y; + if (is_ortho) { orthographic_m4(winmat, viewplane.xmin, @@ -506,35 +744,23 @@ static bool gpencil_render_offscreen(tGPDfill *tgpf) /* return pixel data (rgba) at index */ static void get_pixel(const ImBuf *ibuf, const int idx, float r_col[4]) { - if (ibuf->rect_float) { - const float *frgba = &ibuf->rect_float[idx * 4]; - copy_v4_v4(r_col, frgba); - } - else { - /* XXX: This case probably doesn't happen, as we only write to the float buffer, - * but we get compiler warnings about uninitialized vars otherwise - */ - BLI_assert(!"gpencil_fill.c - get_pixel() non-float case is used!"); - zero_v4(r_col); - } + BLI_assert(ibuf->rect_float != NULL); + memcpy(r_col, &ibuf->rect_float[idx * 4], sizeof(float[4])); } /* set pixel data (rgba) at index */ static void set_pixel(ImBuf *ibuf, int idx, const float col[4]) { - // BLI_assert(idx <= ibuf->x * ibuf->y); - if (ibuf->rect) { - uint *rrect = &ibuf->rect[idx]; - uchar ccol[4]; - - rgba_float_to_uchar(ccol, col); - *rrect = *((uint *)ccol); - } + BLI_assert(ibuf->rect_float != NULL); + float *rrectf = &ibuf->rect_float[idx * 4]; + copy_v4_v4(rrectf, col); +} - if (ibuf->rect_float) { - float *rrectf = &ibuf->rect_float[idx * 4]; - copy_v4_v4(rrectf, col); - } +/* Helper: Check if one image row is empty. */ +static bool is_row_filled(const ImBuf *ibuf, const int row_index) +{ + float *row = &ibuf->rect_float[ibuf->x * 4 * row_index]; + return (row[0] == 0.0f && memcmp(row, row + 1, ((ibuf->x * 4) - 1) * sizeof(float)) != 0); } /** @@ -542,6 +768,9 @@ static void set_pixel(ImBuf *ibuf, int idx, const float col[4]) * this is used for strokes with small gaps between them to get a full fill * and do not get a full screen fill. * + * This function assumes that if the furthest pixel is occupied, + * the other pixels are occupied. + * * \param ibuf: Image pixel data. * \param maxpixel: Maximum index. * \param limit: Limit of pixels to analyze. @@ -551,10 +780,10 @@ static void set_pixel(ImBuf *ibuf, int idx, const float col[4]) static bool is_leak_narrow(ImBuf *ibuf, const int maxpixel, int limit, int index, int type) { float rgba[4]; - int i; int pt; bool t_a = false; bool t_b = false; + const int extreme = limit - 1; /* Horizontal leak (check vertical pixels) * X @@ -565,37 +794,29 @@ static bool is_leak_narrow(ImBuf *ibuf, const int maxpixel, int limit, int index */ if (type == LEAK_HORZ) { /* pixels on top */ - for (i = 1; i <= limit; i++) { - pt = index + (ibuf->x * i); - if (pt <= maxpixel) { - get_pixel(ibuf, pt, rgba); - if (rgba[0] == 1.0f) { - t_a = true; - break; - } - } - else { - /* edge of image*/ + pt = index + (ibuf->x * extreme); + if (pt <= maxpixel) { + get_pixel(ibuf, pt, rgba); + if (rgba[0] == 1.0f) { t_a = true; - break; } } + else { + /* edge of image*/ + t_a = true; + } /* pixels on bottom */ - for (i = 1; i <= limit; i++) { - pt = index - (ibuf->x * i); - if (pt >= 0) { - get_pixel(ibuf, pt, rgba); - if (rgba[0] == 1.0f) { - t_b = true; - break; - } - } - else { - /* edge of image*/ + pt = index - (ibuf->x * extreme); + if (pt >= 0) { + get_pixel(ibuf, pt, rgba); + if (rgba[0] == 1.0f) { t_b = true; - break; } } + else { + /* edge of image*/ + t_b = true; + } } /* Vertical leak (check horizontal pixels) @@ -609,35 +830,27 @@ static bool is_leak_narrow(ImBuf *ibuf, const int maxpixel, int limit, int index int higpix = lowpix + ibuf->x - 1; /* pixels to right */ - for (i = 0; i < limit; i++) { - pt = index - (limit - i); - if (pt >= lowpix) { - get_pixel(ibuf, pt, rgba); - if (rgba[0] == 1.0f) { - t_a = true; - break; - } - } - else { - t_a = true; /* edge of image*/ - break; + pt = index - extreme; + if (pt >= lowpix) { + get_pixel(ibuf, pt, rgba); + if (rgba[0] == 1.0f) { + t_a = true; } } + else { + t_a = true; /* edge of image*/ + } /* pixels to left */ - for (i = 0; i < limit; i++) { - pt = index + (limit - i); - if (pt <= higpix) { - get_pixel(ibuf, pt, rgba); - if (rgba[0] == 1.0f) { - t_b = true; - break; - } - } - else { - t_b = true; /* edge of image */ - break; + pt = index + extreme; + if (pt <= higpix) { + get_pixel(ibuf, pt, rgba); + if (rgba[0] == 1.0f) { + t_b = true; } } + else { + t_b = true; /* edge of image */ + } } return (bool)(t_a && t_b); } @@ -660,10 +873,21 @@ static void gpencil_boundaryfill_area(tGPDfill *tgpf) BLI_Stack *stack = BLI_stack_new(sizeof(int), __func__); - /* calculate index of the seed point using the position of the mouse */ - int index = (tgpf->sizex * tgpf->center[1]) + tgpf->center[0]; + /* Calculate index of the seed point using the position of the mouse looking + * for a blue pixel. */ + int index = -1; + for (int i = 0; i < maxpixel; i++) { + get_pixel(ibuf, i, rgba); + if (rgba[2] == 1.0f) { + index = i; + break; + } + } + if ((index >= 0) && (index <= maxpixel)) { - BLI_stack_push(stack, &index); + if (!FILL_DEBUG) { + BLI_stack_push(stack, &index); + } } /* the fill use a stack to save the pixel list instead of the common recursive @@ -774,18 +998,24 @@ static void gpencil_invert_image(tGPDfill *tgpf) ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock); const int maxpixel = (ibuf->x * ibuf->y) - 1; + const int center = ibuf->x / 2; for (int v = maxpixel; v != 0; v--) { float color[4]; get_pixel(ibuf, v, color); - /* Green. */ + /* Green->Red. */ if (color[1] == 1.0f) { set_pixel(ibuf, v, fill_col[0]); } + /* Red->Green */ else if (color[0] == 1.0f) { set_pixel(ibuf, v, fill_col[1]); + /* Add thickness of 2 pixels to avoid too thin lines. */ + int offset = (v % ibuf->x < center) ? 1 : -1; + set_pixel(ibuf, v + offset, fill_col[1]); } else { + /* Set to Transparent. */ set_pixel(ibuf, v, fill_col[2]); } } @@ -822,21 +1052,37 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf) float rgba[4]; for (int idy = 0; idy < ibuf->y; idy++) { - bool clear = false; + int init = -1; + int end = -1; 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; + if (init < 0) { + init = image_idx; + } + else { + end = image_idx; + } } /* Red. */ else if (rgba[0] == 1.0f) { - clear = false; + if (init > -1) { + for (int i = init; i <= max_ii(init, end); i++) { + set_pixel(ibuf, i, clear_col); + } + init = -1; + end = -1; + } } - if (clear) { - set_pixel(ibuf, image_idx, clear_col); + } + /* Check last segment. */ + if (init > -1) { + for (int i = init; i <= max_ii(init, end); i++) { + set_pixel(ibuf, i, clear_col); } + set_pixel(ibuf, init, clear_col); } } @@ -857,87 +1103,98 @@ static void gpencil_erase_processed_area(tGPDfill *tgpf) * XXXX * ----------- */ -static void dilate_shape(ImBuf *ibuf) +static bool dilate_shape(ImBuf *ibuf) { + bool done = false; + BLI_Stack *stack = BLI_stack_new(sizeof(int), __func__); const float green[4] = {0.0f, 1.0f, 0.0f, 1.0f}; - const int maxpixel = (ibuf->x * ibuf->y) - 1; + // const int maxpixel = (ibuf->x * ibuf->y) - 1; /* detect pixels and expand into red areas */ - for (int v = maxpixel; v != 0; v--) { - float color[4]; - int index; - int tp = 0; - int bm = 0; - int lt = 0; - int rt = 0; - get_pixel(ibuf, v, color); - if (color[1] == 1.0f) { - /* pixel left */ - if (v - 1 >= 0) { - index = v - 1; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); - lt = index; + for (int row = 0; row < ibuf->y; row++) { + if (!is_row_filled(ibuf, row)) { + continue; + } + int maxpixel = (ibuf->x * (row + 1)) - 1; + int minpixel = ibuf->x * row; + + for (int v = maxpixel; v != minpixel; v--) { + float color[4]; + int index; + get_pixel(ibuf, v, color); + if (color[1] == 1.0f) { + int tp = 0; + int bm = 0; + int lt = 0; + int rt = 0; + + /* pixel left */ + if (v - 1 >= 0) { + index = v - 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + lt = index; + } } - } - /* pixel right */ - if (v + 1 <= maxpixel) { - index = v + 1; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); - rt = index; + /* pixel right */ + if (v + 1 <= maxpixel) { + index = v + 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + rt = index; + } } - } - /* pixel top */ - if (v + ibuf->x <= maxpixel) { - index = v + ibuf->x; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); - tp = index; + /* pixel top */ + if (v + (ibuf->x * 1) <= maxpixel) { + index = v + (ibuf->x * 1); + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + tp = index; + } } - } - /* pixel bottom */ - if (v - ibuf->x >= 0) { - index = v - ibuf->x; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); - bm = index; + /* pixel bottom */ + if (v - (ibuf->x * 1) >= 0) { + index = v - (ibuf->x * 1); + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + bm = index; + } } - } - /* pixel top-left */ - if (tp && lt) { - index = tp - 1; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); + /* pixel top-left */ + if (tp && lt) { + index = tp - 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } } - } - /* pixel top-right */ - if (tp && rt) { - index = tp + 1; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); + /* pixel top-right */ + if (tp && rt) { + index = tp + 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } } - } - /* pixel bottom-left */ - if (bm && lt) { - index = bm - 1; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); + /* pixel bottom-left */ + if (bm && lt) { + index = bm - 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } } - } - /* pixel bottom-right */ - if (bm && rt) { - index = bm + 1; - get_pixel(ibuf, index, color); - if (color[0] == 1.0f) { - BLI_stack_push(stack, &index); + /* pixel bottom-right */ + if (bm && rt) { + index = bm + 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } } } } @@ -947,8 +1204,11 @@ static void dilate_shape(ImBuf *ibuf) int v; BLI_stack_pop(stack, &v); set_pixel(ibuf, v, green); + done = true; } BLI_stack_free(stack); + + return done; } /* Get the outline points of a shape using Moore Neighborhood algorithm @@ -964,11 +1224,12 @@ static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) int v[2]; int boundary_co[2]; int start_co[2]; + int first_co[2] = {-1, -1}; int backtracked_co[2]; int current_check_co[2]; int prev_check_co[2]; int backtracked_offset[1][2] = {{0, 0}}; - // bool boundary_found = false; + bool first_pixel = false; bool start_found = false; const int NEIGHBOR_COUNT = 8; @@ -993,7 +1254,6 @@ static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) dilate_shape(ibuf); } - /* find the initial point to start outline analysis */ for (int idx = imagesize - 1; idx != 0; idx--) { get_pixel(ibuf, idx, rgba); if (rgba[1] == 1.0f) { @@ -1046,12 +1306,17 @@ static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) cur_back_offset++; loop++; } - /* current pixel is equal to starting pixel */ - if (boundary_co[0] == start_co[0] && boundary_co[1] == start_co[1]) { + /* Current pixel is equal to starting or firt pixel. */ + if ((boundary_co[0] == start_co[0] && boundary_co[1] == start_co[1]) || + (boundary_co[0] == first_co[0] && boundary_co[1] == first_co[1])) { BLI_stack_pop(tgpf->stack, &v); - // boundary_found = true; break; } + + if (!first_pixel) { + first_pixel = true; + copy_v2_v2_int(first_co, boundary_co); + } } /* release ibuf */ @@ -1172,6 +1437,9 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) return; } + /* Set as done. */ + tgpf->done = true; + /* Get frame or create a new one. */ tgpf->gpf = BKE_gpencil_layer_frame_get(tgpf->gpl, tgpf->active_cfra, GP_GETFRAME_ADD_NEW); @@ -1327,7 +1595,6 @@ static void gpencil_fill_draw_3d(const bContext *C, ARegion *UNUSED(region), voi if (region != tgpf->region) { return; } - gpencil_draw_boundary_lines(C, tgpf); } @@ -1378,6 +1645,10 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *UNUSED(op)) tgpf->win = CTX_wm_window(C); tgpf->active_cfra = CFRA; + /* Setup space conversions. */ + gpencil_point_conversion_init(C, &tgpf->gsc); + tgpf->zoom = 1.0f; + /* set GP datablock */ tgpf->gpd = gpd; tgpf->gpl = BKE_gpencil_layer_active_get(gpd); @@ -1388,6 +1659,7 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *UNUSED(op)) tgpf->lock_axis = ts->gp_sculpt.lock_axis; tgpf->oldkey = -1; + tgpf->is_render = false; tgpf->sbuffer_used = 0; tgpf->sbuffer = NULL; tgpf->depth_arr = NULL; @@ -1399,8 +1671,9 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *UNUSED(op)) tgpf->fill_threshold = brush->gpencil_settings->fill_threshold; tgpf->fill_simplylvl = brush->gpencil_settings->fill_simplylvl; tgpf->fill_draw_mode = brush->gpencil_settings->fill_draw_mode; + tgpf->fill_extend_fac = brush->gpencil_settings->fill_extend_fac; tgpf->fill_factor = max_ff(GPENCIL_MIN_FILL_FAC, - min_ff(brush->gpencil_settings->fill_factor, 8.0f)); + min_ff(brush->gpencil_settings->fill_factor, GPENCIL_MAX_FILL_FAC)); tgpf->fill_leak = (int)ceil((float)brush->gpencil_settings->fill_leak * tgpf->fill_factor); int totcol = tgpf->ob->totcol; @@ -1426,7 +1699,6 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *UNUSED(op)) /* end operator */ static void gpencil_fill_exit(bContext *C, wmOperator *op) { - Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); /* clear undo stack */ @@ -1445,16 +1717,14 @@ static void gpencil_fill_exit(bContext *C, wmOperator *op) MEM_SAFE_FREE(tgpf->sbuffer); MEM_SAFE_FREE(tgpf->depth_arr); + /* Remove any temp stroke. */ + gpencil_delete_temp_stroke_extension(tgpf, true); + /* remove drawing handler */ if (tgpf->draw_handle_3d) { ED_region_draw_cb_exit(tgpf->region->type, tgpf->draw_handle_3d); } - /* Delete temp image. */ - if (tgpf->ima) { - BKE_id_free(bmain, tgpf->ima); - } - /* finally, free memory used by temp data */ MEM_freeN(tgpf); } @@ -1538,7 +1808,11 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE tgpf = op->customdata; /* Enable custom drawing handlers to show help lines */ - if (tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) { + const bool do_extend = (tgpf->fill_extend_fac > 0.0f); + const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || + ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend))); + + if (help_lines) { tgpf->draw_handle_3d = ED_region_draw_cb_activate( tgpf->region->type, gpencil_fill_draw_3d, tgpf, REGION_DRAW_POST_VIEW); } @@ -1556,17 +1830,210 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE return OPERATOR_RUNNING_MODAL; } +/* Helper: Calc the maximum bounding box size of strokes to get the zoom level of the viewport. + * For each stroke, the 2D projected bounding box is calculated and using this data, the total + * object bounding box (all strokes) is calculated. */ +static void gpencil_zoom_level_set(tGPDfill *tgpf) +{ + Brush *brush = tgpf->brush; + if (brush->gpencil_settings->flag & GP_BRUSH_FILL_FIT_DISABLE) { + tgpf->zoom = 1.0f; + return; + } + + Object *ob = tgpf->ob; + bGPdata *gpd = tgpf->gpd; + BrushGpencilSettings *brush_settings = tgpf->brush->gpencil_settings; + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd); + BLI_assert(gpl_active != NULL); + + const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); + BLI_assert(gpl_active_index >= 0); + + /* Init maximum boundbox size. */ + rctf rect_max; + const float winx_half = tgpf->region->winx / 2.0f; + const float winy_half = tgpf->region->winy / 2.0f; + BLI_rctf_init(&rect_max, + 0.0f - winx_half, + tgpf->region->winx + winx_half, + 0.0f - winy_half, + tgpf->region->winy + winy_half); + + float objectbox_min[2], objectbox_max[2]; + INIT_MINMAX2(objectbox_min, objectbox_max); + rctf rect_bound; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + float diff_mat[4][4]; + /* calculate parent matrix */ + BKE_gpencil_layer_transform_matrix_get(tgpf->depsgraph, ob, gpl, diff_mat); + + /* Decide if the strokes of layers are included or not depending on the layer mode. + * Cannot skip the layer because it can use boundary strokes and must be used. */ + const int gpl_index = BLI_findindex(&gpd->layers, gpl); + bool skip = skip_layer_check(brush_settings->fill_layer_mode, gpl_active_index, gpl_index); + + /* Get frame to check. */ + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV); + if (gpf == NULL) { + continue; + } + + /* Read all strokes. */ + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* check if stroke can be drawn */ + if ((gps->points == NULL) || (gps->totpoints < 2)) { + continue; + } + /* check if the color is visible */ + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE)) { + continue; + } + + /* If the layer must be skipped, but the stroke is not boundary, skip stroke. */ + if ((skip) && ((gps->flag & GP_STROKE_NOFILL) == 0)) { + continue; + } + + float boundbox_min[2]; + float boundbox_max[2]; + ED_gpencil_projected_2d_bound_box(&tgpf->gsc, gps, diff_mat, boundbox_min, boundbox_max); + minmax_v2v2_v2(objectbox_min, objectbox_max, boundbox_min); + minmax_v2v2_v2(objectbox_min, objectbox_max, boundbox_max); + } + } + /* Clamp max bound box. */ + BLI_rctf_init( + &rect_bound, objectbox_min[0], objectbox_max[0], objectbox_min[1], objectbox_max[1]); + float r_xy[2]; + BLI_rctf_clamp(&rect_bound, &rect_max, r_xy); + + /* Calculate total width used. */ + float width = tgpf->region->winx; + if (rect_bound.xmin < 0.0f) { + width -= rect_bound.xmin; + } + if (rect_bound.xmax > tgpf->region->winx) { + width += rect_bound.xmax - tgpf->region->winx; + } + /* Calculate total height used. */ + float height = tgpf->region->winy; + if (rect_bound.ymin < 0.0f) { + height -= rect_bound.ymin; + } + if (rect_bound.ymax > tgpf->region->winy) { + height += rect_bound.ymax - tgpf->region->winy; + } + + width = ceilf(width); + height = ceilf(height); + + float zoomx = (width > tgpf->region->winx) ? width / (float)tgpf->region->winx : 1.0f; + float zoomy = (height > tgpf->region->winy) ? height / (float)tgpf->region->winy : 1.0f; + if ((zoomx != 1.0f) || (zoomy != 1.0f)) { + tgpf->zoom = min_ff(max_ff(zoomx, zoomy) * 1.5f, 5.0f); + } +} + +static bool gpencil_do_frame_fill(tGPDfill *tgpf, const bool is_inverted) +{ + wmWindow *win = CTX_wm_window(tgpf->C); + + /* render screen to temp image */ + int totpoints = 1; + if (gpencil_render_offscreen(tgpf)) { + + /* Set red borders to create a external limit. */ + gpencil_set_borders(tgpf, true); + + /* 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); + WM_cursor_time(win, 50); + int totpoints_prv = 0; + int loop_limit = 0; + while (totpoints > 0) { + /* analyze outline */ + gpencil_get_outline_points(tgpf, (totpoints == 1) ? true : false); + + /* create array of points from stack */ + totpoints = gpencil_points_from_stack(tgpf); + + /* create z-depth array for reproject */ + gpencil_get_depth_array(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); + } + WM_cursor_time(win, 100); + + /* Free memory. */ + MEM_SAFE_FREE(tgpf->sbuffer); + MEM_SAFE_FREE(tgpf->depth_arr); + + /* Limit very small areas. */ + if (totpoints < 3) { + break; + } + /* Limit infinite loops is some corner cases. */ + if (totpoints_prv == totpoints) { + loop_limit++; + if (loop_limit > 3) { + break; + } + } + totpoints_prv = totpoints; + } + + /* Delete temp image. */ + if ((tgpf->ima) && (!FILL_DEBUG)) { + BKE_id_free(tgpf->bmain, tgpf->ima); + } + + return true; + } + + return false; +} + /* events handling during interactive part of operator */ 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; + tgpf->on_back = RNA_boolean_get(op->ptr, "on_back"); + 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 */ + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(tgpf->gpd); + const bool do_extend = (tgpf->fill_extend_fac > 0.0f); + const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || + ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend))); + int estate = OPERATOR_RUNNING_MODAL; switch (event->type) { case EVT_ESCKEY: @@ -1574,82 +2041,104 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) estate = OPERATOR_CANCELLED; break; case LEFTMOUSE: - tgpf->on_back = RNA_boolean_get(op->ptr, "on_back"); /* first time the event is not enabled to show help lines. */ - if ((tgpf->oldkey != -1) || ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) == 0)) { + if ((tgpf->oldkey != -1) || (!help_lines)) { ARegion *region = BKE_area_find_region_xy( CTX_wm_area(C), RGN_TYPE_ANY, event->x, event->y); if (region) { bool in_bounds = false; - /* Perform bounds check */ in_bounds = BLI_rcti_isect_pt(®ion->winrct, event->x, event->y); if ((in_bounds) && (region->regiontype == RGN_TYPE_WINDOW)) { - tgpf->center[0] = event->mval[0]; - tgpf->center[1] = event->mval[1]; - - /* Set active frame as current for filling. */ - tgpf->active_cfra = CFRA; + tgpf->mouse[0] = event->mval[0]; + tgpf->mouse[1] = event->mval[1]; + tgpf->is_render = true; + /* Define Zoom level. */ + gpencil_zoom_level_set(tgpf); + + /* Create Temp stroke. */ + tgpf->gps_mouse = BKE_gpencil_stroke_new(0, 1, 10.0f); + tGPspoint point2D; + bGPDspoint *pt = &tgpf->gps_mouse->points[0]; + copy_v2fl_v2i(&point2D.x, tgpf->mouse); + gpencil_stroke_convertcoords_tpoint( + tgpf->scene, tgpf->region, tgpf->ob, &point2D, NULL, &pt->x); + + /* If not multiframe and there is no frame in CFRA for the active layer, create + * a new frame before to make the hash function can find something. */ + if (!is_multiedit) { + tgpf->gpf = BKE_gpencil_layer_frame_get( + tgpf->gpl, tgpf->active_cfra, GP_GETFRAME_ADD_NEW); + tgpf->gpf->flag |= GP_FRAME_SELECT; + } - /* render screen to temp image */ - int totpoints = 1; - if (gpencil_render_offscreen(tgpf)) { + /* Hash of selected frames.*/ + GHash *frame_list = BLI_ghash_int_new_ex(__func__, 64); + BKE_gpencil_frame_selected_hash(tgpf->gpd, frame_list); - /* Set red borders to create a external limit. */ - gpencil_set_borders(tgpf, true); + /* Loop all frames. */ + wmWindow *win = CTX_wm_window(C); - /* apply boundary fill */ - gpencil_boundaryfill_area(tgpf); + GHashIterator gh_iter; + int total = BLI_ghash_len(frame_list); + int i = 1; + GHASH_ITER (gh_iter, frame_list) { + /* Set active frame as current for filling. */ + tgpf->active_cfra = POINTER_AS_INT(BLI_ghashIterator_getKey(&gh_iter)); + int step = ((float)i / (float)total) * 100.0f; + WM_cursor_time(win, step); - /* Invert direction if press Ctrl. */ - if (is_inverted) { - gpencil_invert_image(tgpf); + if (do_extend) { + gpencil_update_extend(tgpf); } - /* Clean borders to avoid infinite loops. */ - gpencil_set_borders(tgpf, false); - - while (totpoints > 0) { - /* analyze outline */ - gpencil_get_outline_points(tgpf, (totpoints == 1) ? true : false); - - /* create array of points from stack */ - totpoints = gpencil_points_from_stack(tgpf); - - /* create z-depth array for reproject */ - gpencil_get_depth_array(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); + /* Repeat loop until get something. */ + tgpf->done = false; + int loop_limit = 0; + while ((!tgpf->done) && (loop_limit < 2)) { + WM_cursor_time(win, loop_limit + 1); + /* Render screen to temp image and do fill. */ + gpencil_do_frame_fill(tgpf, is_inverted); + + /* restore size */ + tgpf->region->winx = (short)tgpf->bwinx; + tgpf->region->winy = (short)tgpf->bwiny; + tgpf->region->winrct = tgpf->brect; + if (!tgpf->done) { + /* If the zoom was not set before, avoid a loop. */ + if (tgpf->zoom == 1.0f) { + loop_limit++; + } + else { + tgpf->zoom = 1.0f; + tgpf->fill_factor = max_ff( + GPENCIL_MIN_FILL_FAC, + min_ff(brush->gpencil_settings->fill_factor, GPENCIL_MAX_FILL_FAC)); + } } + loop_limit++; + } - /* Free memory. */ - MEM_SAFE_FREE(tgpf->sbuffer); - MEM_SAFE_FREE(tgpf->depth_arr); + if (do_extend) { + gpencil_delete_temp_stroke_extension(tgpf, true); } + + i++; } + WM_cursor_modal_restore(win); + /* Free hash table. */ + BLI_ghash_free(frame_list, NULL, NULL); - /* restore size */ - tgpf->region->winx = (short)tgpf->bwinx; - tgpf->region->winy = (short)tgpf->bwiny; - tgpf->region->winrct = tgpf->brect; + /* Free temp stroke. */ + BKE_gpencil_free_stroke(tgpf->gps_mouse); /* push undo data */ gpencil_undo_push(tgpf->gpd); + /* Save extend value for next operation. */ + brush_settings->fill_extend_fac = tgpf->fill_extend_fac; + estate = OPERATOR_FINISHED; } else { @@ -1660,8 +2149,29 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) estate = OPERATOR_CANCELLED; } } + else if (do_extend) { + gpencil_update_extend(tgpf); + } tgpf->oldkey = event->type; break; + case EVT_PAGEUPKEY: + case WHEELUPMOUSE: + if (tgpf->oldkey == 1) { + tgpf->fill_extend_fac -= (event->shift) ? 0.01f : 0.1f; + CLAMP_MIN(tgpf->fill_extend_fac, 0.0f); + gpencil_update_extend(tgpf); + } + break; + case EVT_PAGEDOWNKEY: + case WHEELDOWNMOUSE: + if (tgpf->oldkey == 1) { + tgpf->fill_extend_fac += (event->shift) ? 0.01f : 0.1f; + CLAMP_MAX(tgpf->fill_extend_fac, 100.0f); + gpencil_update_extend(tgpf); + } + break; + default: + break; } /* process last operations before exiting */ switch (estate) { @@ -1674,7 +2184,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) gpencil_fill_exit(C, op); break; - case OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH: + default: break; } diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 2d5491e7569..b833125cf34 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1306,6 +1306,12 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, gps); + /* In Multiframe mode, duplicate the stroke in other frames. */ + if (GPENCIL_MULTIEDIT_SESSIONS_ON(p->gpd)) { + const bool tail = (ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK); + BKE_gpencil_stroke_copy_to_keyframes(gpd, gpl, p->gpf, gps, tail); + } + gpencil_stroke_added_enable(p); } @@ -1323,10 +1329,8 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) } /* only erase stroke points that are visible */ -static bool gpencil_stroke_eraser_is_occluded(tGPsdata *p, - const bGPDspoint *pt, - const int x, - const int y) +static bool gpencil_stroke_eraser_is_occluded( + tGPsdata *p, bGPDlayer *gpl, const bGPDspoint *pt, const int x, const int y) { Object *obact = (Object *)p->ownerPtr.data; Brush *brush = p->brush; @@ -1343,7 +1347,6 @@ static bool gpencil_stroke_eraser_is_occluded(tGPsdata *p, if ((gp_settings != NULL) && (p->area->spacetype == SPACE_VIEW3D) && (gp_settings->flag & GP_BRUSH_OCCLUDE_ERASER)) { RegionView3D *rv3d = p->region->regiondata; - bGPDlayer *gpl = p->gpl; const int mval_i[2] = {x, y}; float mval_3d[3]; @@ -1454,6 +1457,7 @@ static void gpencil_stroke_soft_refine(bGPDstroke *gps) /* eraser tool - evaluation per stroke */ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, + bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, const float mval[2], @@ -1579,9 +1583,9 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, * - this assumes that linewidth is irrelevant */ if (gpencil_stroke_inside_circle(mval, radius, pc0[0], pc0[1], pc2[0], pc2[1])) { - if ((gpencil_stroke_eraser_is_occluded(p, pt0, pc0[0], pc0[1]) == false) || - (gpencil_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) || - (gpencil_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false)) { + if ((gpencil_stroke_eraser_is_occluded(p, gpl, pt0, pc0[0], pc0[1]) == false) || + (gpencil_stroke_eraser_is_occluded(p, gpl, pt1, pc1[0], pc1[1]) == false) || + (gpencil_stroke_eraser_is_occluded(p, gpl, pt2, pc2[0], pc2[1]) == false)) { /* Point is affected: */ /* Adjust thickness * - Influence of eraser falls off with distance from the middle of the eraser @@ -1683,6 +1687,8 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, /* erase strokes which fall under the eraser strokes */ static void gpencil_stroke_doeraser(tGPsdata *p) { + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(p->gpd); + rcti rect; Brush *brush = p->brush; Brush *eraser = p->eraser; @@ -1723,40 +1729,53 @@ static void gpencil_stroke_doeraser(tGPsdata *p) * on multiple layers... */ LISTBASE_FOREACH (bGPDlayer *, gpl, &p->gpd->layers) { - bGPDframe *gpf = gpl->actframe; - /* only affect layer if it's editable (and visible) */ if (BKE_gpencil_layer_is_editable(gpl) == false) { continue; } - if (gpf == NULL) { + + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + if (init_gpf == NULL) { continue; } - /* calculate difference matrix */ - BKE_gpencil_layer_transform_matrix_get(p->depsgraph, p->ob, gpl, p->diff_mat); - /* loop over strokes, checking segments for intersections */ - LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(p->ob, gpl, gps) == false) { - continue; - } + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } + /* calculate difference matrix */ + BKE_gpencil_layer_transform_matrix_get(p->depsgraph, p->ob, gpl, p->diff_mat); + + /* loop over strokes, checking segments for intersections */ + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + /* check if the color is editable */ + if (ED_gpencil_stroke_material_editable(p->ob, gpl, gps) == false) { + continue; + } - /* Check if the stroke collide with mouse. */ - if (!ED_gpencil_stroke_check_collision(&p->gsc, gps, p->mval, calc_radius, p->diff_mat)) { - continue; - } + /* Check if the stroke collide with mouse. */ + if (!ED_gpencil_stroke_check_collision( + &p->gsc, gps, p->mval, calc_radius, p->diff_mat)) { + continue; + } - /* Not all strokes in the datablock may be valid in the current editor/context - * (e.g. 2D space strokes in the 3D view, if the same datablock is shared) - */ - if (ED_gpencil_stroke_can_use_direct(p->area, gps)) { - gpencil_stroke_eraser_dostroke(p, gpf, gps, p->mval, calc_radius, &rect); + /* Not all strokes in the datablock may be valid in the current editor/context + * (e.g. 2D space strokes in the 3D view, if the same datablock is shared) + */ + if (ED_gpencil_stroke_can_use_direct(p->area, gps)) { + gpencil_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, calc_radius, &rect); + } + } + + /* if not multiedit, exit loop*/ + if (!is_multiedit) { + break; + } } } } } - /* ******************************************* */ /* Sketching Operator */ diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index d94a02204a2..bcdde49b93d 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1377,6 +1377,12 @@ static void gpencil_primitive_interaction_end(bContext *C, BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps); } + /* In Multiframe mode, duplicate the stroke in other frames. */ + if (GPENCIL_MULTIEDIT_SESSIONS_ON(tgpi->gpd)) { + const bool tail = (ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK); + BKE_gpencil_stroke_copy_to_keyframes(tgpi->gpd, tgpi->gpl, gpf, gps, tail); + } + DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_COPY_ON_WRITE); DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 42e3055ef65..9b12772bc9b 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -3032,12 +3032,12 @@ void ED_gpencil_sbuffer_vertex_color_set(Depsgraph *depsgraph, } } -/* Helper to get the bigger 2D bound box points. */ -static void gpencil_projected_2d_bound_box(GP_SpaceConversion *gsc, - bGPDstroke *gps, - const float diff_mat[4][4], - float r_min[2], - float r_max[2]) +/* Get the bigger 2D bound box points. */ +void ED_gpencil_projected_2d_bound_box(GP_SpaceConversion *gsc, + bGPDstroke *gps, + const float diff_mat[4][4], + float r_min[2], + float r_max[2]) { float bounds[8][2]; BoundBox bb; @@ -3082,7 +3082,7 @@ bool ED_gpencil_stroke_check_collision(GP_SpaceConversion *gsc, BKE_gpencil_stroke_boundingbox_calc(gps); } - gpencil_projected_2d_bound_box(gsc, gps, diff_mat, boundbox_min, boundbox_max); + ED_gpencil_projected_2d_bound_box(gsc, gps, diff_mat, boundbox_min, boundbox_max); rcti rect_stroke = {boundbox_min[0], boundbox_max[0], boundbox_min[1], boundbox_max[1]}; -- cgit v1.2.3