[gimp/wip/alxsa/mypaint-brush-v2] paint: Add support for MyPaint Brushes v2




commit 72fbf5dbe9e33b7efd4591f4d80924821e6286b5
Author: Alx Sa <cmyk student gmail com>
Date:   Sat Oct 1 14:52:29 2022 +0000

    paint: Add support for MyPaint Brushes v2

 app/core/gimpmybrush-load.c        |  13 +-
 app/core/gimpmybrush-private.h     |   3 +
 app/core/gimpmybrush.c             |  35 +-
 app/core/gimpmybrush.h             |   3 +
 app/paint/gimpmybrushcore.c        |  93 ++--
 app/paint/gimpmybrushoptions.c     |  84 +++-
 app/paint/gimpmybrushoptions.h     |   5 +
 app/paint/gimpmybrushsurface.c     | 965 +++++++++++++++++++++++++++++++------
 app/tools/gimpmybrushoptions-gui.c |   5 +
 app/tools/gimpmybrushtool.c        |  21 +
 10 files changed, 1024 insertions(+), 203 deletions(-)
---
diff --git a/app/core/gimpmybrush-load.c b/app/core/gimpmybrush-load.c
index 1dbb9f5bd7..b9bd74b39f 100644
--- a/app/core/gimpmybrush-load.c
+++ b/app/core/gimpmybrush-load.c
@@ -84,7 +84,7 @@ gimp_mybrush_load (GimpContext   *context,
       return NULL;
     }
 
-  mypaint_brush = mypaint_brush_new ();
+  mypaint_brush = mypaint_brush_new_with_buckets (64);
   mypaint_brush_from_defaults (mypaint_brush);
 
   if (! mypaint_brush_from_string (mypaint_brush, (const gchar *) buffer))
@@ -147,6 +147,17 @@ gimp_mybrush_load (GimpContext   *context,
     mypaint_brush_get_base_value (mypaint_brush,
                                   MYPAINT_BRUSH_SETTING_OFFSET_BY_RANDOM);
 
+  /* Version 2 MyPaint Brush options */
+  brush->priv->pigment = -0.1;
+
+  brush->priv->posterize =
+    mypaint_brush_get_base_value (mypaint_brush,
+                                  MYPAINT_BRUSH_SETTING_POSTERIZE);
+
+  brush->priv->posterize_num =
+    mypaint_brush_get_base_value (mypaint_brush,
+                                  MYPAINT_BRUSH_SETTING_POSTERIZE_NUM);
+
   mypaint_brush_unref (mypaint_brush);
 
   return g_list_prepend (NULL, brush);
diff --git a/app/core/gimpmybrush-private.h b/app/core/gimpmybrush-private.h
index 7966c0b20d..31e0fc6be7 100644
--- a/app/core/gimpmybrush-private.h
+++ b/app/core/gimpmybrush-private.h
@@ -27,6 +27,9 @@ struct _GimpMybrushPrivate
   gdouble  radius;
   gdouble  opaque;
   gdouble  hardness;
+  gdouble  pigment;
+  gdouble  posterize;
+  gdouble  posterize_num;
   gdouble  offset_by_random;
   gboolean eraser;
 };
diff --git a/app/core/gimpmybrush.c b/app/core/gimpmybrush.c
index a26c86b227..1a04bfa76c 100644
--- a/app/core/gimpmybrush.c
+++ b/app/core/gimpmybrush.c
@@ -98,10 +98,13 @@ gimp_mybrush_init (GimpMybrush *brush)
 {
   brush->priv = gimp_mybrush_get_instance_private (brush);
 
-  brush->priv->radius   = 1.0;
-  brush->priv->opaque   = 1.0;
-  brush->priv->hardness = 1.0;
-  brush->priv->eraser   = FALSE;
+  brush->priv->radius        = 1.0;
+  brush->priv->opaque        = 1.0;
+  brush->priv->hardness      = 1.0;
+  brush->priv->pigment       = -0.1;
+  brush->priv->posterize     = 0.0;
+  brush->priv->posterize_num = 1.0;
+  brush->priv->eraser        = FALSE;
 }
 
 static void
@@ -264,6 +267,30 @@ gimp_mybrush_get_hardness (GimpMybrush *brush)
   return brush->priv->hardness;
 }
 
+gdouble
+gimp_mybrush_get_pigment (GimpMybrush *brush)
+{
+  g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), -0.1);
+
+  return brush->priv->pigment;
+}
+
+gdouble
+gimp_mybrush_get_posterize (GimpMybrush *brush)
+{
+  g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 0.0);
+
+  return brush->priv->posterize;
+}
+
+gdouble
+gimp_mybrush_get_posterize_num (GimpMybrush *brush)
+{
+  g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 1.0);
+
+  return brush->priv->posterize_num;
+}
+
 gdouble
 gimp_mybrush_get_offset_by_random (GimpMybrush *brush)
 {
diff --git a/app/core/gimpmybrush.h b/app/core/gimpmybrush.h
index 2dad734fe1..4bf8be8912 100644
--- a/app/core/gimpmybrush.h
+++ b/app/core/gimpmybrush.h
@@ -59,6 +59,9 @@ const gchar * gimp_mybrush_get_brush_json       (GimpMybrush *brush);
 gdouble       gimp_mybrush_get_radius           (GimpMybrush *brush);
 gdouble       gimp_mybrush_get_opaque           (GimpMybrush *brush);
 gdouble       gimp_mybrush_get_hardness         (GimpMybrush *brush);
+gdouble       gimp_mybrush_get_pigment          (GimpMybrush *brush);
+gdouble       gimp_mybrush_get_posterize        (GimpMybrush *brush);
+gdouble       gimp_mybrush_get_posterize_num    (GimpMybrush *brush);
 gdouble       gimp_mybrush_get_offset_by_random (GimpMybrush *brush);
 gboolean      gimp_mybrush_get_is_eraser        (GimpMybrush *brush);
 
diff --git a/app/paint/gimpmybrushcore.c b/app/paint/gimpmybrushcore.c
index 108997f1ca..b7e5910cf6 100644
--- a/app/paint/gimpmybrushcore.c
+++ b/app/paint/gimpmybrushcore.c
@@ -80,7 +80,10 @@ static void      gimp_mybrush_core_motion         (GimpPaintCore     *paint_core
                                                    GimpDrawable      *drawable,
                                                    GimpPaintOptions  *paint_options,
                                                    GimpSymmetry      *sym,
-                                                   guint32            time);
+                                                   guint32            time,
+                                                   gfloat             view_zoom,
+                                                   gfloat             view_rotation,
+                                                   gfloat             barrel_rotation);
 static void      gimp_mybrush_core_create_brushes (GimpMybrushCore   *mybrush,
                                                    GimpDrawable      *drawable,
                                                    GimpPaintOptions  *paint_options,
@@ -200,11 +203,12 @@ gimp_mybrush_core_paint (GimpPaintCore    *paint_core,
                          GimpPaintState    paint_state,
                          guint32           time)
 {
-  GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core);
-  GimpContext     *context = GIMP_CONTEXT (paint_options);
-  gint             offset_x;
-  gint             offset_y;
-  GimpRGB          fg;
+  GimpMybrushCore    *mybrush         = GIMP_MYBRUSH_CORE (paint_core);
+  GimpMybrushOptions *mybrush_options = GIMP_MYBRUSH_OPTIONS (paint_options);
+  GimpContext        *context         = GIMP_CONTEXT (paint_options);
+  gint                offset_x;
+  gint                offset_y;
+  GimpRGB             fg;
 
   g_return_if_fail (g_list_length (drawables) == 1);
 
@@ -231,12 +235,14 @@ gimp_mybrush_core_paint (GimpPaintCore    *paint_core,
 
     case GIMP_PAINT_STATE_MOTION:
       gimp_mybrush_core_motion (paint_core, drawables->data, paint_options,
-                                sym, time);
+                                sym, time, mybrush_options->view_zoom,
+                                mybrush_options->view_rotation, 1.0f);
       break;
 
     case GIMP_PAINT_STATE_FINISH:
       gimp_symmetry_set_stateful (sym, FALSE);
-      mypaint_surface_unref ((MyPaintSurface *) mybrush->private->surface);
+      mypaint_surface_unref (mypaint_surface2_to_surface (
+                             (MyPaintSurface2 *) mybrush->private->surface));
       mybrush->private->surface = NULL;
 
       g_list_free_full (mybrush->private->brushes,
@@ -251,10 +257,14 @@ gimp_mybrush_core_motion (GimpPaintCore    *paint_core,
                           GimpDrawable     *drawable,
                           GimpPaintOptions *paint_options,
                           GimpSymmetry     *sym,
-                          guint32           time)
+                          guint32           time,
+                          gfloat            view_zoom,
+                          gfloat            view_rotation,
+                          gfloat            barrel_rotation)
 {
   GimpMybrushCore  *mybrush = GIMP_MYBRUSH_CORE (paint_core);
   MyPaintRectangle  rect;
+  MyPaintRectangles rects = {1, &rect};
   GList            *iter;
   gdouble           dt = 0.0;
   gint              off_x, off_y;
@@ -272,7 +282,8 @@ gimp_mybrush_core_motion (GimpPaintCore    *paint_core,
       gimp_mybrush_core_create_brushes (mybrush, drawable, paint_options, sym);
     }
 
-  mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
+  mypaint_surface_begin_atomic (mypaint_surface2_to_surface (
+                                (MyPaintSurface2 *) mybrush->private->surface));
 
   if (mybrush->private->last_time < 0)
     {
@@ -284,14 +295,15 @@ gimp_mybrush_core_motion (GimpPaintCore    *paint_core,
           MyPaintBrush *brush  = iter->data;
           GimpCoords    coords = *(gimp_symmetry_get_coords (sym, i));
 
-          mypaint_brush_stroke_to (brush,
-                                   (MyPaintSurface *) mybrush->private->surface,
-                                   coords.x - off_x,
-                                   coords.y - off_y,
-                                   0.0f,
-                                   coords.xtilt,
-                                   coords.ytilt,
-                                   1.0f /* Pretend the cursor hasn't moved in a while */);
+          mypaint_brush_stroke_to_2 (brush,
+                                     (MyPaintSurface2 *) mybrush->private->surface,
+                                     coords.x - off_x,
+                                     coords.y - off_y,
+                                     0.0f,
+                                     coords.xtilt,
+                                     coords.ytilt,
+                                     1.0f, /* Pretend the cursor hasn't moved in a while */
+                                     view_zoom, view_rotation, barrel_rotation);
         }
 
       dt = 0.015;
@@ -316,29 +328,30 @@ gimp_mybrush_core_motion (GimpPaintCore    *paint_core,
       GimpCoords    coords   = *(gimp_symmetry_get_coords (sym, i));
       gdouble       pressure = coords.pressure;
 
-      mypaint_brush_stroke_to (brush,
-                               (MyPaintSurface *) mybrush->private->surface,
-                               coords.x - off_x,
-                               coords.y - off_y,
-                               pressure,
-                               coords.xtilt,
-                               coords.ytilt,
-                               dt);
+      mypaint_brush_stroke_to_2 (brush,
+                                 (MyPaintSurface2 *) mybrush->private->surface,
+                                 coords.x - off_x,
+                                 coords.y - off_y,
+                                 pressure,
+                                 coords.xtilt,
+                                 coords.ytilt,
+                                 dt,
+                                 view_zoom, view_rotation, barrel_rotation);
     }
 
   mybrush->private->last_time = time;
 
-  mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface,
-                              &rect);
+  mypaint_surface2_end_atomic ((MyPaintSurface2 *) mybrush->private->surface,
+                               &rects);
 
-  if (rect.width > 0 && rect.height > 0)
+  if (rects.rectangles[0].width > 0 && rects.rectangles[0].height > 0)
     {
-      paint_core->x1 = MIN (paint_core->x1, rect.x);
-      paint_core->y1 = MIN (paint_core->y1, rect.y);
-      paint_core->x2 = MAX (paint_core->x2, rect.x + rect.width);
-      paint_core->y2 = MAX (paint_core->y2, rect.y + rect.height);
+      paint_core->x1 = MIN (paint_core->x1, rects.rectangles[0].x);
+      paint_core->y1 = MIN (paint_core->y1, rects.rectangles[0].y);
+      paint_core->x2 = MAX (paint_core->x2, rects.rectangles[0].x + rects.rectangles[0].width);
+      paint_core->y2 = MAX (paint_core->y2, rects.rectangles[0].y + rects.rectangles[0].height);
 
-      gimp_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height);
+      gimp_drawable_update (drawable, rects.rectangles[0].x, rects.rectangles[0].y, 
rects.rectangles[0].width, rects.rectangles[0].height);
     }
 }
 
@@ -375,7 +388,7 @@ gimp_mybrush_core_create_brushes (GimpMybrushCore  *mybrush,
 
   for (i = 0; i < n_strokes; i++)
     {
-      MyPaintBrush *brush = mypaint_brush_new ();
+      MyPaintBrush *brush = mypaint_brush_new_with_buckets (64);
       const gchar  *brush_data;
 
       mypaint_brush_from_defaults (brush);
@@ -413,6 +426,16 @@ gimp_mybrush_core_create_brushes (GimpMybrushCore  *mybrush,
                                      gimp_drawable_has_alpha (drawable)) ?
                                     1.0f : 0.0f);
 
+      mypaint_brush_set_base_value (brush,
+                                    MYPAINT_BRUSH_SETTING_PAINT_MODE,
+                                    options->pigment);
+      mypaint_brush_set_base_value (brush,
+                                    MYPAINT_BRUSH_SETTING_POSTERIZE,
+                                    options->posterize);
+      mypaint_brush_set_base_value (brush,
+                                    MYPAINT_BRUSH_SETTING_POSTERIZE_NUM,
+                                    options->posterize_num);
+
       mypaint_brush_new_stroke (brush);
 
       mybrush->private->brushes = g_list_prepend (mybrush->private->brushes,
diff --git a/app/paint/gimpmybrushoptions.c b/app/paint/gimpmybrushoptions.c
index 4d515491a8..b208b2582c 100644
--- a/app/paint/gimpmybrushoptions.c
+++ b/app/paint/gimpmybrushoptions.c
@@ -38,9 +38,14 @@
 enum
 {
   PROP_0,
+  PROP_VIEW_ZOOM,
+  PROP_VIEW_ROTATION,
   PROP_RADIUS,
   PROP_OPAQUE,
   PROP_HARDNESS,
+  PROP_PIGMENT,
+  PROP_POSTERIZE,
+  PROP_POSTERIZE_NUM,
   PROP_ERASER,
   PROP_NO_ERASING
 };
@@ -82,6 +87,20 @@ gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass)
 
   context_class->mybrush_changed = gimp_mybrush_options_mybrush_changed;
 
+  GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS,
+                           "viewzoom",
+                           _("View Zoom"),
+                           NULL,
+                           0.0001, G_MAXFLOAT, 1.0,
+                           GIMP_PARAM_STATIC_STRINGS);
+
+  GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS,
+                           "viewrotation",
+                           _("View Rotation"),
+                           NULL,
+                           0.0, 360.0, 0.0,
+                           GIMP_PARAM_STATIC_STRINGS);
+
   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS,
                            "radius",
                            _("Radius"),
@@ -103,6 +122,27 @@ gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass)
                            0.0, 1.0, 1.0,
                            GIMP_PARAM_STATIC_STRINGS);
 
+  GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_PIGMENT,
+                           "pigment",
+                           _("Pigment"),
+                           _("Enable spectral blending"),
+                           -0.1, 1.0, -0.1,
+                           GIMP_PARAM_STATIC_STRINGS);
+
+  GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_POSTERIZE,
+                           "posterize",
+                           _("Posterize"),
+                           NULL,
+                           0.0, 1.0, 0.0,
+                           GIMP_PARAM_STATIC_STRINGS);
+
+  GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_POSTERIZE_NUM,
+                           "posterizenum",
+                           _("Posterize Number"),
+                           NULL,
+                           0.0, 1.28, 1.0,
+                           GIMP_PARAM_STATIC_STRINGS);
+
   GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ERASER,
                             "eraser",
                             _("Erase with this brush"),
@@ -141,6 +181,13 @@ gimp_mybrush_options_set_property (GObject      *object,
 
   switch (property_id)
     {
+    case PROP_VIEW_ZOOM:
+      options->view_zoom = g_value_get_double (value) > 0.0f ?
+                           g_value_get_double (value) : 0.0001f;
+      break;
+    case PROP_VIEW_ROTATION:
+      options->view_rotation = CLAMP (g_value_get_double (value), 0.0f, 360.0f);
+      break;
     case PROP_RADIUS:
       options->radius = g_value_get_double (value);
       break;
@@ -150,6 +197,15 @@ gimp_mybrush_options_set_property (GObject      *object,
     case PROP_OPAQUE:
       options->opaque = g_value_get_double (value);
       break;
+    case PROP_PIGMENT:
+      options->pigment = g_value_get_double (value);
+      break;
+    case PROP_POSTERIZE:
+      options->posterize = g_value_get_double (value);
+      break;
+    case PROP_POSTERIZE_NUM:
+      options->posterize_num = g_value_get_double (value);
+      break;
     case PROP_ERASER:
       options->eraser = g_value_get_boolean (value);
       break;
@@ -173,6 +229,12 @@ gimp_mybrush_options_get_property (GObject    *object,
 
   switch (property_id)
     {
+    case PROP_VIEW_ZOOM:
+      g_value_set_double (value, options->view_zoom);
+      break;
+    case PROP_VIEW_ROTATION:
+      g_value_set_double (value, options->view_rotation);
+      break;
     case PROP_RADIUS:
       g_value_set_double (value, options->radius);
       break;
@@ -182,6 +244,15 @@ gimp_mybrush_options_get_property (GObject    *object,
     case PROP_HARDNESS:
       g_value_set_double (value, options->hardness);
       break;
+    case PROP_PIGMENT:
+      g_value_set_double (value, options->pigment);
+      break;
+    case PROP_POSTERIZE:
+      g_value_set_double (value, options->posterize);
+      break;
+    case PROP_POSTERIZE_NUM:
+      g_value_set_double (value, options->posterize_num);
+      break;
     case PROP_ERASER:
       g_value_set_boolean (value, options->eraser);
       break;
@@ -201,10 +272,15 @@ gimp_mybrush_options_mybrush_changed (GimpContext *context,
 {
   if (brush)
     g_object_set (context,
-                  "radius",   gimp_mybrush_get_radius (brush),
-                  "opaque",   gimp_mybrush_get_opaque (brush),
-                  "hardness", gimp_mybrush_get_hardness (brush),
-                  "eraser",   gimp_mybrush_get_is_eraser (brush),
+                  "viewzoom",       1.0f,
+                  "viewrotation",   0.0f,
+                  "radius",         gimp_mybrush_get_radius (brush),
+                  "opaque",         gimp_mybrush_get_opaque (brush),
+                  "hardness",       gimp_mybrush_get_hardness (brush),
+                  "pigment",        gimp_mybrush_get_pigment (brush),
+                  "posterize",      gimp_mybrush_get_posterize (brush),
+                  "posterizenum",   gimp_mybrush_get_posterize_num (brush),
+                  "eraser",         gimp_mybrush_get_is_eraser (brush),
                   NULL);
 }
 
diff --git a/app/paint/gimpmybrushoptions.h b/app/paint/gimpmybrushoptions.h
index b1b5cd55a3..d6305efd7a 100644
--- a/app/paint/gimpmybrushoptions.h
+++ b/app/paint/gimpmybrushoptions.h
@@ -36,9 +36,14 @@ struct _GimpMybrushOptions
 {
   GimpPaintOptions  parent_instance;
 
+  gdouble           view_zoom;
+  gdouble           view_rotation;
   gdouble           radius;
   gdouble           opaque;
   gdouble           hardness;
+  gdouble           pigment;
+  gdouble           posterize;
+  gdouble           posterize_num;
   gboolean          eraser;
   gboolean          no_erasing;
 };
diff --git a/app/paint/gimpmybrushsurface.c b/app/paint/gimpmybrushsurface.c
index 436c77ced2..1d134fa225 100644
--- a/app/paint/gimpmybrushsurface.c
+++ b/app/paint/gimpmybrushsurface.c
@@ -17,6 +17,7 @@
 
 #include "config.h"
 #include <gegl.h>
+#include <stdint.h>
 
 #include <mypaint-surface.h>
 
@@ -25,25 +26,149 @@
 #include "libgimpmath/gimpmath.h"
 
 #include <cairo.h>
+#ifdef _OPENMP
+#include <omp.h>
+#endif
+
 #include <gdk-pixbuf/gdk-pixbuf.h>
+#include "fastapprox/fastpow.h"
 #include "libgimpcolor/gimpcolor.h"
 
 #include "gimpmybrushoptions.h"
 #include "gimpmybrushsurface.h"
 
+#define WGM_EPSILON 0.001
 
 struct _GimpMybrushSurface
 {
-  MyPaintSurface surface;
-  GeglBuffer *buffer;
-  GeglBuffer *paint_mask;
-  gint        paint_mask_x;
-  gint        paint_mask_y;
-  GeglRectangle dirty;
-  GimpComponentMask component_mask;
+  MyPaintSurface2     surface;
+  GeglBuffer         *buffer;
+  GeglBuffer         *paint_mask;
+  gint                paint_mask_x;
+  gint                paint_mask_y;
+  GeglRectangle       dirty;
+  GimpComponentMask   component_mask;
   GimpMybrushOptions *options;
 };
 
+static const float T_MATRIX_SMALL[3][10] = 
{{0.026595621243689,0.049779426257903,0.022449850859496,-0.218453689278271
+,-0.256894883201278,0.445881722194840,0.772365886289756,0.194498761382537
+,0.014038157587820,0.007687264480513}
+,{-0.032601672674412,-0.061021043498478,-0.052490001018404
+,0.206659098273522,0.572496335158169,0.317837248815438,-0.021216624031211
+,-0.019387668756117,-0.001521339050858,-0.000835181622534}
+,{0.339475473216284,0.635401374177222,0.771520797089589,0.113222640692379
+,-0.055251113343776,-0.048222578468680,-0.012966666339586
+,-0.001523814504223,-0.000094718948810,-0.000051604594741}};
+
+static const float spectral_r_small[10] = 
{0.009281362787953,0.009732627042016,0.011254252737167,0.015105578649573
+,0.024797924177217,0.083622585502406,0.977865045723212,1.000000000000000
+,0.999961046144372,0.999999992756822};
+
+static const float spectral_g_small[10] = 
{0.002854127435775,0.003917589679914,0.012132151699187,0.748259205918013
+,1.000000000000000,0.865695937531795,0.037477469241101,0.022816789725717
+,0.021747419446456,0.021384940572308};
+
+static const float spectral_b_small[10] = 
{0.537052150373386,0.546646402401469,0.575501819073983,0.258778829633924
+,0.041709923751716,0.012662638828324,0.007485593127390,0.006766900622462
+,0.006699764779016,0.006676219883241};
+
+void
+rgb_to_spectral                  (float     r,
+                                  float     g,
+                                  float     b,
+                                  float    *spectral_);
+void
+spectral_to_rgb                  (float    *spectral,
+                                  float    *rgb_);
+
+float
+spectral_blend_factor            (float     x);
+
+void
+get_color_pixels_legacy          (float     mask,
+                                  float    *pixel,
+                                  float    *sum_weight,
+                                  float    *sum_r,
+                                  float    *sum_g,
+                                  float    *sum_b,
+                                  float    *sum_a);
+
+void
+get_color_pixels_accumulate      (float     mask,
+                                  float    *pixel,
+                                  float    *sum_weight,
+                                  float    *sum_r,
+                                  float    *sum_g,
+                                  float    *sum_b,
+                                  float    *sum_a,
+                                  float     paint,
+                                  uint16_t  sample_interval,
+                                  float     random_sample_rate,
+                                  uint16_t  interval_counter);
+
+void
+draw_dab_pixels_BlendMode_Normal (float    *mask,
+                                  float    *pixel,
+                                  float     color_r,
+                                  float     color_g,
+                                  float     color_b,
+                                  float     opacity);
+
+void
+draw_dab_pixels_BlendMode_Normal_Paint
+                                 (float    *mask,
+                                  float    *pixel,
+                                  float     color_r,
+                                  float     color_g,
+                                  float     color_b,
+                                  float     opacity);
+
+void
+draw_dab_pixels_BlendMode_Normal_and_Eraser
+                                 (float    *mask,
+                                  float    *pixel,
+                                  float     color_r,
+                                  float     color_g,
+                                  float     color_b,
+                                  float     color_a,
+                                  float     opacity);
+
+void
+draw_dab_pixels_BlendMode_Normal_and_Eraser_Paint
+                                 (float    *mask,
+                                  float    *pixel,
+                                  float     color_r,
+                                  float     color_g,
+                                  float     color_b,
+                                  float     color_a,
+                                  float     opacity);
+
+void
+draw_dab_pixels_BlendMode_LockAlpha
+                                 (float    *mask,
+                                  float    *pixel,
+                                  float     color_r,
+                                  float     color_g,
+                                  float     color_b,
+                                  float     opacity);
+
+void
+draw_dab_pixels_BlendMode_LockAlpha_Paint
+                                 (float    *mask,
+                                  float    *pixel,
+                                  float     color_r,
+                                  float     color_g,
+                                  float     color_b,
+                                  float     opacity);
+
+void
+draw_dab_pixels_BlendMode_Posterize
+                                 (float    *mask,
+                                  float    *pixel,
+                                  float     opacity,
+                                  float     posterize_num);
+
 /* --- Taken from mypaint-tiled-surface.c --- */
 static inline float
 calculate_rr (int   xp,
@@ -200,6 +325,401 @@ calculate_rr_antialiased (int  xp,
 
     return 1.0f - visibilityNear;
 }
+
+/* -- Taken from helpers.c -- */
+void
+rgb_to_spectral (float  r,
+                 float  g,
+                 float  b,
+                 float *spectral_)
+{
+  float offset = 1.0 - WGM_EPSILON;
+  float spec_r[10] = {0};
+  float spec_g[10] = {0};
+  float spec_b[10] = {0};
+
+  r = r * offset + WGM_EPSILON;
+  g = g * offset + WGM_EPSILON;
+  b = b * offset + WGM_EPSILON;
+  /* upsample rgb to spectral primaries */
+
+  for (int i = 0; i < 10; i++) {
+    spec_r[i] = spectral_r_small[i] * r;
+  }
+  for (int i = 0; i < 10; i++) {
+    spec_g[i] = spectral_g_small[i] * g;
+  }
+  for (int i = 0; i < 10; i++) {
+    spec_b[i] = spectral_b_small[i] * b;
+  }
+  //collapse into one spd
+  for (int i = 0; i < 10; i++) {
+    spectral_[i] += spec_r[i] + spec_g[i] + spec_b[i];
+  }
+}
+
+void
+spectral_to_rgb (float *spectral,
+                 float *rgb_)
+{
+  float offset = 1.0 - WGM_EPSILON;
+  /* We need this tmp. array to allow auto vectorization. */
+  float tmp[3] = {0};
+  for (int i=0; i<10; i++) {
+    tmp[0] += T_MATRIX_SMALL[0][i] * spectral[i];
+    tmp[1] += T_MATRIX_SMALL[1][i] * spectral[i];
+    tmp[2] += T_MATRIX_SMALL[2][i] * spectral[i];
+  }
+  for (int i=0; i<3; i++) {
+    rgb_[i] = CLAMP((tmp[i] - WGM_EPSILON) / offset, 0.0f, 1.0f);
+  }
+}
+
+/* -- Taken from brushmode.c -- */
+void
+get_color_pixels_legacy (float  mask,
+                         float *pixel,
+                         float *sum_weight,
+                         float *sum_r,
+                         float *sum_g,
+                         float *sum_b,
+                         float *sum_a)
+{
+  *sum_r += mask * pixel[RED];
+  *sum_g += mask * pixel[GREEN];
+  *sum_b += mask * pixel[BLUE];
+  *sum_a += mask * pixel[ALPHA];
+  *sum_weight += mask;
+}
+
+void
+get_color_pixels_accumulate (float    mask,
+                             float   *pixel,
+                             float   *sum_weight,
+                             float   *sum_r,
+                             float   *sum_g,
+                             float   *sum_b,
+                             float   *sum_a,
+                             float    paint,
+                             uint16_t sample_interval,
+                             float    random_sample_rate,
+                             uint16_t interval_counter)
+{
+  const int random_sample_threshold = (int) (random_sample_rate * G_MAXINT);
+  float     avg_spectral[10]        = {0};
+  float     avg_rgb[3]              = {*sum_r, *sum_g, *sum_b};
+  float     spec_rgb[3]             = {0};
+
+  /* V1 Brush Code */
+  if (paint < 0.0f)
+    {
+      get_color_pixels_legacy (mask, pixel, sum_weight,
+                               sum_r, sum_g, sum_b, sum_a);
+      return;
+    }
+
+  rgb_to_spectral (*sum_r, *sum_g, *sum_b, avg_spectral);
+
+  if (interval_counter == 0 || rand() < random_sample_threshold)
+    {
+      float fac_a, fac_b;
+      float a = mask * pixel[ALPHA];
+      float alpha_sums = a + *sum_a;
+
+      *sum_weight += mask;
+
+      fac_a = fac_b = 1.0f;
+      if (alpha_sums > 0.0f)
+        {
+          fac_a = a / alpha_sums;
+          fac_b = 1.0 - fac_a;
+        }
+
+      if (paint > 0.0f && pixel[ALPHA] > 0)
+        {
+          float spectral[10] = {0};
+          rgb_to_spectral (pixel[RED] / pixel[ALPHA],
+                           pixel[GREEN] / pixel[ALPHA],
+                           pixel[BLUE] / pixel[ALPHA],
+                           spectral);
+
+          for (int i = 0; i < 10; i++)
+            avg_spectral[i] =
+              fastpow (spectral[i], fac_a) * fastpow (avg_spectral[i], fac_b);
+        }
+
+      if (paint < 1.0f && pixel[ALPHA] > 0)
+        {
+          for (int i = 0; i < 3; i++)
+            avg_rgb[i] = pixel[i] * fac_a / pixel[ALPHA] + avg_rgb[i] * fac_b;
+        }
+
+      *sum_a += a;
+    }
+
+  spectral_to_rgb (avg_spectral, spec_rgb);
+
+  *sum_r = spec_rgb[0] * paint + (1.0 - paint) * avg_rgb[0];
+  *sum_g = spec_rgb[1] * paint + (1.0 - paint) * avg_rgb[1];
+  *sum_b = spec_rgb[2] * paint + (1.0 - paint) * avg_rgb[2];
+}
+
+// Fast sigmoid-like function with constant offsets, used to get a
+// fairly smooth transition between additive and spectral blending.
+float
+spectral_blend_factor (float x)
+{
+  const float ver_fac = 1.65; // vertical compression factor
+  const float hor_fac = 8.0f; // horizontal compression factor
+  const float hor_offs = 3.0f; // horizontal offset (slightly left of center)
+  const float b = x * hor_fac - hor_offs;
+  return 0.5 + b / (1 + fabsf(b) * ver_fac);
+}
+
+void
+draw_dab_pixels_BlendMode_Normal (float *mask,
+                                  float *pixel,
+                                  float  color_r,
+                                  float  color_g,
+                                  float  color_b,
+                                  float  opacity)
+{
+  float src_term = *mask * opacity;
+  float dst_term = 1.0f - src_term;
+
+  pixel[RED]   = color_r * src_term + pixel[RED] * dst_term;
+  pixel[GREEN] = color_g * src_term + pixel[GREEN] * dst_term;
+  pixel[BLUE]  = color_b * src_term + pixel[BLUE] * dst_term;
+}
+
+void
+draw_dab_pixels_BlendMode_Normal_Paint (float *mask,
+                                        float *pixel,
+                                        float  color_r,
+                                        float  color_g,
+                                        float  color_b,
+                                        float  opacity)
+{
+  float spectral_a[10]      = {0};
+  float spectral_b[10]      = {0};
+  float spectral_result[10] = {0};
+  float rgb_result[3]       = {0};
+  float src_term;
+  float dst_term;
+  float fac_a;
+  float fac_b;
+
+  rgb_to_spectral (color_r, color_g, color_b, spectral_a);
+  opacity = MAX (opacity, 1.5);
+
+  src_term = *mask * opacity;
+  dst_term = 1.0f - src_term;
+
+  if (pixel[ALPHA] <= 0)
+    {
+      pixel[ALPHA] = src_term + dst_term * pixel[ALPHA];
+      pixel[RED]   = src_term * color_r + dst_term * pixel[RED];
+      pixel[GREEN] = src_term * color_g + dst_term * pixel[GREEN];
+      pixel[BLUE]  = src_term * color_b + dst_term * pixel[BLUE];
+    }
+  else
+    {
+      fac_a = src_term / (src_term + dst_term * pixel[ALPHA]);
+      fac_b = 1.0f - fac_a;
+
+      rgb_to_spectral (pixel[RED] / pixel[ALPHA],
+                       pixel[GREEN] / pixel[ALPHA],
+                       pixel[BLUE] / pixel[ALPHA],
+                       spectral_b);
+
+      for (int i = 0; i < 10; i++)
+        spectral_result[i] =
+          fastpow (spectral_a[i], fac_a) * fastpow (spectral_b[i], fac_b);
+
+      spectral_to_rgb (spectral_result, rgb_result);
+      pixel[ALPHA] = src_term + dst_term * pixel[ALPHA];
+
+      pixel[RED]   = (rgb_result[0] * pixel[ALPHA]);
+      pixel[GREEN] = (rgb_result[1] * pixel[ALPHA]);
+      pixel[BLUE]  = (rgb_result[2] * pixel[ALPHA]);
+    }
+}
+
+void
+draw_dab_pixels_BlendMode_Normal_and_Eraser (float *mask,
+                                             float *pixel,
+                                             float  color_r,
+                                             float  color_g,
+                                             float  color_b,
+                                             float  color_a,
+                                             float  opacity)
+{
+  float src_term = *mask * opacity;
+  float dst_term = 1.0f - src_term;
+
+  src_term *= color_a;
+
+  pixel[RED]   = color_r * src_term + pixel[RED] * dst_term;
+  pixel[GREEN] = color_g * src_term + pixel[GREEN] * dst_term;
+  pixel[BLUE]  = color_b * src_term + pixel[BLUE] * dst_term;
+}
+
+void
+draw_dab_pixels_BlendMode_Normal_and_Eraser_Paint (float *mask,
+                                                   float *pixel,
+                                                   float  color_r,
+                                                   float  color_g,
+                                                   float  color_b,
+                                                   float  color_a,
+                                                   float  opacity)
+{
+  float spectral_a[10]      = {0};
+  float spectral_b[10]      = {0};
+  float spectral_result[10] = {0};
+  float rgb[3]              = {0};
+  float rgb_result[3]       = {0};
+  float src_term            = *mask * opacity;
+  float dst_term            = 1.0f - src_term;
+  float src_term2           = src_term * color_a;
+  float out_term            = src_term2 + dst_term * pixel[ALPHA];
+  float fac_a;
+  float fac_b;
+  float spectral_factor;
+  float additive_factor;
+
+  rgb_to_spectral (color_r, color_g, color_b, spectral_a);
+
+  spectral_factor =
+    CLAMP (spectral_blend_factor (pixel[ALPHA]), 0.0f, 1.0f);
+  additive_factor = 1.0 - spectral_factor;
+  if (additive_factor)
+    {
+      rgb[0] = src_term2 * color_r + dst_term * pixel[RED];
+      rgb[1] = src_term2 * color_g + dst_term * pixel[GREEN];
+      rgb[2] = src_term2 * color_b + dst_term * pixel[BLUE];
+    }
+
+  if (spectral_factor && pixel[ALPHA] != 0)
+    {
+      /* Convert straightened tile pixel color to a spectral */
+      rgb_to_spectral (pixel[RED] / pixel[ALPHA],
+                       pixel[GREEN] / pixel[ALPHA],
+                       pixel[BLUE] / pixel[ALPHA],
+                       spectral_b);
+
+      fac_a = src_term / (src_term + dst_term * pixel[ALPHA]);
+      fac_a *= color_a;
+      fac_b = 1.0 - fac_a;
+
+      /* Mix input and tile pixel colors using WGM */
+      for (int i = 0; i < 10; i++) {
+        spectral_result[i] =
+          fastpow (spectral_a[i], fac_a) * fastpow (spectral_b[i], fac_b);
+      }
+
+      /* Convert back to RGB */
+      spectral_to_rgb (spectral_result, rgb_result);
+
+      for (int i = 0; i < 3; i++)
+        rgb[i] = (additive_factor * rgb[i]) +
+          (spectral_factor * rgb_result[i] * out_term);
+    }
+
+  pixel[ALPHA] = out_term;
+  pixel[RED] = rgb[0];
+  pixel[GREEN] = rgb[1];
+  pixel[BLUE] = rgb[2];
+}
+
+void
+draw_dab_pixels_BlendMode_LockAlpha (float *mask,
+                                     float *pixel,
+                                     float  color_r,
+                                     float  color_g,
+                                     float  color_b,
+                                     float  opacity)
+{
+  float src_term = *mask * opacity;
+  float dst_term = 1.0f - src_term;
+
+  src_term /= pixel[ALPHA];
+
+  pixel[RED]   = color_r * src_term + pixel[RED] * dst_term;
+  pixel[GREEN] = color_g * src_term + pixel[GREEN] * dst_term;
+  pixel[BLUE]  = color_b * src_term + pixel[BLUE] * dst_term;
+}
+
+void draw_dab_pixels_BlendMode_LockAlpha_Paint (float *mask,
+                                                float    *pixel,
+                                                float     color_r,
+                                                float     color_g,
+                                                float     color_b,
+                                                float     opacity)
+{
+  float spectral_a[10]      = {0};
+  float spectral_b[10]      = {0};
+  float spectral_result[10] = {0};
+  float rgb_result[3]       = {0};
+  float src_term;
+  float dst_term;
+  float fac_a;
+  float fac_b;
+
+  rgb_to_spectral (color_r, color_g, color_b, spectral_a);
+  opacity = MAX (opacity, 1.5);
+
+  src_term = *mask * opacity;
+  dst_term = 1.0f - src_term;
+  src_term *= pixel[ALPHA];
+
+  if (pixel[ALPHA] <= 0)
+    {
+      pixel[RED]   = src_term * color_r + dst_term * pixel[RED];
+      pixel[GREEN] = src_term * color_g + dst_term * pixel[GREEN];
+      pixel[BLUE]  = src_term * color_b + dst_term * pixel[BLUE];
+    }
+  else
+    {
+      fac_a = src_term / (src_term + dst_term * pixel[ALPHA]);
+      fac_b = 1.0f - fac_a;
+
+      rgb_to_spectral (pixel[RED] / pixel[ALPHA],
+                       pixel[GREEN] / pixel[ALPHA],
+                       pixel[BLUE] / pixel[ALPHA],
+                       spectral_b);
+
+      for (int i = 0; i < 10; i++)
+        spectral_result[i] =
+          fastpow (spectral_a[i], fac_a) * fastpow (spectral_b[i], fac_b);
+
+      spectral_to_rgb (spectral_result, rgb_result);
+      pixel[ALPHA] = src_term + dst_term * pixel[ALPHA];
+
+      pixel[RED]   = rgb_result[0] * pixel[ALPHA];
+      pixel[GREEN] = rgb_result[1] * pixel[ALPHA];
+      pixel[BLUE]  = rgb_result[2] * pixel[ALPHA];
+    }
+}
+
+void
+draw_dab_pixels_BlendMode_Posterize (float *mask,
+                                     float *pixel,
+                                     float  opacity,
+                                     float  posterize_num)
+{
+  float post_r = ROUND (pixel[RED] * posterize_num) / posterize_num;
+  float post_g = ROUND (pixel[GREEN] * posterize_num) / posterize_num;
+  float post_b = ROUND (pixel[BLUE] * posterize_num) / posterize_num;
+
+  float src_term = *mask * opacity;
+  float dst_term = 1 - src_term;
+
+  pixel[RED]   = src_term * post_r + dst_term * pixel[RED];
+  pixel[GREEN] = src_term * post_g + dst_term * pixel[GREEN];
+  pixel[BLUE]  = src_term * post_b + dst_term * pixel[BLUE];
+}
+
+
 /* -- end mypaint code */
 
 static inline float
@@ -230,128 +750,43 @@ calculate_dab_roi (float x,
 }
 
 static void
-gimp_mypaint_surface_get_color (MyPaintSurface *base_surface,
-                                float           x,
-                                float           y,
-                                float           radius,
-                                float          *color_r,
-                                float          *color_g,
-                                float          *color_b,
-                                float          *color_a)
+gimp_mypaint_surface_begin_atomic (MyPaintSurface *base_surface)
 {
-  GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
-  GeglRectangle dabRect;
-
-  if (radius < 1.0f)
-    radius = 1.0f;
-
-  dabRect = calculate_dab_roi (x, y, radius);
-
-  *color_r = 0.0f;
-  *color_g = 0.0f;
-  *color_b = 0.0f;
-  *color_a = 0.0f;
-
-  if (dabRect.width > 0 || dabRect.height > 0)
-  {
-    const float one_over_radius2 = 1.0f / (radius * radius);
-    float sum_weight = 0.0f;
-    float sum_r = 0.0f;
-    float sum_g = 0.0f;
-    float sum_b = 0.0f;
-    float sum_a = 0.0f;
 
-     /* Read in clamp mode to avoid transparency bleeding in at the edges */
-    GeglBufferIterator *iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0,
-                                                         babl_format ("R'aG'aB'aA float"),
-                                                         GEGL_BUFFER_READ,
-                                                         GEGL_ABYSS_CLAMP, 2);
-    if (surface->paint_mask)
-      {
-        GeglRectangle mask_roi = dabRect;
-        mask_roi.x -= surface->paint_mask_x;
-        mask_roi.y -= surface->paint_mask_y;
-        gegl_buffer_iterator_add (iter, surface->paint_mask, &mask_roi, 0,
-                                  babl_format ("Y float"),
-                                  GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
-      }
-
-    while (gegl_buffer_iterator_next (iter))
-      {
-        float *pixel = (float *)iter->items[0].data;
-        float *mask;
-        int iy, ix;
-
-        if (surface->paint_mask)
-          mask = iter->items[1].data;
-        else
-          mask = NULL;
-
-        for (iy = iter->items[0].roi.y; iy < iter->items[0].roi.y + iter->items[0].roi.height; iy++)
-          {
-            float yy = (iy + 0.5f - y);
-            for (ix = iter->items[0].roi.x; ix < iter->items[0].roi.x +  iter->items[0].roi.width; ix++)
-              {
-                /* pixel_weight == a standard dab with hardness = 0.5, aspect_ratio = 1.0, and angle = 0.0 */
-                float xx = (ix + 0.5f - x);
-                float rr = (yy * yy + xx * xx) * one_over_radius2;
-                float pixel_weight = 0.0f;
-                if (rr <= 1.0f)
-                  pixel_weight = 1.0f - rr;
-                if (mask)
-                  pixel_weight *= *mask;
-
-                sum_r += pixel_weight * pixel[RED];
-                sum_g += pixel_weight * pixel[GREEN];
-                sum_b += pixel_weight * pixel[BLUE];
-                sum_a += pixel_weight * pixel[ALPHA];
-                sum_weight += pixel_weight;
-
-                pixel += 4;
-                if (mask)
-                  mask += 1;
-              }
-          }
-      }
-
-    if (sum_a > 0.0f && sum_weight > 0.0f)
-      {
-        sum_r /= sum_weight;
-        sum_g /= sum_weight;
-        sum_b /= sum_weight;
-        sum_a /= sum_weight;
-
-        sum_r /= sum_a;
-        sum_g /= sum_a;
-        sum_b /= sum_a;
+}
 
-        /* FIXME: Clamping is wrong because GEGL allows alpha > 1, this should probably re-multipy things */
-        *color_r = CLAMP(sum_r, 0.0f, 1.0f);
-        *color_g = CLAMP(sum_g, 0.0f, 1.0f);
-        *color_b = CLAMP(sum_b, 0.0f, 1.0f);
-        *color_a = CLAMP(sum_a, 0.0f, 1.0f);
-      }
-  }
+static void
+gimp_mypaint_surface_destroy (MyPaintSurface *base_surface)
+{
+  GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
 
+  g_clear_object (&surface->buffer);
+  g_clear_object (&surface->paint_mask);
+  g_free (surface);
 }
 
+/* MyPaintSurface2 implementation */
 static int
-gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
-                               float           x,
-                               float           y,
-                               float           radius,
-                               float           color_r,
-                               float           color_g,
-                               float           color_b,
-                               float           opaque,
-                               float           hardness,
-                               float           color_a,
-                               float           aspect_ratio,
-                               float           angle,
-                               float           lock_alpha,
-                               float           colorize)
+gimp_mypaint_surface_draw_dab_2 (MyPaintSurface2 *base_surface,
+                                float             x,
+                                float             y,
+                                float             radius,
+                                float             color_r,
+                                float             color_g,
+                                float             color_b,
+                                float             opaque,
+                                float             hardness,
+                                float             color_a,
+                                float             aspect_ratio,
+                                float             angle,
+                                float             lock_alpha,
+                                float             colorize,
+                                float             posterize,
+                                float             posterize_num,
+                                float             paint)
 {
-  GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
+  /* Placeholder - eventually implement here */
+  GimpMybrushSurface *surface = (GimpMybrushSurface *) base_surface;
   GeglBufferIterator *iter;
   GeglRectangle       dabRect;
   GimpComponentMask   component_mask = surface->component_mask;
@@ -374,7 +809,8 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
   r_aa_start = MAX (r_aa_start, 0);
   r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio;
 
-  normal_mode = opaque * (1.0f - colorize);
+  posterize = CLAMP (posterize, 0.0f, 1.0f);
+  normal_mode = opaque * (1.0f - colorize) * (1.0f - posterize);
   colorize = opaque * colorize;
 
   /* FIXME: This should use the real matrix values to trim aspect_ratio dabs */
@@ -432,16 +868,52 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
               g = pixel[GREEN];
               b = pixel[BLUE];
 
-              if (a > 0.0f)
+              /* v1 Brush code */
+              if (paint < 1.0f)
                 {
-                  /* By definition the ratio between each color[] and pixel[] component in a 
non-pre-multipled blend always sums to 1.0f.
-                   * Originally this would have been "(color[n] * alpha * color_a + pixel[n] * dst_alpha * 
(1.0f - alpha)) / a",
-                   * instead we only calculate the cheaper term. */
-                  float src_term = (alpha * color_a) / a;
-                  float dst_term = 1.0f - src_term;
-                  r = color_r * src_term + r * dst_term;
-                  g = color_g * src_term + g * dst_term;
-                  b = color_b * src_term + b * dst_term;
+                  if (color_a == 1.0f)
+                    draw_dab_pixels_BlendMode_Normal (&alpha, pixel,
+                                                      color_r, color_g, color_b,
+                                                      normal_mode * opaque * (1 - paint));
+                  else
+                    draw_dab_pixels_BlendMode_Normal_and_Eraser (&alpha, pixel,
+                                                                 color_r, color_g,
+                                                                 color_b, color_a,
+                                                                 normal_mode * opaque * (1 - paint));
+
+                  if (lock_alpha > 0.0f && color_a != 0)
+                    draw_dab_pixels_BlendMode_LockAlpha (&alpha, pixel,
+                                                         color_r, color_g, color_b,
+                                                         lock_alpha * opaque * (1 - colorize) *
+                                                         (1 - posterize) * (1 - paint));
+
+                  r = pixel[RED];
+                  g = pixel[GREEN];
+                  b = pixel[BLUE];
+                }
+
+              /* v2 Brush code */
+              if (paint > 0.0f)
+                {
+                  if (color_a == 1.0f)
+                    draw_dab_pixels_BlendMode_Normal_Paint (&alpha, pixel,
+                                                            color_r, color_g, color_b,
+                                                            normal_mode * opaque * paint);
+                  else
+                    draw_dab_pixels_BlendMode_Normal_and_Eraser_Paint (&alpha, pixel,
+                                                                       color_r, color_g,
+                                                                       color_b, color_a,
+                                                                       normal_mode * opaque * paint);
+
+                  if (lock_alpha > 0.0f && color_a != 0)
+                    draw_dab_pixels_BlendMode_LockAlpha_Paint (&alpha, pixel,
+                                                               color_r, color_g, color_b,
+                                                               lock_alpha * opaque * (1 - colorize) *
+                                                               (1 - posterize) * paint);
+
+                  r = pixel[RED];
+                  g = pixel[GREEN];
+                  b = pixel[BLUE];
                 }
 
               if (colorize > 0.0f && base_alpha > 0.0f)
@@ -469,6 +941,11 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
                     }
                 }
 
+              if (posterize > 0.0f)
+                draw_dab_pixels_BlendMode_Posterize (&alpha, pixel,
+                                                     opaque, posterize_num);
+
+
               if (surface->options->no_erasing)
                 a = MAX (a, pixel[ALPHA]);
 
@@ -501,33 +978,195 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
   return 1;
 }
 
+static int
+gimp_mypaint_surface_draw_dab_wrapper (MyPaintSurface *surface,
+                                       float           x,
+                                       float           y,
+                                       float           radius,
+                                       float           color_r,
+                                       float           color_g,
+                                       float           color_b,
+                                       float           opaque,
+                                       float           hardness,
+                                       float           color_a,
+                                       float           aspect_ratio,
+                                       float           angle,
+                                       float           lock_alpha,
+                                       float           colorize)
+{
+  const gfloat posterize = 0.0;
+  const gfloat posterize_num = 1.0;
+  const gfloat pigment = 0.0;
+
+  return gimp_mypaint_surface_draw_dab_2 ((MyPaintSurface2 *) surface, x, y, radius,
+                                          color_r, color_g, color_b, opaque, hardness,
+                                          color_a, aspect_ratio, angle, lock_alpha,
+                                          colorize, posterize, posterize_num,
+                                          pigment);
+}
+
 static void
-gimp_mypaint_surface_begin_atomic (MyPaintSurface *base_surface)
+gimp_mypaint_surface_get_color_2 (MyPaintSurface2 *base_surface,
+                                  float            x,
+                                  float            y,
+                                  float            radius,
+                                  float           *color_r,
+                                  float           *color_g,
+                                  float           *color_b,
+                                  float           *color_a,
+                                  float            paint)
 {
+  GimpMybrushSurface *surface = (GimpMybrushSurface *) base_surface;
+  GeglRectangle       dabRect;
 
+  if (radius < 1.0f)
+    radius = 1.0f;
+
+  dabRect = calculate_dab_roi (x, y, radius);
+
+  *color_r = 0.0f;
+  *color_g = 0.0f;
+  *color_b = 0.0f;
+  *color_a = 0.0f;
+
+  if (dabRect.width > 0 || dabRect.height > 0)
+  {
+    const float one_over_radius2 = 1.0f / (radius * radius);
+    const int sample_interval = radius <= 2.0f ? 1 : (int)(radius * 7);
+    const float random_sample_rate = 1.0f / (7 * radius);
+    float sum_weight = 0.0f;
+    float sum_r = 0.0f;
+    float sum_g = 0.0f;
+    float sum_b = 0.0f;
+    float sum_a = 0.0f;
+
+    /* Read in clamp mode to avoid transparency bleeding in at the edges */
+    GeglBufferIterator *iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0,
+                                                         babl_format ("R'aG'aB'aA float"),
+                                                         GEGL_BUFFER_READ,
+                                                         GEGL_ABYSS_CLAMP, 2);
+    if (surface->paint_mask)
+      {
+        GeglRectangle mask_roi = dabRect;
+        mask_roi.x -= surface->paint_mask_x;
+        mask_roi.y -= surface->paint_mask_y;
+        gegl_buffer_iterator_add (iter, surface->paint_mask, &mask_roi, 0,
+                                  babl_format ("Y float"),
+                                  GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+      }
+
+    while (gegl_buffer_iterator_next (iter))
+      {
+        float *pixel = (float *)iter->items[0].data;
+        float *mask;
+        int iy, ix;
+        uint16_t interval_counter = 0;
+
+        if (surface->paint_mask)
+          mask = iter->items[1].data;
+        else
+          mask = NULL;
+
+        #ifdef _OPENMP
+        #pragma omp parallel for schedule(static)
+        #endif
+        for (iy = iter->items[0].roi.y; iy < iter->items[0].roi.y + iter->items[0].roi.height; iy++)
+          {
+            float yy = (iy + 0.5f - y);
+            for (ix = iter->items[0].roi.x; ix < iter->items[0].roi.x +  iter->items[0].roi.width; ix++)
+              {
+                /* pixel_weight == a standard dab with hardness = 0.5, aspect_ratio = 1.0, and angle = 0.0 */
+                float xx = (ix + 0.5f - x);
+                float rr = (yy * yy + xx * xx) * one_over_radius2;
+                float pixel_weight = 0.0f;
+                if (rr <= 1.0f)
+                  pixel_weight = 1.0f - rr;
+                if (mask)
+                  pixel_weight *= *mask;
+
+                #ifdef _OPENMP
+                #pragma omp critical
+                #endif
+                get_color_pixels_accumulate (pixel_weight, pixel, &sum_weight,
+                                             &sum_r, &sum_g, &sum_b, &sum_a, paint,
+                                             sample_interval, random_sample_rate,
+                                             interval_counter);
+
+                interval_counter = (interval_counter + 1) % sample_interval;
+
+                pixel += 4;
+                if (mask)
+                  mask += 1;
+              }
+          }
+      }
+
+    if (sum_a > 0.0f && sum_weight > 0.0f)
+      {
+        float demul;
+        sum_a /= sum_weight;
+
+        if (paint < 0.0f)
+          {
+            sum_r /= sum_weight;
+            sum_g /= sum_weight;
+            sum_b /= sum_weight;
+          }
+
+        demul = paint < 0.0 ? sum_a : 1.0;
+
+        /* FIXME: Clamping is wrong because GEGL allows alpha > 1, this should probably re-multipy things */
+        *color_r = CLAMP(sum_r / demul, 0.0f, 1.0f);
+        *color_g = CLAMP(sum_g / demul, 0.0f, 1.0f);
+        *color_b = CLAMP(sum_b / demul, 0.0f, 1.0f);
+        *color_a = CLAMP(sum_a, 0.0f, 1.0f);
+      }
+  }
 }
 
 static void
-gimp_mypaint_surface_end_atomic (MyPaintSurface   *base_surface,
-                                 MyPaintRectangle *roi)
+gimp_mypaint_surface_get_color_wrapper (MyPaintSurface *surface,
+                                        float           x,
+                                        float           y,
+                                        float           radius,
+                                        float          *color_r,
+                                        float          *color_g,
+                                        float          *color_b,
+                                        float          *color_a)
 {
-  GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
-
-  roi->x         = surface->dirty.x;
-  roi->y         = surface->dirty.y;
-  roi->width     = surface->dirty.width;
-  roi->height    = surface->dirty.height;
-  surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0);
+  return gimp_mypaint_surface_get_color_2 ((MyPaintSurface2 *) surface, x, y, radius,
+                                           color_r, color_g, color_b, color_a,
+                                           -1.0);
 }
 
+
 static void
-gimp_mypaint_surface_destroy (MyPaintSurface *base_surface)
+gimp_mypaint_surface_end_atomic_2 (MyPaintSurface2    *base_surface,
+                                   MyPaintRectangles  *roi)
 {
-  GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
+  GimpMybrushSurface *surface = (GimpMybrushSurface *) base_surface;
 
-  g_clear_object (&surface->buffer);
-  g_clear_object (&surface->paint_mask);
-  g_free (surface);
+  if (roi)
+    {
+      const gint roi_rects = roi->num_rectangles;
+
+      for (gint i = 0; i < roi_rects; i++)
+        {
+          roi->rectangles[i].x         = surface->dirty.x;
+          roi->rectangles[i].y         = surface->dirty.y;
+          roi->rectangles[i].width     = surface->dirty.width;
+          roi->rectangles[i].height    = surface->dirty.height;
+          surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0);
+        }
+    }
+}
+
+static void
+gimp_mypaint_surface_end_atomic_wrapper (MyPaintSurface   *surface,
+                                         MyPaintRectangle *roi)
+{
+  MyPaintRectangles rois = {1, roi};
+  gimp_mypaint_surface_end_atomic_2 ((MyPaintSurface2 *) surface, &rois);
 }
 
 GimpMybrushSurface *
@@ -539,23 +1178,31 @@ gimp_mypaint_surface_new (GeglBuffer         *buffer,
                           GimpMybrushOptions *options)
 {
   GimpMybrushSurface *surface = g_malloc0 (sizeof (GimpMybrushSurface));
+  MyPaintSurface2    *s;
+
+  mypaint_surface_init (&surface->surface.parent);
+  s = &surface->surface;
+
+  s->get_color_pigment    = gimp_mypaint_surface_get_color_2;
+  s->draw_dab_pigment     = gimp_mypaint_surface_draw_dab_2;
+  s->parent.begin_atomic  = gimp_mypaint_surface_begin_atomic;
+  s->end_atomic_multi     = gimp_mypaint_surface_end_atomic_2;
+
+  s->parent.draw_dab      = gimp_mypaint_surface_draw_dab_wrapper;
+  s->parent.get_color     = gimp_mypaint_surface_get_color_wrapper;
+  s->parent.end_atomic    = gimp_mypaint_surface_end_atomic_wrapper;
 
-  mypaint_surface_init ((MyPaintSurface *)surface);
+  s->parent.destroy       = gimp_mypaint_surface_destroy;
 
-  surface->surface.get_color    = gimp_mypaint_surface_get_color;
-  surface->surface.draw_dab     = gimp_mypaint_surface_draw_dab;
-  surface->surface.begin_atomic = gimp_mypaint_surface_begin_atomic;
-  surface->surface.end_atomic   = gimp_mypaint_surface_end_atomic;
-  surface->surface.destroy      = gimp_mypaint_surface_destroy;
-  surface->component_mask       = component_mask;
-  surface->options              = options;
-  surface->buffer               = g_object_ref (buffer);
+  surface->component_mask = component_mask;
+  surface->options        = options;
+  surface->buffer         = g_object_ref (buffer);
   if (paint_mask)
-    surface->paint_mask         = g_object_ref (paint_mask);
+    surface->paint_mask   = g_object_ref (paint_mask);
 
-  surface->paint_mask_x         = paint_mask_x;
-  surface->paint_mask_y         = paint_mask_y;
-  surface->dirty                = *GEGL_RECTANGLE (0, 0, 0, 0);
+  surface->paint_mask_x   = paint_mask_x;
+  surface->paint_mask_y   = paint_mask_y;
+  surface->dirty          = *GEGL_RECTANGLE (0, 0, 0, 0);
 
   return surface;
 }
diff --git a/app/tools/gimpmybrushoptions-gui.c b/app/tools/gimpmybrushoptions-gui.c
index a2f927f920..0bfc418e3c 100644
--- a/app/tools/gimpmybrushoptions-gui.c
+++ b/app/tools/gimpmybrushoptions-gui.c
@@ -78,5 +78,10 @@ gimp_mybrush_options_gui (GimpToolOptions *tool_options)
                                     0.1, 1.0, 2);
   gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
 
+  /* pigment */
+  scale = gimp_prop_spin_scale_new (config, "pigment",
+                                    0.1, 0.0, 2);
+  gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+
   return vbox;
 }
diff --git a/app/tools/gimpmybrushtool.c b/app/tools/gimpmybrushtool.c
index 2a7378802f..d8964b67d7 100644
--- a/app/tools/gimpmybrushtool.c
+++ b/app/tools/gimpmybrushtool.c
@@ -56,6 +56,10 @@ static GimpCanvasItem * gimp_mybrush_tool_get_outline (GimpPaintTool *paint_tool
                                                        gdouble        x,
                                                        gdouble        y);
 
+static void   gimp_mybrush_tool_cursor_update         (GimpTool         *tool,
+                                                       const GimpCoords *coords,
+                                                       GdkModifierType   state,
+                                                       GimpDisplay      *display);
 
 void
 gimp_mybrush_tool_register (GimpToolRegisterCallback  callback,
@@ -84,6 +88,7 @@ gimp_mybrush_tool_class_init (GimpMybrushToolClass *klass)
   GimpToolClass      *tool_class       = GIMP_TOOL_CLASS (klass);
   GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
 
+  tool_class->cursor_update     = gimp_mybrush_tool_cursor_update;
   tool_class->options_notify    = gimp_mybrush_tool_options_notify;
 
   paint_tool_class->get_outline = gimp_mybrush_tool_get_outline;
@@ -176,3 +181,19 @@ gimp_mybrush_tool_create_cursor (GimpPaintTool *paint_tool,
 
   return NULL;
 }
+
+static void gimp_mybrush_tool_cursor_update (GimpTool         *tool,
+                                             const GimpCoords *coords,
+                                             GdkModifierType   state,
+                                             GimpDisplay      *display)
+{
+  GimpDisplayShell   *shell;
+  GimpMybrushOptions *options = GIMP_MYBRUSH_TOOL_GET_OPTIONS (tool);
+
+  g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+  shell = gimp_display_get_shell (display);
+
+  options->view_zoom = gimp_zoom_model_get_factor (shell->zoom);
+  options->view_rotation = shell->rotate_angle;
+}


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