Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Vazquez <blendergit@gmail.com>2019-09-27 15:00:29 +0300
committerAntonio Vazquez <blendergit@gmail.com>2019-09-27 16:28:32 +0300
commit12688a6636da2b1ca4004ced800d9dee1627081d (patch)
tree77794f74d0947d43fc88e29de8ab53ab11313a53 /source/blender/editors/gpencil
parent770e91703d1196751432ef4a2db5ca7afed02aee (diff)
GPencil: New smart smooth for strokes
When using the samples, the interpolated points get abrupt steps because the system cannot receive all events in a short period of time. This is more noticeable when the samples are set to 10 and the pen is moved very fast. The problem with post-processing smooth is that is applied to all stroke and this removes details. The smart smooth is automatic and detect only the segments in the stroke where the system was unable to capture all movements and apply a smooth algorithm.
Diffstat (limited to 'source/blender/editors/gpencil')
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c176
1 files changed, 151 insertions, 25 deletions
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 917c883909c..bf87408d38f 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -636,6 +636,111 @@ static void gp_smooth_buffer(tGPsdata *p, float inf, int idx)
ptc->strength = interpf(ptc->strength, strength, inf);
}
+/* Helper: Apply smooth to segment from Index to Index */
+static void gp_smooth_segment(bGPdata *gpd, const float inf, int from_idx, int to_idx)
+{
+ const short num_points = to_idx - from_idx;
+ /* Do nothing if not enough points to smooth out */
+ if ((num_points < 3) || (inf == 0.0f)) {
+ return;
+ }
+
+ if (from_idx <= 2) {
+ return;
+ }
+
+ tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer;
+ const float steps = (num_points < 4) ? 3.0f : 4.0f;
+
+ for (int i = from_idx; i < to_idx + 1; i++) {
+
+ tGPspoint *pta = i >= 3 ? &points[i - 3] : NULL;
+ tGPspoint *ptb = i >= 2 ? &points[i - 2] : NULL;
+ tGPspoint *ptc = i >= 1 ? &points[i - 1] : &points[i];
+ tGPspoint *ptd = &points[i];
+
+ float sco[2] = {0.0f};
+ const float average_fac = 1.0f / steps;
+
+ /* Compute smoothed coordinate by taking the ones nearby */
+ if (pta) {
+ madd_v2_v2fl(sco, &pta->x, average_fac);
+ }
+ else {
+ madd_v2_v2fl(sco, &ptc->x, average_fac);
+ }
+
+ if (ptb) {
+ madd_v2_v2fl(sco, &ptb->x, average_fac);
+ }
+ else {
+ madd_v2_v2fl(sco, &ptc->x, average_fac);
+ }
+
+ madd_v2_v2fl(sco, &ptc->x, average_fac);
+
+ madd_v2_v2fl(sco, &ptd->x, average_fac);
+
+ /* Based on influence factor, blend between original and optimal smoothed coordinate. */
+ interp_v2_v2v2(&ptc->x, &ptc->x, sco, inf);
+ }
+}
+
+/* Smooth all the sections created with fake events to avoid abrupt transitions.
+ *
+ * As the fake events add points between two real events, this produces a straight line, but if
+ * there is 3 or more real points that used fakes, the stroke is not smooth and produces abrupt
+ * angles.
+ * This function reads these segments and finds the real points and smooth with the surrounding
+ * points. */
+static void gp_smooth_fake_segments(tGPsdata *p)
+{
+ bGPdata *gpd = p->gpd;
+ Brush *brush = p->brush;
+
+ if (brush->gpencil_settings->input_samples < 2) {
+ return;
+ }
+
+ tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer;
+ tGPspoint *pt = NULL;
+ /* Index where segment starts. */
+ int from_idx = 0;
+ /* Index where segment ends. */
+ int to_idx = 0;
+
+ bool doit = false;
+ /* Loop all points except the extremes. */
+ for (int i = 1; i < gpd->runtime.sbuffer_used - 1; i++) {
+ pt = &points[i];
+ bool is_fake = (bool)(pt->tflag & GP_TPOINT_FAKE);
+ to_idx = i;
+
+ /* Detect fake points in the stroke. */
+ if ((!doit) && (is_fake)) {
+ from_idx = i;
+ doit = true;
+ }
+ /* If detect control point after fake points, select a segment with same length in both sides,
+ * except if it is more than stroke length. */
+ if ((doit) && (!is_fake)) {
+ if (i + (i - from_idx) < gpd->runtime.sbuffer_used - 1) {
+ to_idx = i + (i - from_idx);
+ /* Smooth this segments (need loop to get cumulative smooth). */
+ for (int r = 0; r < 5; r++) {
+ gp_smooth_segment(gpd, 0.1f, from_idx, to_idx);
+ }
+ }
+ else {
+ break;
+ }
+ /* Reset to new segments. */
+ from_idx = i;
+ doit = false;
+ }
+ }
+}
+
/* Smooth the section added with fake events when pen moves very fast. */
static void gp_smooth_fake_events(tGPsdata *p, int size_before, int size_after)
{
@@ -676,7 +781,8 @@ static void gp_smooth_fake_events(tGPsdata *p, int size_before, int size_after)
}
/* add current stroke-point to buffer (returns whether point was successfully added) */
-static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure, double curtime)
+static short gp_stroke_addpoint(
+ tGPsdata *p, const float mval[2], float pressure, double curtime, bool is_fake)
{
bGPdata *gpd = p->gpd;
Brush *brush = p->brush;
@@ -735,6 +841,14 @@ static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure
/* get pointer to destination point */
pt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_used);
+ /* Set if point was created by fake events. */
+ if (is_fake) {
+ pt->tflag |= GP_TPOINT_FAKE;
+ }
+ else {
+ pt->tflag &= ~GP_TPOINT_FAKE;
+ }
+
/* store settings */
/* pressure */
if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) {
@@ -892,7 +1006,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure
bGPDspoint *pts;
MDeformVert *dvert = NULL;
- /* first time point is adding to temporary buffer -- need to allocate new point in stroke */
+ /* First time point is adding to temporary buffer (need to allocate new point in stroke) */
if (gpd->runtime.sbuffer_used == 0) {
gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
if (gps->dvert != NULL) {
@@ -1249,6 +1363,9 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
}
}
+ /* Smooth any point created with fake events when the mouse/pen move very fast. */
+ gp_smooth_fake_segments(p);
+
pt = gps->points;
dvert = gps->dvert;
@@ -2631,7 +2748,8 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p)
/* ------------------------------- */
/* create a new stroke point at the point indicated by the painting context */
-static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgraph *depsgraph)
+static void gpencil_draw_apply(
+ bContext *C, wmOperator *op, tGPsdata *p, Depsgraph *depsgraph, bool is_fake)
{
bGPdata *gpd = p->gpd;
tGPspoint *pt = NULL;
@@ -2660,7 +2778,7 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra
}
/* try to add point */
- short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime);
+ short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime, is_fake);
/* handle errors while adding point */
if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) {
@@ -2674,12 +2792,12 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra
/* XXX We only need to reuse previous point if overflow! */
if (ok == GP_STROKEADD_OVERFLOW) {
p->inittime = p->ocurtime;
- gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime);
+ gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime, is_fake);
}
else {
p->inittime = p->curtime;
}
- gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime);
+ gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime, is_fake);
}
else if (ok == GP_STROKEADD_INVALID) {
/* the painting operation cannot continue... */
@@ -2885,8 +3003,13 @@ static void gpencil_speed_guide(tGPsdata *p, GP_Sculpt_Guide *guide)
}
/* handle draw event */
-static void gpencil_draw_apply_event(
- bContext *C, wmOperator *op, const wmEvent *event, Depsgraph *depsgraph, float x, float y)
+static void gpencil_draw_apply_event(bContext *C,
+ wmOperator *op,
+ const wmEvent *event,
+ Depsgraph *depsgraph,
+ float x,
+ float y,
+ const bool is_fake)
{
tGPsdata *p = op->customdata;
GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
@@ -3039,10 +3162,10 @@ static void gpencil_draw_apply_event(
/* create fake events */
float tmp[2];
copy_v2_v2(tmp, p->mval);
- gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1]);
+ gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], false);
if (len_v2v2(p->mval, p->mvalo)) {
sub_v2_v2v2(pt, p->mval, p->mvalo);
- gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1]);
+ gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], false);
}
copy_v2_v2(p->mval, tmp);
}
@@ -3073,7 +3196,7 @@ static void gpencil_draw_apply_event(
RNA_float_set(&itemptr, "time", p->curtime - p->inittime);
/* apply the current latest drawing point */
- gpencil_draw_apply(C, op, p, depsgraph);
+ gpencil_draw_apply(C, op, p, depsgraph, is_fake);
/* force refresh */
/* just active area for now, since doing whole screen is too slow */
@@ -3139,7 +3262,7 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op)
}
/* apply this data as necessary now (as per usual) */
- gpencil_draw_apply(C, op, p, depsgraph);
+ gpencil_draw_apply(C, op, p, depsgraph, false);
}
RNA_END;
@@ -3312,8 +3435,6 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
/* TODO: set any additional settings that we can take from the events?
* TODO? if tablet is erasing, force eraser to be on? */
- /* TODO: move cursor setting stuff to stroke-start so that paintmode can be changed midway... */
-
/* if eraser is on, draw radial aid */
if (p->paintmode == GP_PAINTMODE_ERASER) {
gpencil_draw_toggle_eraser_cursor(C, p, true);
@@ -3334,7 +3455,8 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
p->status = GP_STATUS_PAINTING;
/* handle the initial drawing - i.e. for just doing a simple dot */
- gpencil_draw_apply_event(C, op, event, CTX_data_ensure_evaluated_depsgraph(C), 0.0f, 0.0f);
+ gpencil_draw_apply_event(
+ C, op, event, CTX_data_ensure_evaluated_depsgraph(C), 0.0f, 0.0f, false);
op->flag |= OP_IS_MODAL_CURSOR_REGION;
}
else {
@@ -3451,11 +3573,8 @@ static void gpencil_move_last_stroke_to_back(bContext *C)
BLI_insertlinkbefore(&gpf->strokes, gpf->strokes.first, gps);
}
-/* add events for missing mouse movements when the artist draw very fast */
-static bool gpencil_add_missing_events(bContext *C,
- wmOperator *op,
- const wmEvent *event,
- tGPsdata *p)
+/* Add fake events for missing mouse movements when the artist draw very fast */
+static bool gpencil_add_fake_events(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p)
{
Brush *brush = p->brush;
GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
@@ -3518,7 +3637,7 @@ static bool gpencil_add_missing_events(bContext *C,
interp_v2_v2v2(pt, a, b, 0.5f);
sub_v2_v2v2(pt, b, pt);
/* create fake event */
- gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1]);
+ gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], true);
added_events = true;
}
else if (dist >= factor) {
@@ -3528,7 +3647,7 @@ static bool gpencil_add_missing_events(bContext *C,
interp_v2_v2v2(pt, a, b, n * i);
sub_v2_v2v2(pt, b, pt);
/* create fake event */
- gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1]);
+ gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], true);
added_events = true;
}
}
@@ -3541,6 +3660,8 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
tGPsdata *p = op->customdata;
ToolSettings *ts = CTX_data_tool_settings(C);
GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
+ tGPspoint *points = (tGPspoint *)p->gpd->runtime.sbuffer;
+
/* default exit state - pass through to support MMB view nav, etc. */
int estate = OPERATOR_PASS_THROUGH;
@@ -3663,7 +3784,8 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
/* toggle painting mode upon mouse-button movement
- * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox only)
+ * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox
+ * only)
* - RIGHTMOUSE = polyline (hotkey) / eraser (all)
* (Disabling RIGHTMOUSE case here results in bugs like [#32647])
* also making sure we have a valid event value, to not exit too early
@@ -3840,12 +3962,16 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
int size_before = p->gpd->runtime.sbuffer_used;
bool added_events = false;
if (((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) && (p->paintmode != GP_PAINTMODE_ERASER)) {
- added_events = gpencil_add_missing_events(C, op, event, p);
+ added_events = gpencil_add_fake_events(C, op, event, p);
}
- gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph_pointer(C), 0.0f, 0.0f);
+ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph_pointer(C), 0.0f, 0.0f, false);
int size_after = p->gpd->runtime.sbuffer_used;
+ /* Last point of the event is always real (not fake). */
+ tGPspoint *pt = &points[size_after - 1];
+ pt->tflag &= ~GP_TPOINT_FAKE;
+
/* Smooth the fake events to get smoother strokes, specially at ends. */
if (added_events) {
gp_smooth_fake_events(p, size_before, size_after);