diff options
author | Antonio Vazquez <blendergit@gmail.com> | 2021-02-08 18:28:42 +0300 |
---|---|---|
committer | Antonio Vazquez <blendergit@gmail.com> | 2021-02-09 18:00:36 +0300 |
commit | 1352d81b174726639bbfb6f7aa32dbadf188a8dd (patch) | |
tree | b7bc33114a93c86313b774d8fa05e1214c6844c3 | |
parent | 5213b18eb2d57153e0e394190af1624401fed74b (diff) |
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.
20 files changed, 1082 insertions, 377 deletions
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 5d241e8e216..30e50bde969 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -1218,11 +1218,13 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False) row = layout.row(align=True) row.prop(gp_settings, "fill_direction", text="", expand=True) row = layout.row(align=True) + row.prop(gp_settings, "fill_factor") + row = layout.row(align=True) row.prop(gp_settings, "fill_leak", text="Leak Size") row = layout.row(align=True) row.prop(brush, "size", text="Thickness") row = layout.row(align=True) - row.prop(gp_settings, "fill_simplify_level", text="Simplify") + row.prop(gp_settings, "use_fill_autofit", text="", icon="SNAP_FACE_CENTER") else: # brush.gpencil_tool == 'DRAW/TINT': row = layout.row(align=True) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index ebb6280a42f..29e462026e1 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -695,6 +695,10 @@ class VIEW3D_HT_header(Header): row.prop(tool_settings, "use_gpencil_vertex_select_mask_stroke", text="") row.prop(tool_settings, "use_gpencil_vertex_select_mask_segment", text="") + if gpd.is_stroke_paint_mode: + row = layout.row(align=True) + row.prop(gpd, "use_multiedit", text="", icon='GP_MULTIFRAME_EDITING') + if ( gpd.use_stroke_edit_mode or gpd.is_stroke_sculpt_mode or diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 7c718ee8155..20ec26200f6 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1394,7 +1394,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel): bl_parent_id = 'VIEW3D_PT_tools_grease_pencil_brush_settings' bl_category = "Tool" bl_options = {'DEFAULT_CLOSED'} - bl_ui_units_x = 11 + bl_ui_units_x = 13 @classmethod def poll(cls, context): @@ -1447,7 +1447,12 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel): row.prop(gp_settings, "fill_layer_mode", text="Layers") col.separator() - col.prop(gp_settings, "fill_factor") + row = col.row(align=True) + row.prop(gp_settings, "extend_stroke_factor") + row.prop(gp_settings, "show_fill_extend", text="", icon='GRID') + + col.separator() + col.prop(gp_settings, "fill_simplify_level", text="Simplify") if gp_settings.fill_draw_mode != 'STROKE': col = layout.column(align=False, heading="Ignore Transparent") col.use_property_decorate = False diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index b7fad9c891a..a8caf317467 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -115,6 +115,8 @@ struct bGPDlayer *BKE_gpencil_layer_duplicate(const struct bGPDlayer *gpl_src, const bool dup_frames, const bool dup_strokes); void BKE_gpencil_frame_copy_strokes(struct bGPDframe *gpf_src, struct bGPDframe *gpf_dst); +void BKE_gpencil_frame_selected_hash(struct bGPdata *gpd, struct GHash *r_list); + struct bGPDcurve *BKE_gpencil_stroke_curve_duplicate(struct bGPDcurve *gpc_src); struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, const bool dup_points, diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index 1c86df73d3c..89a794f2df3 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -149,6 +149,11 @@ void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a, struct bGPDstroke *gps_b, const bool leave_gaps, const bool fit_thickness); +void BKE_gpencil_stroke_copy_to_keyframes(struct bGPdata *gpd, + struct bGPDlayer *gpl, + struct bGPDframe *gpf, + struct bGPDstroke *gps, + const bool tail); bool BKE_gpencil_convert_mesh(struct Main *bmain, struct Depsgraph *depsgraph, diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index 1ba5571a26b..59ff59b82e0 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -975,7 +975,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) break; } case GP_BRUSH_PRESET_FILL_AREA: { - brush->size = 20.0f; + brush->size = 5.0f; brush->gpencil_settings->fill_leak = 3; brush->gpencil_settings->fill_threshold = 0.1f; @@ -989,6 +989,8 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) brush->gpencil_settings->draw_smoothlvl = 1; brush->gpencil_settings->draw_subdivide = 1; + brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES; + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_FILL; brush->gpencil_tool = GPAINT_TOOL_FILL; brush->gpencil_settings->vertex_mode = GPPAINT_MODE_FILL; diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 0c813c170ad..3d0152a6c7d 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -851,6 +851,7 @@ bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness) gps->mat_nr = mat_idx; + gps->dvert = NULL; gps->editcurve = NULL; return gps; @@ -2606,6 +2607,15 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if (gpf == act_gpf || (gpf->flag & GP_FRAME_SELECT)) { gpf->runtime.onion_id = 0; + if (do_onion) { + if (gpf->framenum < act_gpf->framenum) { + gpf->runtime.onion_id = -1; + } + else { + gpf->runtime.onion_id = 1; + } + } + if (sta_gpf == NULL) { sta_gpf = gpf; } @@ -2939,4 +2949,26 @@ int BKE_gpencil_material_find_index_by_name_prefix(Object *ob, const char *name_ return -1; } +/* Create a hash with the list of selected frame number. */ +void BKE_gpencil_frame_selected_hash(bGPdata *gpd, struct GHash *r_list) +{ + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + + LISTBASE_FOREACH (bGPDlayer *, gpl_iter, &gpd->layers) { + if ((gpl != NULL) && (!is_multiedit) && (gpl != gpl_iter)) { + continue; + } + + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl_iter->frames) { + if (((gpf == gpl->actframe) && (!is_multiedit)) || + ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (!BLI_ghash_lookup(r_list, POINTER_FROM_INT(gpf->framenum))) { + BLI_ghash_insert(r_list, POINTER_FROM_INT(gpf->framenum), gpf); + } + } + } + } +} + /** \} */ diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 60ffa3c3e73..2ef85439a46 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -3228,6 +3228,45 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, } } +/* Copy the stroke of the frame to all frames selected (except current). */ +void BKE_gpencil_stroke_copy_to_keyframes( + bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, const bool tail) +{ + GHash *frame_list = BLI_ghash_int_new_ex(__func__, 64); + BKE_gpencil_frame_selected_hash(gpd, frame_list); + + GHashIterator gh_iter; + GHASH_ITER (gh_iter, frame_list) { + int cfra = POINTER_AS_INT(BLI_ghashIterator_getKey(&gh_iter)); + + if (gpf->framenum != cfra) { + bGPDframe *gpf_new = BKE_gpencil_layer_frame_find(gpl, cfra); + if (gpf_new == NULL) { + gpf_new = BKE_gpencil_frame_addnew(gpl, cfra); + } + + if (gpf_new == NULL) { + continue; + } + + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, true, true); + if (gps_new == NULL) { + continue; + } + + if (tail) { + BLI_addhead(&gpf_new->strokes, gps_new); + } + else { + BLI_addtail(&gpf_new->strokes, gps_new); + } + } + } + + /* Free hash table. */ + BLI_ghash_free(frame_list, NULL, NULL); +} + /* Stroke Uniform Subdivide ------------------------------------- */ typedef struct tSamplePoint { 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]}; diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 0136d599e8a..12aef6e9464 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -370,6 +370,11 @@ bool ED_gpencil_stroke_point_is_inside(struct bGPDstroke *gps, struct GP_SpaceConversion *gsc, int mouse[2], const float diff_mat[4][4]); +void ED_gpencil_projected_2d_bound_box(struct GP_SpaceConversion *gsc, + struct bGPDstroke *gps, + const float diff_mat[4][4], + float r_min[2], + float r_max[2]); struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C, struct GP_SpaceConversion *gsc, diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index f12934c9104..093a1a00ece 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -75,6 +75,10 @@ typedef enum eGPDbrush_Flag { GP_BRUSH_USE_STRENGTH_PRESSURE = (1 << 1), /* brush use pressure for alpha factor */ GP_BRUSH_USE_JITTER_PRESSURE = (1 << 2), + /* Disable automatic zoom for filling. */ + GP_BRUSH_FILL_FIT_DISABLE = (1 << 3), + /* Show extend fill help lines. */ + GP_BRUSH_FILL_SHOW_EXTENDLINES = (1 << 4), /* fill hide transparent */ GP_BRUSH_FILL_HIDE = (1 << 6), /* show fill help lines */ diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 91e0bc0ffa1..a11e7d77c67 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -47,8 +47,6 @@ typedef struct BrushClone { char _pad[4]; } BrushClone; -#define GPENCIL_MIN_FILL_FAC 0.05f - typedef struct BrushGpencilSettings { /** Amount of smoothing to apply to newly created strokes. */ float draw_smoothfac; @@ -133,6 +131,10 @@ typedef struct BrushGpencilSettings { /** Randomness for Value. */ float random_value; + /** Factor to extend stroke extremes using fill tool. */ + float fill_extend_fac; + char _pad3[4]; + struct CurveMapping *curve_sensitivity; struct CurveMapping *curve_strength; struct CurveMapping *curve_jitter; diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 1dbfd547673..e02757c1249 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -45,6 +45,9 @@ struct MDeformVert; #define GP_DEFAULT_CURVE_ERROR 0.1f #define GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE M_PI_2 +#define GPENCIL_MIN_FILL_FAC 0.05f +#define GPENCIL_MAX_FILL_FAC 5.0f + /* ***************************************** */ /* GP Stroke Points */ @@ -809,8 +812,9 @@ typedef enum eGP_DrawMode { /* Check if 'multiedit sessions' is enabled */ #define GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) \ ((gpd) && \ - ((gpd)->flag & (GP_DATA_STROKE_EDITMODE | GP_DATA_STROKE_SCULPTMODE | \ - GP_DATA_STROKE_WEIGHTMODE | GP_DATA_STROKE_VERTEXMODE)) && \ + ((gpd)->flag & \ + (GP_DATA_STROKE_PAINTMODE | GP_DATA_STROKE_EDITMODE | GP_DATA_STROKE_SCULPTMODE | \ + GP_DATA_STROKE_WEIGHTMODE | GP_DATA_STROKE_VERTEXMODE)) && \ ((gpd)->flag & GP_DATA_STROKE_MULTIEDIT)) #define GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) \ diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 756841dd283..6e2e986ebf8 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -1466,7 +1466,7 @@ static void rna_def_gpencil_options(BlenderRNA *brna) /* fill factor size */ prop = RNA_def_property(srna, "fill_factor", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "fill_factor"); - RNA_def_property_range(prop, GPENCIL_MIN_FILL_FAC, 8.0f); + RNA_def_property_range(prop, GPENCIL_MIN_FILL_FAC, GPENCIL_MAX_FILL_FAC); RNA_def_property_ui_text( prop, "Precision", @@ -1608,6 +1608,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Value", "Random factor to modify original value"); RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + /* Factor to extend stroke extremes in Fill tool. */ + prop = RNA_def_property(srna, "extend_stroke_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fill_extend_fac"); + RNA_def_property_range(prop, 0.0f, 10.0f); + RNA_def_property_float_default(prop, 0.0f); + RNA_def_property_ui_text( + prop, "Stroke Extension", "Strokes end extension for closing gaps, use zero to disable"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + /* Flags */ prop = RNA_def_property(srna, "use_pressure", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_USE_PRESSURE); @@ -1827,6 +1836,12 @@ static void rna_def_gpencil_options(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Show Lines", "Show help lines for filling to see boundaries"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "show_fill_extend", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_FILL_SHOW_EXTENDLINES); + RNA_def_property_boolean_default(prop, true); + RNA_def_property_ui_text(prop, "Show Extend Lines", "Show help lines for stroke extension"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "show_fill", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_BRUSH_FILL_HIDE); RNA_def_property_boolean_default(prop, true); @@ -1834,6 +1849,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna) prop, "Show Fill", "Show transparent lines to use as boundary for filling"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "use_fill_autofit", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_BRUSH_FILL_FIT_DISABLE); + RNA_def_property_boolean_default(prop, true); + RNA_def_property_ui_text( + prop, + "Automatic Fit", + "Fit the shape of the stroke to try to fill areas outside visible viewport"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "use_default_eraser", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_DEFAULT_ERASER); RNA_def_property_boolean_default(prop, true); |