[Bf-blender-cvs] [12688a6636d] master: GPencil: New smart smooth for strokes

Antonio Vazquez noreply at git.blender.org
Fri Sep 27 15:28:40 CEST 2019


Commit: 12688a6636da2b1ca4004ced800d9dee1627081d
Author: Antonio Vazquez
Date:   Fri Sep 27 14:00:29 2019 +0200
Branches: master
https://developer.blender.org/rB12688a6636da2b1ca4004ced800d9dee1627081d

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.

===================================================================

M	source/blender/editors/gpencil/gpencil_paint.c
M	source/blender/editors/include/ED_gpencil.h

===================================================================

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_miss

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list