[gimp/gimp-2-10] Issue #2372 - Reduced quality of the Parametric brush in 2.10



commit d39634a91432bdc7a6e7a96b6bdaa4b8efaecdd1
Author: Ell <ell_se yahoo com>
Date:   Wed Feb 6 14:15:47 2019 -0500

    Issue #2372 - Reduced quality of the Parametric brush in 2.10
    
    Promote the precision of generated brushes to 32-bit float, and
    modify brush preview generation, and gimpbrushcore-loops, to handle
    float brushes.  This avoids posterization in large brushes.
    
    Note that non-generated brushes are still uint8.
    
    (cherry picked from commit 8ef1113dee378e66182e13dcf924b32b7dd816c4)

 app/core/gimpbrush.c              |  15 +-
 app/core/gimpbrushgenerated.c     |  24 +-
 app/paint/gimpbrushcore-kernels.h | 126 ++++++---
 app/paint/gimpbrushcore-loops.cc  | 554 +++++++++++++++++++++++++-------------
 4 files changed, 481 insertions(+), 238 deletions(-)
---
diff --git a/app/core/gimpbrush.c b/app/core/gimpbrush.c
index b4791b70c4..88c3691cef 100644
--- a/app/core/gimpbrush.c
+++ b/app/core/gimpbrush.c
@@ -270,6 +270,7 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
   GimpTempBuf       *return_buf  = NULL;
   gint               mask_width;
   gint               mask_height;
+  guchar            *mask_data;
   guchar            *mask;
   guchar            *buf;
   gint               x, y;
@@ -327,12 +328,18 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
                                   babl_format ("R'G'B'A u8"));
   gimp_temp_buf_data_clear (return_buf);
 
-  mask = gimp_temp_buf_get_data (mask_buf);
+  mask = mask_data = gimp_temp_buf_lock (mask_buf, babl_format ("Y u8"),
+                                         GEGL_ACCESS_READ);
   buf  = gimp_temp_buf_get_data (return_buf);
 
   if (pixmap_buf)
     {
-      guchar *pixmap = gimp_temp_buf_get_data (pixmap_buf);
+      guchar *pixmap_data;
+      guchar *pixmap;
+
+      pixmap = pixmap_data = gimp_temp_buf_lock (pixmap_buf,
+                                                 babl_format ("R'G'B' u8"),
+                                                 GEGL_ACCESS_READ);
 
       for (y = 0; y < mask_height; y++)
         {
@@ -344,6 +351,8 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
               *buf++ = *mask++;
             }
         }
+
+      gimp_temp_buf_unlock (pixmap_buf, pixmap_data);
     }
   else
     {
@@ -359,6 +368,8 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
         }
     }
 
+  gimp_temp_buf_unlock (mask_buf, mask_data);
+
   if (scaled)
     {
       gimp_temp_buf_unref ((GimpTempBuf *) mask_buf);
diff --git a/app/core/gimpbrushgenerated.c b/app/core/gimpbrushgenerated.c
index 0284bddef6..dacdaa8683 100644
--- a/app/core/gimpbrushgenerated.c
+++ b/app/core/gimpbrushgenerated.c
@@ -405,11 +405,11 @@ gauss (gdouble f)
 }
 
 /* set up lookup table */
-static guchar *
+static gfloat *
 gimp_brush_generated_calc_lut (gdouble radius,
                                gdouble hardness)
 {
-  guchar  *lookup;
+  gfloat  *lookup;
   gint     length;
   gint     x;
   gdouble  d;
@@ -419,7 +419,7 @@ gimp_brush_generated_calc_lut (gdouble radius,
 
   length = OVERSAMPLING * ceil (1 + sqrt (2 * SQR (ceil (radius + 1.0))));
 
-  lookup = g_malloc (length);
+  lookup = gegl_scratch_new (gfloat, length);
   sum = 0.0;
 
   if ((1.0 - hardness) < 0.0000004)
@@ -449,12 +449,12 @@ gimp_brush_generated_calc_lut (gdouble radius,
         buffer[x % OVERSAMPLING] = gauss (pow (d / radius, exponent));
 
       sum += buffer[x % OVERSAMPLING];
-      lookup[x++] = RINT (sum * (255.0 / OVERSAMPLING));
+      lookup[x++] = sum / OVERSAMPLING;
     }
 
   while (x < length)
     {
-      lookup[x++] = 0;
+      lookup[x++] = 0.0f;
     }
 
   return lookup;
@@ -472,9 +472,9 @@ gimp_brush_generated_calc (GimpBrushGenerated      *brush,
                            GimpVector2             *xaxis,
                            GimpVector2             *yaxis)
 {
-  guchar      *centerp;
-  guchar      *lookup;
-  guchar       a;
+  gfloat      *centerp;
+  gfloat      *lookup;
+  gfloat       a;
   gint         x, y;
   gdouble      c, s, cs, ss;
   GimpVector2  x_axis;
@@ -497,12 +497,12 @@ gimp_brush_generated_calc (GimpBrushGenerated      *brush,
                                  &s, &c, &x_axis, &y_axis);
 
   mask = gimp_temp_buf_new (width, height,
-                            babl_format ("Y u8"));
+                            babl_format ("Y float"));
 
   half_width  = width  / 2;
   half_height = height / 2;
 
-  centerp = gimp_temp_buf_get_data (mask) +
+  centerp = (gfloat *) gimp_temp_buf_get_data (mask) +
             half_height * width + half_width;
 
   lookup = gimp_brush_generated_calc_lut (radius, hardness);
@@ -553,7 +553,7 @@ gimp_brush_generated_calc (GimpBrushGenerated      *brush,
           if (d < radius + 1)
             a = lookup[(gint) RINT (d * OVERSAMPLING)];
           else
-            a = 0;
+            a = 0.0f;
 
           centerp[y * width + x] = a;
 
@@ -562,7 +562,7 @@ gimp_brush_generated_calc (GimpBrushGenerated      *brush,
         }
     }
 
-  g_free (lookup);
+  gegl_scratch_free (lookup);
 
   if (xaxis)
     *xaxis = x_axis;
diff --git a/app/paint/gimpbrushcore-kernels.h b/app/paint/gimpbrushcore-kernels.h
index a9ae0768d0..32503ef56e 100644
--- a/app/paint/gimpbrushcore-kernels.h
+++ b/app/paint/gimpbrushcore-kernels.h
@@ -7,50 +7,110 @@
 #ifndef __GIMP_BRUSH_CORE_KERNELS_H__
 #define __GIMP_BRUSH_CORE_KERNELS_H__
 
+
 #define KERNEL_WIDTH     3
 #define KERNEL_HEIGHT    3
 #define KERNEL_SUBSAMPLE 4
-#define KERNEL_SUM       256
 
 
-/*  Brush pixel subsampling kernels  */
-static const int subsample[5][5][9] =
+#ifdef __cplusplus
+
+template <class T>
+struct Kernel;
+
+template <>
+struct Kernel<guchar>
 {
+  using value_type  = guchar;
+  using kernel_type = guint;
+  using accum_type  = gulong;
+
+  static constexpr kernel_type
+  coeff (kernel_type x)
   {
-    {  64,  64,   0,  64,  64,   0,   0,   0,   0, },
-    {  25, 103,   0,  25, 103,   0,   0,   0,   0, },
-    {   0, 128,   0,   0, 128,   0,   0,   0,   0, },
-    {   0, 103,  25,   0, 103,  25,   0,   0,   0, },
-    {   0,  64,  64,   0,  64,  64,   0,   0,   0, }
-  },
-  {
-    {  25,  25,   0, 103, 103,   0,   0,   0,   0, },
-    {   6,  44,   0,  44, 162,   0,   0,   0,   0, },
-    {   0,  50,   0,   0, 206,   0,   0,   0,   0, },
-    {   0,  44,   6,   0, 162,  44,   0,   0,   0, },
-    {   0,  25,  25,   0, 103, 103,   0,   0,   0, }
-  },
+    return x;
+  }
+
+  static constexpr value_type
+  round (accum_type x)
   {
-    {   0,   0,   0, 128, 128,   0,   0,   0,   0, },
-    {   0,   0,   0,  50, 206,   0,   0,   0,   0, },
-    {   0,   0,   0,   0, 256,   0,   0,   0,   0, },
-    {   0,   0,   0,   0, 206,  50,   0,   0,   0, },
-    {   0,   0,   0,   0, 128, 128,   0,   0,   0, }
-  },
+    return (x + 127) / 256;
+  }
+};
+
+template <>
+struct Kernel<gfloat>
+{
+  using value_type  = gfloat;
+  using kernel_type = gfloat;
+  using accum_type  = gfloat;
+
+  static constexpr kernel_type
+  coeff (kernel_type x)
   {
-    {   0,   0,   0, 103, 103,   0,  25,  25,   0, },
-    {   0,   0,   0,  44, 162,   0,   6,  44,   0, },
-    {   0,   0,   0,   0, 206,   0,   0,  50,   0, },
-    {   0,   0,   0,   0, 162,  44,   0,  44,   6, },
-    {   0,   0,   0,   0, 103, 103,   0,  25,  25, }
-  },
+    return x / 256.0f;
+  }
+
+  static constexpr value_type
+  round (accum_type x)
   {
-    {   0,   0,   0,  64,  64,   0,  64,  64,   0, },
-    {   0,   0,   0,  25, 103,   0,  25, 103,   0, },
-    {   0,   0,   0,   0, 128,   0,   0, 128,   0, },
-    {   0,   0,   0,   0, 103,  25,   0, 103,  25, },
-    {   0,   0,   0,   0,  64,  64,   0,  64,  64, }
+    return x;
   }
 };
 
+
+/*  Brush pixel subsampling kernels  */
+template <class T>
+struct Subsample : Kernel<T>
+{
+  #define C(x) (Subsample::coeff (x))
+
+  static constexpr typename Subsample::kernel_type kernel[5][5][9] =
+  {
+    {
+      { C( 64), C( 64), C(  0), C( 64), C( 64), C(  0), C(  0), C(  0), C(  0), },
+      { C( 25), C(103), C(  0), C( 25), C(103), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C(128), C(  0), C(  0), C(128), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C(103), C( 25), C(  0), C(103), C( 25), C(  0), C(  0), C(  0), },
+      { C(  0), C( 64), C( 64), C(  0), C( 64), C( 64), C(  0), C(  0), C(  0), }
+    },
+    {
+      { C( 25), C( 25), C(  0), C(103), C(103), C(  0), C(  0), C(  0), C(  0), },
+      { C(  6), C( 44), C(  0), C( 44), C(162), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C( 50), C(  0), C(  0), C(206), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C( 44), C(  6), C(  0), C(162), C( 44), C(  0), C(  0), C(  0), },
+      { C(  0), C( 25), C( 25), C(  0), C(103), C(103), C(  0), C(  0), C(  0), }
+    },
+    {
+      { C(  0), C(  0), C(  0), C(128), C(128), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C(  0), C(  0), C( 50), C(206), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(256), C(  0), C(  0), C(  0), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(206), C( 50), C(  0), C(  0), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(128), C(128), C(  0), C(  0), C(  0), }
+    },
+    {
+      { C(  0), C(  0), C(  0), C(103), C(103), C(  0), C( 25), C( 25), C(  0), },
+      { C(  0), C(  0), C(  0), C( 44), C(162), C(  0), C(  6), C( 44), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(206), C(  0), C(  0), C( 50), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(162), C( 44), C(  0), C( 44), C(  6), },
+      { C(  0), C(  0), C(  0), C(  0), C(103), C(103), C(  0), C( 25), C( 25), }
+    },
+    {
+      { C(  0), C(  0), C(  0), C( 64), C( 64), C(  0), C( 64), C( 64), C(  0), },
+      { C(  0), C(  0), C(  0), C( 25), C(103), C(  0), C( 25), C(103), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(128), C(  0), C(  0), C(128), C(  0), },
+      { C(  0), C(  0), C(  0), C(  0), C(103), C( 25), C(  0), C(103), C( 25), },
+      { C(  0), C(  0), C(  0), C(  0), C( 64), C( 64), C(  0), C( 64), C( 64), }
+    }
+  };
+
+  #undef C
+};
+
+template <class T>
+constexpr typename Subsample<T>::kernel_type Subsample<T>::kernel[5][5][9];
+
+#endif /* __cplusplus */
+
+
 #endif /* __GIMP_BRUSH_CORE_KERNELS_H__ */
diff --git a/app/paint/gimpbrushcore-loops.cc b/app/paint/gimpbrushcore-loops.cc
index c0b7d6a120..adf866ba23 100644
--- a/app/paint/gimpbrushcore-loops.cc
+++ b/app/paint/gimpbrushcore-loops.cc
@@ -33,28 +33,141 @@ extern "C"
 
 #include "gimpbrushcore.h"
 #include "gimpbrushcore-loops.h"
+
+} /* extern "C" */
+
 #include "gimpbrushcore-kernels.h"
 
 
 #define PIXELS_PER_THREAD \
   (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
 
+#define EPSILON 1e-6
 
+
+template <class T>
 static inline void
-rotate_pointers (gulong  **p,
-                 guint32   n)
+rotate_pointers (T    **p,
+                 gint   n)
 {
-  guint32  i;
-  gulong  *tmp;
+  T    *tmp;
+  gint  i;
 
   tmp = p[0];
 
-  for (i = 0; i < n-1; i++)
-    p[i] = p[i+1];
+  for (i = 0; i < n - 1; i++)
+    p[i] = p[i + 1];
 
   p[i] = tmp;
 }
 
+template <class T>
+static void
+gimp_brush_core_subsample_mask_impl (const GimpTempBuf *mask,
+                                     GimpTempBuf       *dest,
+                                     gint               dest_offset_x,
+                                     gint               dest_offset_y,
+                                     gint               index1,
+                                     gint               index2)
+{
+  using value_type  = typename Subsample<T>::value_type;
+  using kernel_type = typename Subsample<T>::kernel_type;
+  using accum_type  = typename Subsample<T>::accum_type;
+
+  Subsample<T>       subsample;
+  const kernel_type *kernel      = subsample.kernel[index2][index1];
+  gint               mask_width  = gimp_temp_buf_get_width  (mask);
+  gint               mask_height = gimp_temp_buf_get_height (mask);
+  gint               dest_width  = gimp_temp_buf_get_width  (dest);
+  gint               dest_height = gimp_temp_buf_get_height (dest);
+
+  gegl_parallel_distribute_range (
+    mask_height, PIXELS_PER_THREAD / mask_width,
+    [=] (gint y, gint height)
+    {
+      const value_type  *m;
+      value_type        *d;
+      const kernel_type *k;
+      gint               y0;
+      gint               i, j;
+      gint               r, s;
+      gint               offs;
+      accum_type        *accum[KERNEL_HEIGHT];
+
+      /* Allocate and initialize the accum buffer */
+      for (i = 0; i < KERNEL_HEIGHT ; i++)
+        accum[i] = gegl_scratch_new0 (accum_type, dest_width + 1);
+
+      y0 = MAX (y - (KERNEL_HEIGHT - 1), 0);
+
+      m = (const value_type *) gimp_temp_buf_get_data (mask) +
+          y0 * mask_width;
+
+      for (i = y0; i < y; i++)
+        {
+          for (j = 0; j < mask_width; j++)
+            {
+              k = kernel + KERNEL_WIDTH * (y - i);
+              for (r = y - i; r < KERNEL_HEIGHT; r++)
+                {
+                  offs = j + dest_offset_x;
+                  s = KERNEL_WIDTH;
+                  while (s--)
+                    accum[r][offs++] += *m * *k++;
+                }
+              m++;
+            }
+
+          rotate_pointers (accum, KERNEL_HEIGHT);
+        }
+
+      for (i = y; i < y + height; i++)
+        {
+          for (j = 0; j < mask_width; j++)
+            {
+              k = kernel;
+              for (r = 0; r < KERNEL_HEIGHT; r++)
+                {
+                  offs = j + dest_offset_x;
+                  s = KERNEL_WIDTH;
+                  while (s--)
+                    accum[r][offs++] += *m * *k++;
+                }
+              m++;
+            }
+
+          /* store the accum buffer into the destination mask */
+          d = (value_type *) gimp_temp_buf_get_data (dest) +
+              (i + dest_offset_y) * dest_width;
+          for (j = 0; j < dest_width; j++)
+            *d++ = subsample.round (accum[0][j]);
+
+          rotate_pointers (accum, KERNEL_HEIGHT);
+
+          memset (accum[KERNEL_HEIGHT - 1], 0,
+                  sizeof (accum_type) * dest_width);
+        }
+
+      if (y + height == mask_height)
+        {
+          /* store the rest of the accum buffer into the dest mask */
+          while (i + dest_offset_y < dest_height)
+            {
+              d = (value_type *) gimp_temp_buf_get_data (dest) +
+                  (i + dest_offset_y) * dest_width;
+              for (j = 0; j < dest_width; j++)
+                *d++ = subsample.round (accum[0][j]);
+
+              rotate_pointers (accum, KERNEL_HEIGHT);
+              i++;
+            }
+        }
+
+      for (i = KERNEL_HEIGHT - 1; i >= 0; i--)
+        gegl_scratch_free (accum[i]);
+    });
+}
+
 const GimpTempBuf *
 gimp_brush_core_subsample_mask (GimpBrushCore     *core,
                                 const GimpTempBuf *mask,
@@ -62,16 +175,14 @@ gimp_brush_core_subsample_mask (GimpBrushCore     *core,
                                 gdouble            y)
 {
   GimpTempBuf *dest;
+  const Babl  *mask_format;
   gdouble      left;
   gint         index1;
   gint         index2;
   gint         dest_offset_x = 0;
   gint         dest_offset_y = 0;
-  const gint  *kernel;
   gint         mask_width  = gimp_temp_buf_get_width  (mask);
   gint         mask_height = gimp_temp_buf_get_height (mask);
-  gint         dest_width;
-  gint         dest_height;
 
   left = x - floor (x);
   index1 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1));
@@ -79,7 +190,6 @@ gimp_brush_core_subsample_mask (GimpBrushCore     *core,
   left = y - floor (y);
   index2 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1));
 
-
   if ((mask_width % 2) == 0)
     {
       index1 += KERNEL_SUBSAMPLE >> 1;
@@ -102,8 +212,6 @@ gimp_brush_core_subsample_mask (GimpBrushCore     *core,
         }
     }
 
-  kernel = subsample[index2][index1];
-
   if (mask == core->last_subsample_brush_mask &&
       ! core->subsample_cache_invalid)
     {
@@ -122,150 +230,93 @@ gimp_brush_core_subsample_mask (GimpBrushCore     *core,
       core->subsample_cache_invalid   = FALSE;
     }
 
+  mask_format = gimp_temp_buf_get_format (mask);
+
   dest = gimp_temp_buf_new (mask_width  + 2,
                             mask_height + 2,
-                            gimp_temp_buf_get_format (mask));
+                            mask_format);
   gimp_temp_buf_data_clear (dest);
 
-  dest_width  = gimp_temp_buf_get_width  (dest);
-  dest_height = gimp_temp_buf_get_height (dest);
-
   core->subsample_brushes[index2][index1] = dest;
 
-  gegl_parallel_distribute_range (
-    mask_height, PIXELS_PER_THREAD / mask_width,
-    [=] (gint y, gint height)
+  if (mask_format == babl_format ("Y u8"))
     {
-      const guchar *m;
-      guchar       *d;
-      const gint   *k;
-      gint          y0;
-      gint          i, j;
-      gint          r, s;
-      gint          offs;
-      gulong       *accum[KERNEL_HEIGHT];
-
-      /* Allocate and initialize the accum buffer */
-      for (i = 0; i < KERNEL_HEIGHT ; i++)
-        accum[i] = g_new0 (gulong, dest_width + 1);
-
-      y0 = MAX (y - (KERNEL_HEIGHT - 1), 0);
-
-      m = gimp_temp_buf_get_data (mask) + y0 * mask_width;
-
-      for (i = y0; i < y; i++)
-        {
-          for (j = 0; j < mask_width; j++)
-            {
-              k = kernel + KERNEL_WIDTH * (y - i);
-              for (r = y - i; r < KERNEL_HEIGHT; r++)
-                {
-                  offs = j + dest_offset_x;
-                  s = KERNEL_WIDTH;
-                  while (s--)
-                    accum[r][offs++] += *m * *k++;
-                }
-              m++;
-            }
-
-          rotate_pointers (accum, KERNEL_HEIGHT);
-        }
-
-      for (i = y; i < y + height; i++)
-        {
-          for (j = 0; j < mask_width; j++)
-            {
-              k = kernel;
-              for (r = 0; r < KERNEL_HEIGHT; r++)
-                {
-                  offs = j + dest_offset_x;
-                  s = KERNEL_WIDTH;
-                  while (s--)
-                    accum[r][offs++] += *m * *k++;
-                }
-              m++;
-            }
-
-          /* store the accum buffer into the destination mask */
-          d = gimp_temp_buf_get_data (dest) + (i + dest_offset_y) * dest_width;
-          for (j = 0; j < dest_width; j++)
-            *d++ = (accum[0][j] + 127) / KERNEL_SUM;
-
-          rotate_pointers (accum, KERNEL_HEIGHT);
-
-          memset (accum[KERNEL_HEIGHT - 1], 0, sizeof (gulong) * dest_width);
-        }
-
-      if (y + height == mask_height)
-        {
-          /* store the rest of the accum buffer into the dest mask */
-          while (i + dest_offset_y < dest_height)
-            {
-              d = gimp_temp_buf_get_data (dest) + (i + dest_offset_y) * dest_width;
-              for (j = 0; j < dest_width; j++)
-                *d++ = (accum[0][j] + (KERNEL_SUM / 2)) / KERNEL_SUM;
-
-              rotate_pointers (accum, KERNEL_HEIGHT);
-              i++;
-            }
-        }
-
-      for (i = 0; i < KERNEL_HEIGHT ; i++)
-        g_free (accum[i]);
-    });
+      gimp_brush_core_subsample_mask_impl<guchar> (mask, dest,
+                                                   dest_offset_x, dest_offset_y,
+                                                   index1, index2);
+    }
+  else if (mask_format == babl_format ("Y float"))
+    {
+      gimp_brush_core_subsample_mask_impl<gfloat> (mask, dest,
+                                                   dest_offset_x, dest_offset_y,
+                                                   index1, index2);
+    }
+  else
+    {
+      g_warn_if_reached ();
+    }
 
   return dest;
 }
 
-/* #define FANCY_PRESSURE */
-
-const GimpTempBuf *
-gimp_brush_core_pressurize_mask (GimpBrushCore     *core,
-                                 const GimpTempBuf *brush_mask,
-                                 gdouble            x,
-                                 gdouble            y,
-                                 gdouble            pressure)
+/* The simple pressure profile
+ *
+ * It is: I'(I) = MIN (2 * pressure * I, 1)
+ */
+class SimplePressure
 {
-  static guchar      mapi[256];
-  const GimpTempBuf *subsample_mask;
-  gint               i;
+  gfloat scale;
 
-  /* Get the raw subsampled mask */
-  subsample_mask = gimp_brush_core_subsample_mask (core,
-                                                   brush_mask,
-                                                   x, y);
+public:
+  SimplePressure (gdouble pressure)
+  {
+    scale = 2.0 * pressure;
+  }
 
-  /* Special case pressure = 0.5 */
-  if ((gint) (pressure * 100 + 0.5) == 50)
-    return subsample_mask;
+  guchar
+  operator () (guchar x) const
+  {
+    gint v = RINT (scale * x);
 
-  g_clear_pointer (&core->pressure_brush, gimp_temp_buf_unref);
+    return MIN (v, 255);
+  }
 
-  core->pressure_brush =
-    gimp_temp_buf_new (gimp_temp_buf_get_width  (brush_mask) + 2,
-                       gimp_temp_buf_get_height (brush_mask) + 2,
-                       gimp_temp_buf_get_format (brush_mask));
-  gimp_temp_buf_data_clear (core->pressure_brush);
+  gfloat
+  operator () (gfloat x) const
+  {
+    gfloat v = scale * x;
 
-#ifdef FANCY_PRESSURE
+    return MIN (v, 1.0f);
+  }
+
+  template <class T>
+  T
+  operator () (T x) const = delete;
+};
 
-  /* Create the pressure profile
-   *
-   * It is: I'(I) = tanh (20 * (pressure - 0.5) * I)           : pressure > 0.5
-   *        I'(I) = 1 - tanh (20 * (0.5 - pressure) * (1 - I)) : pressure < 0.5
-   *
-   * It looks like:
-   *
-   *    low pressure      medium pressure     high pressure
-   *
-   *         |                   /                  --
-   *         |                  /                  /
-   *        /                  /                  |
-   *      --                  /                   |
-   */
+/* The fancy pressure profile
+ *
+ * It is: I'(I) = tanh (20 * (pressure - 0.5) * I)           : pressure > 0.5
+ *        I'(I) = 1 - tanh (20 * (0.5 - pressure) * (1 - I)) : pressure < 0.5
+ *
+ * It looks like:
+ *
+ *    low pressure      medium pressure     high pressure
+ *
+ *         |                   /                  --
+ *         |                  /                  /
+ *        /                  /                  |
+ *      --                  /                   |
+ */
+class FancyPressure
+{
+  gfloat map[257];
+
+public:
+  FancyPressure (gdouble pressure)
   {
-    gdouble  map[256];
-    gdouble  ds, s, c;
+    gdouble ds, s, c;
+    gint    i;
 
     ds = (pressure - 0.5) * (20.0 / 256.0);
     s  = 0;
@@ -281,7 +332,7 @@ gimp_brush_core_pressurize_mask (GimpBrushCore     *core,
           }
 
         for (i = 0; i < 256; i++)
-          mapi[i] = (gint) (255 * map[i] / map[255]);
+          map[i] = map[i] / map[255];
       }
     else
       {
@@ -294,54 +345,183 @@ gimp_brush_core_pressurize_mask (GimpBrushCore     *core,
             c += s * ds;
           }
 
-        for (i = 0; i < 256; i++)
-          mapi[i] = (gint) (255 * (1 - map[i] / map[0]));
+        for (i = 255; i >= 0; i--)
+          map[i] = 1.0f - map[i] / map[0];
       }
+
+    map[256] = map[255];
   }
 
-#else /* ! FANCY_PRESSURE */
+  guchar
+  operator () (guchar x) const
+  {
+    return RINT (255.0f * map[x]);
+  }
 
+  gfloat
+  operator () (gfloat x) const
   {
-    gdouble j, k;
+    gint   i;
+    gfloat f;
 
-    j = pressure + pressure;
-    k = 0;
+    x *= 255.0f;
 
-    for (i = 0; i < 256; i++)
-      {
-        if (k > 255)
-          mapi[i] = 255;
-        else
-          mapi[i] = (guchar) k;
+    i = floorf (x);
+    f = x - i;
 
-        k += j;
-      }
+    return map[i] + (map[i + 1] - map[i]) * f;
   }
 
-#endif /* FANCY_PRESSURE */
+  template <class T>
+  T
+  operator () (T x) const = delete;
+};
+
+template <class T>
+class CachedPressure
+{
+  T map[T (~0) + 1];
 
-  /* Now convert the brush */
+public:
+  template <class Pressure>
+  CachedPressure (Pressure pressure)
+  {
+    gint i;
 
+    for (i = 0; i < G_N_ELEMENTS (map); i++)
+      map[i] = pressure (T (i));
+  }
+
+  T
+  operator () (T x) const
+  {
+    return map[x];
+  }
+
+  template <class U>
+  U
+  operator () (U x) const = delete;
+};
+
+template <class T,
+          class Pressure>
+void
+gimp_brush_core_pressurize_mask_impl (const GimpTempBuf *mask,
+                                      GimpTempBuf       *dest,
+                                      Pressure           pressure)
+{
   gegl_parallel_distribute_range (
-    gimp_temp_buf_get_width  (subsample_mask) *
-    gimp_temp_buf_get_height (subsample_mask),
+    gimp_temp_buf_get_width  (mask) * gimp_temp_buf_get_height (mask),
     PIXELS_PER_THREAD,
     [=] (gint offset, gint size)
     {
-      const guchar *source;
-      guchar       *dest;
-      gint          i;
+      const T *m;
+      T       *d;
+      gint     i;
 
-      source = gimp_temp_buf_get_data (subsample_mask)       + offset;
-      dest   = gimp_temp_buf_get_data (core->pressure_brush) + offset;
+      m = (const T *) gimp_temp_buf_get_data (mask) + offset;
+      d = (      T *) gimp_temp_buf_get_data (dest) + offset;
 
       for (i = 0; i < size; i++)
-        *dest++ = mapi[(*source++)];
+        *d++ = pressure (*m++);
     });
+}
+
+/* #define FANCY_PRESSURE */
+
+const GimpTempBuf *
+gimp_brush_core_pressurize_mask (GimpBrushCore     *core,
+                                 const GimpTempBuf *brush_mask,
+                                 gdouble            x,
+                                 gdouble            y,
+                                 gdouble            pressure)
+{
+  const GimpTempBuf *subsample_mask;
+  const Babl        *subsample_mask_format;
+  gint               i;
+
+  /* Get the raw subsampled mask */
+  subsample_mask = gimp_brush_core_subsample_mask (core,
+                                                   brush_mask,
+                                                   x, y);
+
+  /* Special case pressure = 0.5 */
+  if (fabs (pressure - 0.5) <= EPSILON)
+    return subsample_mask;
+
+  g_clear_pointer (&core->pressure_brush, gimp_temp_buf_unref);
+
+  subsample_mask_format = gimp_temp_buf_get_format (subsample_mask);
+
+  core->pressure_brush =
+    gimp_temp_buf_new (gimp_temp_buf_get_width  (brush_mask) + 2,
+                       gimp_temp_buf_get_height (brush_mask) + 2,
+                       subsample_mask_format);
+  gimp_temp_buf_data_clear (core->pressure_brush);
+
+#ifdef FANCY_PRESSURE
+  using Pressure = FancyPressure;
+#else
+  using Pressure = SimplePressure;
+#endif
+
+  if (subsample_mask_format == babl_format ("Y u8"))
+    {
+      gimp_brush_core_pressurize_mask_impl<guchar> (subsample_mask,
+                                                    core->pressure_brush,
+                                                    CachedPressure<guchar> (
+                                                      Pressure (pressure)));
+    }
+  else if (subsample_mask_format == babl_format ("Y float"))
+    {
+      gimp_brush_core_pressurize_mask_impl<gfloat> (subsample_mask,
+                                                    core->pressure_brush,
+                                                    Pressure (pressure));
+    }
+  else
+    {
+      g_warn_if_reached ();
+    }
 
   return core->pressure_brush;
 }
 
+template <class T>
+static void
+gimp_brush_core_solidify_mask_impl (const GimpTempBuf *brush_mask,
+                                    GimpTempBuf       *dest,
+                                    gint               dest_offset_x,
+                                    gint               dest_offset_y)
+{
+  gint brush_mask_width  = gimp_temp_buf_get_width  (brush_mask);
+  gint brush_mask_height = gimp_temp_buf_get_height (brush_mask);
+
+  gegl_parallel_distribute_area (
+    GEGL_RECTANGLE (0, 0, brush_mask_width, brush_mask_height),
+    PIXELS_PER_THREAD,
+    [=] (const GeglRectangle *area)
+    {
+      const T *m;
+      gfloat  *d;
+      gint     i, j;
+
+      m = (const T *) gimp_temp_buf_get_data (brush_mask) +
+          area->y * brush_mask_width + area->x;
+      d = ((gfloat *) gimp_temp_buf_get_data (dest) +
+           ((dest_offset_y + 1 + area->y) * gimp_temp_buf_get_width (dest) +
+            (dest_offset_x + 1 + area->x)));
+
+      for (i = 0; i < area->height; i++)
+        {
+          for (j = 0; j < area->width; j++)
+            *d++ = (*m++) ? 1.0 : 0.0;
+
+          m += brush_mask_width     - area->width;
+          d += brush_mask_width + 2 - area->width;
+        }
+    });
+}
+
 const GimpTempBuf *
 gimp_brush_core_solidify_mask (GimpBrushCore     *core,
                                const GimpTempBuf *brush_mask,
@@ -349,6 +529,7 @@ gimp_brush_core_solidify_mask (GimpBrushCore     *core,
                                gdouble            y)
 {
   GimpTempBuf  *dest;
+  const Babl   *brush_mask_format;
   gint          dest_offset_x     = 0;
   gint          dest_offset_y     = 0;
   gint          brush_mask_width  = gimp_temp_buf_get_width  (brush_mask);
@@ -356,8 +537,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore     *core,
 
   if ((brush_mask_width % 2) == 0)
     {
-      while (x < 0)
-        x += brush_mask_width;
+      if (x < 0.0)
+        x = fmod (x, brush_mask_width) + brush_mask_width;
 
       if ((x - floor (x)) >= 0.5)
         dest_offset_x++;
@@ -365,8 +546,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore     *core,
 
   if ((brush_mask_height % 2) == 0)
     {
-      while (y < 0)
-        y += brush_mask_height;
+      if (y < 0.0)
+        y = fmod (y, brush_mask_height) + brush_mask_height;
 
       if ((y - floor (y)) >= 0.5)
         dest_offset_y++;
@@ -390,6 +571,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore     *core,
       core->solid_cache_invalid   = FALSE;
     }
 
+  brush_mask_format = gimp_temp_buf_get_format (brush_mask);
+
   dest = gimp_temp_buf_new (brush_mask_width  + 2,
                             brush_mask_height + 2,
                             babl_format ("Y float"));
@@ -397,32 +580,21 @@ gimp_brush_core_solidify_mask (GimpBrushCore     *core,
 
   core->solid_brushes[dest_offset_y][dest_offset_x] = dest;
 
-  gegl_parallel_distribute_area (
-    GEGL_RECTANGLE (0, 0, brush_mask_width, brush_mask_height),
-    PIXELS_PER_THREAD,
-    [=] (const GeglRectangle *area)
-    {
-      const guchar *m;
-      gfloat       *d;
-      gint          i, j;
-
-      m = gimp_temp_buf_get_data (brush_mask) +
-          area->y * brush_mask_width + area->x;
-      d = ((gfloat *) gimp_temp_buf_get_data (dest) +
-           ((dest_offset_y + 1 + area->y) * gimp_temp_buf_get_width (dest) +
-            (dest_offset_x + 1 + area->x)));
-
-      for (i = 0; i < area->height; i++)
-        {
-          for (j = 0; j < area->width; j++)
-            *d++ = (*m++) ? 1.0 : 0.0;
 
-          m += brush_mask_width     - area->width;
-          d += brush_mask_width + 2 - area->width;
-        }
-    });
+  if (brush_mask_format == babl_format ("Y u8"))
+    {
+      gimp_brush_core_solidify_mask_impl<guchar> (brush_mask, dest,
+                                                  dest_offset_x, dest_offset_y);
+    }
+  else if (brush_mask_format == babl_format ("Y float"))
+    {
+      gimp_brush_core_solidify_mask_impl<gfloat> (brush_mask, dest,
+                                                  dest_offset_x, dest_offset_y);
+    }
+  else
+    {
+      g_warn_if_reached ();
+    }
 
   return dest;
 }
-
-} /* extern "C" */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]