[Bf-blender-cvs] [d4e1458db3a] master: GPencil: Improve smooth operation

Henrik Dick noreply at git.blender.org
Fri Mar 25 11:58:58 CET 2022


Commit: d4e1458db3a0e0eaf80219dc8e6d10cb27620793
Author: Henrik Dick
Date:   Fri Mar 25 11:51:45 2022 +0100
Branches: master
https://developer.blender.org/rBd4e1458db3a0e0eaf80219dc8e6d10cb27620793

GPencil: Improve smooth operation

This patch makes the grease pencil smooth operation symmetric.
It also increases the performance a lot if strong smoothing is
required. Additionally there is an option for the position smooth
operation to keep the shape closer to the original for more iterations.

Since the result differs from the previous algorithm, versioning is used
to change the iterations and factor to match the old result.

Differential Revision: http://developer.blender.org/D14325

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

M	source/blender/blenkernel/BKE_gpencil_geom.h
M	source/blender/blenkernel/intern/gpencil_geom.cc
M	source/blender/blenloader/intern/versioning_300.c
M	source/blender/editors/gpencil/gpencil_edit.c
M	source/blender/editors/gpencil/gpencil_fill.c
M	source/blender/editors/gpencil/gpencil_interpolate.c
M	source/blender/editors/gpencil/gpencil_paint.c
M	source/blender/editors/gpencil/gpencil_sculpt_paint.c
M	source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c
M	source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c
M	source/blender/makesdna/DNA_gpencil_modifier_defaults.h
M	source/blender/makesdna/DNA_gpencil_modifier_types.h
M	source/blender/makesrna/intern/rna_brush.c
M	source/blender/makesrna/intern/rna_gpencil_modifier.c

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

diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index 4127030e96f..ad3b1971ca9 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -220,30 +220,78 @@ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd,
  * \param gps: Stroke to smooth
  * \param i: Point index
  * \param inf: Amount of smoothing to apply
+ * \param iterations: Radius of points to consider, equivalent to iterations
  * \param smooth_caps: Apply smooth to stroke extremes
+ * \param keep_shape: Smooth out fine details first
+ * \param r_gps: Stroke to put the result into
  */
-bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf, bool smooth_caps);
+bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps,
+                                     int point_index,
+                                     float influence,
+                                     int iterations,
+                                     bool smooth_caps,
+                                     bool keep_shape,
+                                     struct bGPDstroke *r_gps);
 /**
  * Apply smooth strength to stroke point.
  * \param gps: Stroke to smooth
  * \param point_index: Point index
  * \param influence: Amount of smoothing to apply
+ * \param iterations: Radius of points to consider, equivalent to iterations
+ * \param r_gps: Stroke to put the result into
  */
-bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence);
+bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps,
+                                        int point_index,
+                                        float influence,
+                                        int iterations,
+                                        struct bGPDstroke *r_gps);
 /**
  * Apply smooth for thickness to stroke point (use pressure).
  * \param gps: Stroke to smooth
  * \param point_index: Point index
  * \param influence: Amount of smoothing to apply
+ * \param iterations: Radius of points to consider, equivalent to iterations
+ * \param r_gps: Stroke to put the result into
  */
-bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence);
+bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps,
+                                         int point_index,
+                                         float influence,
+                                         int iterations,
+                                         struct bGPDstroke *r_gps);
 /**
- * Apply smooth for UV rotation to stroke point (use pressure).
+ * Apply smooth for UV rotation/factor to stroke point.
  * \param gps: Stroke to smooth
  * \param point_index: Point index
  * \param influence: Amount of smoothing to apply
+ * \param iterations: Radius of points to consider, equivalent to iterations
+ * \param r_gps: Stroke to put the result into
  */
-bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence);
+bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps,
+                                  int point_index,
+                                  float influence,
+                                  int iterations,
+                                  struct bGPDstroke *r_gps);
+/**
+ * Apply smooth operation to the stroke.
+ * \param gps: Stroke to smooth
+ * \param influence: The interpolation factor for the smooth and the original stroke
+ * \param iterations: Radius of points to consider, equivalent to iterations
+ * \param smooth_position: Smooth point locations
+ * \param smooth_strength: Smooth point strength
+ * \param smooth_thickness: Smooth point thickness
+ * \param smooth_uv: Smooth uv rotation/factor
+ * \param keep_shape: Use different distribution for smooth locations to keep the shape
+ * \param weights: per point weights to multiply influence with (optional, can be null)
+ */
+void BKE_gpencil_stroke_smooth(struct bGPDstroke *gps,
+                               const float influence,
+                               const int iterations,
+                               const bool smooth_position,
+                               const bool smooth_strength,
+                               const bool smooth_thickness,
+                               const bool smooth_uv,
+                               const bool keep_shape,
+                               const float *weights);
 /**
  * Close grease pencil stroke.
  * \param gps: Stroke to close
diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc
index a5eff1f9d5a..a0b6ab2d654 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.cc
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -980,74 +980,116 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
 /** \name Stroke Smooth Positions
  * \{ */
 
-bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf, const bool smooth_caps)
+bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
+                                     int i,
+                                     float influence,
+                                     int iterations,
+                                     const bool smooth_caps,
+                                     const bool keep_shape,
+                                     bGPDstroke *r_gps)
 {
-  bGPDspoint *pt = &gps->points[i];
-  float sco[3] = {0.0f};
-  const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
-
-  /* Do nothing if not enough points to smooth out */
-  if (gps->totpoints <= 2) {
+  /* If nothing to do, return early */
+  if (gps->totpoints <= 2 || iterations <= 0) {
     return false;
   }
 
-  /* Only affect endpoints by a fraction of the normal strength,
-   * to prevent the stroke from shrinking too much
+  /* Overview of the algorithm here and in the following smooth functions:
+   *  The smooth functions return the new attribute in question for a single point.
+   *  The result is stored in r_gps->points[i], while the data is read from gps.
+   *  To get a correct result, duplicate the stroke point data and read from the copy,
+   *  while writing to the real stroke. Not doing that will result in acceptable, but
+   *  asymmetric results.
+   * This algorithm works as long as all points are being smoothed. If there is
+   * points that should not get smoothed, use the old repeat smooth pattern with
+   * the parameter "iterations" set to 1 or 2. (2 matches the old algorithm).
    */
-  if ((!smooth_caps) && (!is_cyclic && ELEM(i, 0, gps->totpoints - 1))) {
-    inf *= 0.1f;
-  }
-
-  /* Compute smoothed coordinate by taking the ones nearby */
-  /* XXX: This is potentially slow,
-   *      and suffers from accumulation error as earlier points are handled before later ones. */
-  {
-    /* XXX: this is hardcoded to look at 2 points on either side of the current one
-     * (i.e. 5 items total). */
-    const int steps = 2;
-    const float average_fac = 1.0f / (float)(steps * 2 + 1);
-    int step;
-
-    /* add the point itself */
-    madd_v3_v3fl(sco, &pt->x, average_fac);
-
-    /* n-steps before/after current point */
-    /* XXX: review how the endpoints are treated by this algorithm. */
-    /* XXX: falloff measures should also introduce some weighting variations,
-     *      so that further-out points get less weight. */
-    for (step = 1; step <= steps; step++) {
-      bGPDspoint *pt1, *pt2;
-      int before = i - step;
-      int after = i + step;
-
-      if (is_cyclic) {
-        if (before < 0) {
-          /* Sub to end point (before is already negative). */
-          before = gps->totpoints + before;
-          CLAMP(before, 0, gps->totpoints - 1);
-        }
-        if (after > gps->totpoints - 1) {
-          /* Add to start point. */
-          after = after - gps->totpoints;
-          CLAMP(after, 0, gps->totpoints - 1);
+
+  const bGPDspoint *pt = &gps->points[i];
+  const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
+  /* If smooth_caps is false, the caps will not be translated by smoothing. */
+  if (!smooth_caps && !is_cyclic && ELEM(i, 0, gps->totpoints - 1)) {
+    copy_v3_v3(&r_gps->points[i].x, &pt->x);
+    return true;
+  }
+
+  /* This function uses a binomial kernel, which is the discrete version of gaussian blur.
+   * The weight for a vertex at the relative index i is
+   * w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n
+   * All weights together sum up to 1
+   * This is equivalent to doing multiple iterations of averaging neighbors,
+   * where n = iterations * 2 and -n/2 <= j <= n/2
+   *
+   * Now the problem is that nCr(n, j + n/2) is very hard to compute for n > 500, since even
+   * double precision isn't sufficient. A very good robust approximation for n > 20 is
+   * nCr(n, j + n/2) / 2^n = sqrt(2/(pi*n)) * exp(-2*j*j/n)
+   *
+   * There is one more problem left: The old smooth algorithm was doing a more aggressive
+   * smooth. To solve that problem, choose a different n/2, which does not match the range and
+   * normalize the weights on finish. This may cause some artifacts at low values.
+   *
+   * keep_shape is a new option to stop the stroke from severly deforming.
+   * It uses different partially negative weights.
+   * w = 2 * (nCr(n, j + n/2) / 2^n) - (nCr(3*n, j + n) / 2^(3*n))
+   *   ~ 2 * sqrt(2/(pi*n)) * exp(-2*j*j/n) - sqrt(2/(pi*3*n)) * exp(-2*j*j/(3*n))
+   * All weigths still sum up to 1.
+   * Note these weights only work because the averaging is done in relative coordinates.
+   */
+  float sco[3] = {0.0f, 0.0f, 0.0f};
+  float tmp[3];
+  const int n_half = keep_shape ? (iterations * iterations) / 8 + iterations :
+                                  (iterations * iterations) / 4 + 2 * iterations + 12;
+  double w = keep_shape ? 2.0 : 1.0;
+  double w2 = keep_shape ?
+                  (1.0 / M_SQRT3) * exp((2 * iterations * iterations) / (double)(n_half * 3)) :
+                  0.0;
+  double total_w = 0.0;
+  for (int step = iterations; step > 0; step--) {
+    int before = i - step;
+    int after = i + step;
+    float w_before = (float)(w - w2)

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list