[gimp] app: refactor gimppaintcore-loops to coalesce iteration



commit f2a1fd5bf0e170e79bc3c2f1f162d2f73ecb9747
Author: Ell <ell_se yahoo com>
Date:   Sat Apr 14 19:02:21 2018 -0400

    app: refactor gimppaintcore-loops to coalesce iteration
    
    The gimppaintcore-loops functions perform very little actual
    computational work (in case of do_layer_blend(), at least for
    simple blend modes), which makes the cost of buffer iteration, and
    memory bandwidth, nonnegligible factors.  Since these functions are
    usually called in succession, acessing the same region of the same
    buffers, using the same foramts, coalescing them into a single
    function, which performs all the necessary processing in a single
    step, can improve performance when these functions are the
    bottleneck.
    
    Add a gimp_paint_core_loops_process() function, which does just
    that: it takes a set of algorithms to run, and a set of parameters,
    and performs all of them in one go.  The individual functions are
    kept for convenience, but are merely wrappers around
    gimp_paint_core_loops_process().
    
    Be warned: the implementation uses unholy C++ from outer space, in
    order to make this (sort of) managable.  See the comments for more
    details.

 app/paint/gimppaintcore-loops.cc | 1494 ++++++++++++++++++++++++++++++--------
 app/paint/gimppaintcore-loops.h  |  116 ++-
 app/paint/gimppaintcore.c        |   75 +-
 3 files changed, 1315 insertions(+), 370 deletions(-)
---
diff --git a/app/paint/gimppaintcore-loops.cc b/app/paint/gimppaintcore-loops.cc
index c02335d..9b1fdee 100644
--- a/app/paint/gimppaintcore-loops.cc
+++ b/app/paint/gimppaintcore-loops.cc
@@ -34,212 +34,1211 @@ extern "C"
 
 #include "gimppaintcore-loops.h"
 
+} /* extern "C" */
+
 
 #define MIN_PARALLEL_SUB_SIZE 64
 #define MIN_PARALLEL_SUB_AREA (MIN_PARALLEL_SUB_SIZE * MIN_PARALLEL_SUB_SIZE)
 
 
-void
-combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
-                                   gint               mask_x_offset,
-                                   gint               mask_y_offset,
-                                   GeglBuffer        *canvas_buffer,
-                                   gint               x_offset,
-                                   gint               y_offset,
-                                   gfloat             opacity,
-                                   gboolean           stipple)
+/* In order to avoid iterating over the same region of the same buffers
+ * multiple times, when calling more than one of the paint-core loop functions
+ * (hereafter referred to as "algorithms") in succession, we provide a single
+ * function, gimp_paint_core_loops_process(), which can be used to perform
+ * multiple algorithms in a row.  This function takes a pointer to a
+ * GimpPaintCoreLoopsParams structure, providing the parameters for the
+ * algorithms, and a GimpPaintCoreLoopsAlgorithm bitset, which specifies the
+ * set of algorithms to run; currently, the algorithms are always run in a
+ * fixed order.  For convenience, we provide public functions for the
+ * individual algorithms, but they're merely wrappers around
+ * gimp_paint_core_loops_process().
+ *
+ * We use some C++ magic to statically generate specialized versions of
+ * gimp_paint_core_loops_process() for all possible combinations of algorithms,
+ * and, where relevant, formats and input parameters, and to dispatch to the
+ * correct version at runtime.
+ *
+ * To achieve this, each algorithm provides two components:
+ *
+ *   - The algorithm class template, which implements the algorithm, following
+ *     a common interface.  See the AlgorithmBase class for a description of
+ *     the interface.  Each algorithm class takes its base class as a template
+ *     parameter, which allows us to construct a class hierarchy corresponding
+ *     to a specific set of algorithms.  Some classes in the hierarchy are not
+ *     algorithms themselves, but are rather helpers, which provide some
+ *     functionality to the algorithms further down the hierarchy, such as
+ *     access to specific buffers.
+ *
+ *   - A dispatch function, which takes the input parameters, the requested set
+ *     of algorithms, the (type of) the current algorithm hierarchy, and a
+ *     visitor object.  The function calls the visitor with a (potentially)
+ *     modified hierarchy, depending on the input.  Ihe dispatch function for
+ *     an algorithm checks if the requested set of algorithms contains a
+ *     certain algorithm, adds the said algorithm to the hierarchy accordingly,
+ *     and calls the visitor with the new hierarchy.  See the AlgorithmDispatch
+ *     class, which provides a dispatch-function implementation which
+ *     algorithms can use instead of providing their own dispatch function.
+ *
+ *     Helper classes in the hierarchy may also provide dispatch functions,
+ *     which likewise modify the hierarchy based on the input parameters.  For
+ *     example, the dispatch_paint_mask() function adds a certain PaintMask
+ *     specialization to the hierarchy, depending on the format of the paint
+ *     mask buffer; this can be used to specialize algorithms based on the mask
+ *     format; an algorithm that depends on the paint mask may dispatch through
+ *     this function, before modifying the hierarchy itself.
+ *
+ * The dispatch() function is used to construct an algorithm hierarchy by
+ * dispatching through a list of functions.  gimp_paint_core_loops_process()
+ * calls dispatch() with the full list of algorithm dispatch functions,
+ * receiving in return the algorithm hierarchy matching the input.  It then
+ * uses the algorithm interface to perform the actual processing.
+ */
+
+
+enum
 {
-  GeglRectangle roi;
+  ALGORITHM_PAINT_BUF  = 1u << 31,
+  ALGORITHM_PAINT_MASK = 1u << 30,
+  ALGORITHM_STIPPLE    = 1u << 29
+};
 
-  const gint   mask_stride       = gimp_temp_buf_get_width (paint_mask);
-  const gint   mask_start_offset = mask_y_offset * mask_stride + mask_x_offset;
-  const Babl  *mask_format       = gimp_temp_buf_get_format (paint_mask);
-  gint         width;
-  gint         height;
 
-  width  = gimp_temp_buf_get_width (paint_mask);
-  height = gimp_temp_buf_get_height (paint_mask);
+template <class T>
+struct identity
+{
+  using type = T;
+};
 
-  roi.x = x_offset;
-  roi.y = y_offset;
-  roi.width  = width - mask_x_offset;
-  roi.height = height - mask_y_offset;
 
-  gimp_parallel_distribute_area (&roi, MIN_PARALLEL_SUB_AREA,
-                                 [=] (const GeglRectangle *area)
+/* dispatch():
+ *
+ * Takes a list of dispatch function objects, and calls each of them, in order,
+ * with the same 'params' and 'algorithms' parameters, passing 'algorithm' as
+ * the input hierarchy to the first dispatch function, and passing the output
+ * hierarchy of the previous dispatch function as the input hierarchy for the
+ * next dispatch function.  Calls 'visitor' with the output hierarchy of the
+ * last dispatch function.
+ *
+ * Each algorithm hierarchy should provide a 'filter' static data member, and
+ * each dispatch function object should provide a 'mask' static data member.
+ * If the bitwise-AND of the current hierarchy's 'filter' member and the
+ * current dispatch function's 'mask' member is equal to 'mask', the dispatch
+ * function is skipped.  This can be used to make sure that a class appears
+ * only once in the hierarchy, even if its dispatch function is used multiple
+ * times, or to prevent an algorithm from being dispatched, if it cannot be
+ * used together with another algorithm.
+ */
+
+template <class Visitor,
+          class Algorithm>
+static inline void
+dispatch (Visitor                         visitor,
+          const GimpPaintCoreLoopsParams *params,
+          GimpPaintCoreLoopsAlgorithm     algorithms,
+          identity<Algorithm>             algorithm)
+{
+  visitor (algorithm);
+}
+
+template <class Algorithm,
+          class Dispatch,
+          gboolean = (Algorithm::filter & Dispatch::mask) == Dispatch::mask>
+struct dispatch_impl
+{
+  template <class    Visitor,
+            class... DispatchRest>
+  static void
+  apply (Visitor                         visitor,
+         const GimpPaintCoreLoopsParams *params,
+         GimpPaintCoreLoopsAlgorithm     algorithms,
+         identity<Algorithm>             algorithm,
+         Dispatch                        disp,
+         DispatchRest...                 disp_rest)
+  {
+    disp (
+      [&] (auto algorithm)
+      {
+        dispatch (visitor, params, algorithms, algorithm, disp_rest...);
+      },
+      params, algorithms, algorithm);
+  }
+};
+
+template <class Algorithm,
+          class Dispatch>
+struct dispatch_impl<Algorithm, Dispatch, TRUE>
+{
+  template <class    Visitor,
+            class... DispatchRest>
+  static void
+  apply (Visitor                         visitor,
+         const GimpPaintCoreLoopsParams *params,
+         GimpPaintCoreLoopsAlgorithm     algorithms,
+         identity<Algorithm>             algorithm,
+         Dispatch                        disp,
+         DispatchRest...                 disp_rest)
+  {
+    dispatch (visitor, params, algorithms, algorithm, disp_rest...);
+  }
+};
+
+template <class    Visitor,
+          class    Algorithm,
+          class    Dispatch,
+          class... DispatchRest>
+static inline void
+dispatch (Visitor                         visitor,
+          const GimpPaintCoreLoopsParams *params,
+          GimpPaintCoreLoopsAlgorithm     algorithms,
+          identity<Algorithm>             algorithm,
+          Dispatch                        disp,
+          DispatchRest...                 disp_rest)
+{
+  dispatch_impl<Algorithm, Dispatch>::apply (
+    visitor, params, algorithms, algorithm, disp, disp_rest...);
+}
+
+
+/* value_to_float():
+ *
+ * Converts a component value to float.
+ */
+
+static inline gfloat
+value_to_float (guint8 value)
+{
+  return value / 255.0f;
+}
+
+static inline gfloat
+value_to_float (gfloat value)
+{
+  return value;
+}
+
+template <class T>
+static inline gfloat
+value_to_float (T value) = delete;
+
+
+/* AlgorithmBase:
+ *
+ * The base class of the algorithm hierarchy.
+ */
+
+struct AlgorithmBase
+{
+  /* Used to filter-out dispatch functions; see the description of dispatch().
+   * Algorithms that redefine 'filter' should bitwise-OR their filter with that
+   * of their base class.
+   */
+  static constexpr guint          filter                 = 0;
+
+  /* See CanvasBufferIterator. */
+  static constexpr gint           canvas_buffer_iterator = -1;
+  static constexpr GeglAccessMode canvas_buffer_access   = {};
+
+  /* The current number of iterators used by the hierarchy.  Algorithms should
+   * use the 'n_iterators' value of their base class as the base-index for
+   * their iterators, and redefine 'n_iterators' by adding the number of
+   * iterators they use to this value.
+   */
+  static constexpr gint           n_iterators            = 0;
+
+  /* Non-static data members should be initialized in the constructor, and
+   * should not be further modified.
+   */
+  explicit
+  AlgorithmBase (const GimpPaintCoreLoopsParams *params)
+  {
+  }
+
+  /* Algorithms should store their dynamic state in the 'State' member class
+   * template.  This template will be instantiated with the most-derived type
+   * of the hierarchy, which allows an algorithm to depend on the properties of
+   * its descendants.  Algorithms that provide their own 'State' class should
+   * derive it from the 'State' class of their base class, passing 'Derived' as
+   * the template argument.
+   *
+   * Algorithms can be run in parallel on multiple threads.  In this case, each
+   * thread uses its own 'State' object, while the algorithm object itself is
+   * either shared, or is a copy of a shared algorithm object.  Either way, the
+   * algorithm object itself is immutable, while the state object is mutable.
+   */
+  template <class Derived>
+  struct State
+  {
+  };
+
+  /* The 'init()' function is called once per state object before processing
+   * starts, and should initialize the state object, and, if necessary, the
+   * iterator.
+   *
+   * 'params' is the same parameter struct passed to the constructor.  'state'
+   * is the state object.  'iter' is the iterator; each distinct state object
+   * uses a distinct iterator.  'roi' is the full region to be processed.
+   * 'area' is the subregion to be processed by the current state object.
+   *
+   * An algorithm that overrides this function should call the 'init()'
+   * function of its base class first, using the same arguments.
+   */
+  template <class Derived>
+  void
+  init (const GimpPaintCoreLoopsParams *params,
+        State<Derived>                 *state,
+        GeglBufferIterator             *iter,
+        const GeglRectangle            *roi,
+        const GeglRectangle            *area) const
+  {
+  }
+
+  /* The 'init_step()' function is called once after each
+   * 'gegl_buffer_iterator_next()' call, and should perform any necessary
+   * initialization required before processing the current chunk.
+   *
+   * The parameters are the same as for 'init()'.
+   *
+   * An algorithm that overrides this function should call the 'init_step()'
+   * function of its base class first, using the same arguments.
+   */
+  template <class Derived>
+  void
+  init_step (const GimpPaintCoreLoopsParams *params,
+             State<Derived>                 *state,
+             GeglBufferIterator             *iter,
+             const GeglRectangle            *roi,
+             const GeglRectangle            *area) const
+  {
+  }
+
+  /* The 'process_row()' function is called for each row in the current chunk,
+   * and should perform the actual processing.
+   *
+   * The parameters are the same as for 'init()', with the addition of 'y',
+   * which is the current row.
+   *
+   * An algorithm that overrides this function should call the 'process_row()'
+   * function of its base class first, using the same arguments.
+   */
+  template <class Derived>
+  void
+  process_row (const GimpPaintCoreLoopsParams *params,
+               State<Derived>                 *state,
+               GeglBufferIterator             *iter,
+               const GeglRectangle            *roi,
+               const GeglRectangle            *area,
+               gint                            y) const
+  {
+  }
+};
+
+
+/* BasicDispatch:
+ *
+ * A class template implementing a simple dispatch function object, which adds
+ * an algorithm to the heirarchy unconditionally.  'AlgorithmTemplate' is the
+ * alogithm class template (usually a helper class, rather than an actual
+ * algorithm), and 'Mask' is the dispatch function mask, as described in
+ * 'dispatch()'.
+ */
+
+template <template <class Base> class AlgorithmTemplate,
+          guint                       Mask>
+struct BasicDispatch
+{
+  static constexpr guint mask = Mask;
+
+  template <class Visitor,
+            class Algorithm>
+  void
+  operator () (Visitor                         visitor,
+               const GimpPaintCoreLoopsParams *params,
+               GimpPaintCoreLoopsAlgorithm     algorithms,
+               identity<Algorithm>             algorithm) const
+  {
+    visitor (identity<AlgorithmTemplate<Algorithm>> ());
+  }
+};
+
+
+/* AlgorithmDispatch:
+ *
+ * A class template implementing a dispatch function suitable for dispatching
+ * algorithms.  'AlgorithmTemplate' is the algorithm class template, 'Mask' is
+ * the dispatch function mask, as described in 'dispatch()', and 'Dependencies'
+ * is a list of (types of) dispatch functions the algorithm depends on, usd as
+ * explained below.
+ *
+ * 'AlgorithmDispatch' adds the algorithm to the heirarchy if it's included in
+ * the set of requested algorithms; specifically, if the bitwise-AND of the
+ * requested-algorithms bitset and of 'Mask' is equal to 'Mask'.
+ *
+ * Before adding the algorithm to the hierarchy, the hierarchy is augmented by
+ * dispatching through the list of dependencies, in order.
+ */
+
+template <template <class Base> class AlgorithmTemplate,
+          guint                       Mask,
+          class...                    Dependencies>
+struct AlgorithmDispatch
+{
+  static constexpr guint mask = Mask;
+
+  template <class Visitor,
+            class Algorithm>
+  void
+  operator () (Visitor                         visitor,
+               const GimpPaintCoreLoopsParams *params,
+               GimpPaintCoreLoopsAlgorithm     algorithms,
+               identity<Algorithm>             algorithm) const
+  {
+    if ((algorithms & mask) == mask)
+      {
+        dispatch (
+          [&] (auto algorithm)
+          {
+            using NewAlgorithm = typename decltype (algorithm)::type;
+
+            visitor (identity<AlgorithmTemplate<NewAlgorithm>> ());
+          },
+          params, algorithms, algorithm, Dependencies ()...);
+      }
+    else
+      {
+        visitor (algorithm);
+      }
+  }
+};
+
+
+/* PaintBuf, dispatch_paint_buf():
+ *
+ * An algorithm helper class, providing access to the paint buffer.  Algorithms
+ * that use the paint buffer should specify 'dispatch_paint_buf()' as a
+ * dependency, and access 'PaintBuf' members through their base type/subobject.
+ */
+
+template <class Base>
+struct PaintBuf : Base
+{
+  /* Component type of the paint buffer. */
+  using paint_type = gfloat;
+
+  static constexpr guint filter = Base::filter | ALGORITHM_PAINT_BUF;
+
+  /* Paint buffer stride, in 'paint_type' elements. */
+  gint        paint_stride;
+  /* Pointer to the start of the paint buffer data. */
+  paint_type *paint_data;
+
+  explicit
+  PaintBuf (const GimpPaintCoreLoopsParams *params) :
+    Base (params)
+  {
+    paint_stride = gimp_temp_buf_get_width (params->paint_buf) * 4;
+    paint_data   = (paint_type *) gimp_temp_buf_get_data (params->paint_buf);
+  }
+};
+
+static BasicDispatch<PaintBuf, ALGORITHM_PAINT_BUF> dispatch_paint_buf;
+
+
+/* PaintMask, dispatch_paint_mask():
+ *
+ * An algorithm helper class, providing access to the paint mask.  Algorithms
+ * that use the paint mask should specify 'dispatch_paint_mask()' as a
+ * dependency, and access 'PaintMask' members through their base type/
+ * subobject.
+ */
+
+template <class Base,
+          class MaskType>
+struct PaintMask : Base
+{
+  /* Component type of the paint mask. */
+  using mask_type = MaskType;
+
+  static constexpr guint filter = Base::filter | ALGORITHM_PAINT_MASK;
+
+  /* Paint mask stride, in 'mask_type' elements. */
+  gint             mask_stride;
+  /* Pointer to the start of the paint mask data, taking the mask offset into
+   * account.
+   */
+  const mask_type *mask_data;
+
+  explicit
+  PaintMask (const GimpPaintCoreLoopsParams *params) :
+    Base (params)
+  {
+    mask_stride = gimp_temp_buf_get_width (params->paint_mask);
+    mask_data   =
+      (const mask_type *) gimp_temp_buf_get_data (params->paint_mask) +
+      params->paint_mask_offset_y * mask_stride                       +
+      params->paint_mask_offset_x;
+  }
+};
+
+struct DispatchPaintMask
+{
+  static constexpr guint mask = ALGORITHM_PAINT_MASK;
+
+  template <class Visitor,
+            class Algorithm>
+  void
+  operator () (Visitor                         visitor,
+               const GimpPaintCoreLoopsParams *params,
+               GimpPaintCoreLoopsAlgorithm     algorithms,
+               identity<Algorithm>             algorithm) const
+  {
+    const Babl *mask_format = gimp_temp_buf_get_format (params->paint_mask);
+
+    if (mask_format == babl_format ("Y u8"))
+      visitor (identity<PaintMask<Algorithm, guint8>> ());
+    else if (mask_format == babl_format ("Y float"))
+      visitor (identity<PaintMask<Algorithm, gfloat>> ());
+    else
+      g_warning ("Mask format not supported: %s", babl_get_name (mask_format));
+  }
+} static dispatch_paint_mask;
+
+
+/* Stipple, dispatch_stipple():
+ *
+ * An algorithm helper class, providing access to the 'stipple' parameter.
+ * Algorithms that use the 'stipple' parameter should specify
+ * 'dispatch_stipple()' as a dependency, and access 'Stipple' members through
+ * their base type/subobject.
+ */
+
+template <class    Base,
+          gboolean StippleFlag>
+struct Stipple : Base
+{
+  static constexpr guint filter = Base::filter | ALGORITHM_STIPPLE;
+
+  /* The value of the 'stipple' parameter, usable as a constant expression. */
+  static constexpr gboolean stipple = StippleFlag;
+
+  using Base::Base;
+};
+
+struct DispatchStipple
+{
+  static constexpr guint mask = ALGORITHM_STIPPLE;
+
+  template <class Visitor,
+            class Algorithm>
+  void
+  operator () (Visitor                         visitor,
+               const GimpPaintCoreLoopsParams *params,
+               GimpPaintCoreLoopsAlgorithm     algorithms,
+               identity<Algorithm>             algorithm) const
+  {
+    if (params->stipple)
+      visitor (identity<Stipple<Algorithm, TRUE>> ());
+    else
+      visitor (identity<Stipple<Algorithm, FALSE>> ());
+  }
+} static dispatch_stipple;
+
+
+/* CanvasBufferIterator:
+ *
+ * An algorithm helper class, providing iterator-access to the canvas buffer.
+ * Algorithms that iterate over the canvas buffer should derive from this
+ * class, and access its members through their base type/subobject.
+ *
+ * 'Base' is the base class to use, which should normally be the base template-
+ * parameter class passed to the algorithm.  'Access' specifies the desired
+ * iterator access to the canvas buffer.
+ */
+
+template <class Base,
+          guint Access>
+struct CanvasBufferIterator : Base
+{
+  /* The iterator index of the canvas buffer. */
+  static constexpr gint           canvas_buffer_iterator =
+    Base::canvas_buffer_iterator < 0 ? Base::n_iterators :
+                                       Base::canvas_buffer_iterator;
+  /* Used internally. */
+  static constexpr GeglAccessMode canvas_buffer_access   =
+    (GeglAccessMode) (Base::canvas_buffer_access | Access);
+  /* The total number of iterators used by the hierarchy, up to, and including,
+   * the current class.
+   */
+  static constexpr gint           n_iterators            =
+    Base::canvas_buffer_iterator < 0 ? Base::n_iterators + 1:
+                                       Base::n_iterators;
+
+  using Base::Base;
+
+  template <class Derived>
+  using State = typename Base::template State<Derived>;
+
+  template <class Derived>
+  void
+  init (const GimpPaintCoreLoopsParams *params,
+        State<Derived>                 *state,
+        GeglBufferIterator             *iter,
+        const GeglRectangle            *roi,
+        const GeglRectangle            *area) const
+  {
+    Base::init (params, state, iter, roi, area);
+
+    if (Base::canvas_buffer_iterator < 0)
+      {
+        gegl_buffer_iterator_add (iter, params->canvas_buffer, area, 0,
+                                  babl_format ("Y float"),
+                                  Derived::canvas_buffer_access,
+                                  GEGL_ABYSS_NONE);
+      }
+  }
+};
+
+
+/* CombinePaintMaskToCanvasMaskToPaintBufAlpha,
+ * dispatch_combine_paint_mask_to_canvas_mask_to_paint_buf_alpha():
+ *
+ * An algorithm class, providing an optimized version combining both the
+ * COMBINE_PAINT_MASK_TO_CANVAS_MASK and the CANVAS_BUFFER_TO_PAINT_BUF_ALPHA
+ * algorithms.  Used instead of the individual implementations, when both
+ * algorithms are requested.
+ */
+
+template <class Base>
+struct CombinePaintMaskToCanvasMaskToPaintBufAlpha :
+  CanvasBufferIterator<Base, GEGL_BUFFER_READWRITE>
+{
+  using base_type = CanvasBufferIterator<Base, GEGL_BUFFER_READWRITE>;
+  using mask_type = typename base_type::mask_type;
+
+  static constexpr guint filter =
+    base_type::filter                                                 |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA  |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER;
+
+  using base_type::base_type;
+
+  template <class Derived>
+  struct State : base_type::template State<Derived>
+  {
+    gfloat *canvas_pixel;
+  };
+
+  template <class Derived>
+  void
+  init_step (const GimpPaintCoreLoopsParams *params,
+             State<Derived>                 *state,
+             GeglBufferIterator             *iter,
+             const GeglRectangle            *roi,
+             const GeglRectangle            *area) const
+  {
+    base_type::init_step (params, state, iter, roi, area);
+
+    state->canvas_pixel =
+      (gfloat *) iter->data[base_type::canvas_buffer_iterator];
+  }
+
+  template <class Derived>
+  void
+  process_row (const GimpPaintCoreLoopsParams *params,
+               State<Derived>                 *state,
+               GeglBufferIterator             *iter,
+               const GeglRectangle            *roi,
+               const GeglRectangle            *area,
+               gint                            y) const
+  {
+    base_type::process_row (params, state, iter, roi, area, y);
+
+    gint             mask_offset  = (y              - roi->y) * this->mask_stride +
+                                    (iter->roi[0].x - roi->x);
+    const mask_type *mask_pixel   = &this->mask_data[mask_offset];
+    gint             paint_offset = (y              - roi->y) * this->paint_stride +
+                                    (iter->roi[0].x - roi->x) * 4;
+    gfloat          *paint_pixel  = &this->paint_data[paint_offset];
+    gint             x;
+
+    for (x = 0; x < iter->roi[0].width; x++)
+      {
+        if (base_type::stipple)
+          {
+            state->canvas_pixel[0] += (1.0 - state->canvas_pixel[0])  *
+                                      value_to_float (*mask_pixel)    *
+                                      params->paint_opacity;
+          }
+        else
+          {
+            if (params->paint_opacity > state->canvas_pixel[0])
+              {
+                state->canvas_pixel[0] += (params->paint_opacity - state->canvas_pixel[0]) *
+                                           value_to_float (*mask_pixel)                    *
+                                           params->paint_opacity;
+              }
+          }
+
+        paint_pixel[3] *= state->canvas_pixel[0];
+
+        mask_pixel          += 1;
+        state->canvas_pixel += 1;
+        paint_pixel         += 4;
+      }
+  }
+};
+
+static AlgorithmDispatch<
+  CombinePaintMaskToCanvasMaskToPaintBufAlpha,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK |
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA,
+  decltype (dispatch_paint_buf),
+  decltype (dispatch_paint_mask),
+  decltype (dispatch_stipple)
+>
+dispatch_combine_paint_mask_to_canvas_mask_to_paint_buf_alpha;
+
+
+/* CombinePaintMaskToCanvasMask, dispatch_combine_paint_mask_to_canvas_mask():
+ *
+ * An algorithm class, implementing the COMBINE_PAINT_MASK_TO_CANVAS_MASK
+ * algorithm.
+ */
+
+template <class Base>
+struct CombinePaintMaskToCanvasMask :
+  CanvasBufferIterator<Base, GEGL_BUFFER_READWRITE>
+{
+  using base_type = CanvasBufferIterator<Base, GEGL_BUFFER_READWRITE>;
+  using mask_type = typename base_type::mask_type;
+
+  static constexpr guint filter =
+    base_type::filter                                                 |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER;
+
+  using base_type::base_type;
+
+  template <class Derived>
+  struct State : base_type::template State<Derived>
+  {
+    gfloat *canvas_pixel;
+  };
+
+  template <class Derived>
+  void
+  init_step (const GimpPaintCoreLoopsParams *params,
+             State<Derived>                 *state,
+             GeglBufferIterator             *iter,
+             const GeglRectangle            *roi,
+             const GeglRectangle            *area) const
+  {
+    base_type::init_step (params, state, iter, roi, area);
+
+    state->canvas_pixel =
+      (gfloat *) iter->data[base_type::canvas_buffer_iterator];
+  }
+
+  template <class Derived>
+  void
+  process_row (const GimpPaintCoreLoopsParams *params,
+               State<Derived>                 *state,
+               GeglBufferIterator             *iter,
+               const GeglRectangle            *roi,
+               const GeglRectangle            *area,
+               gint                            y) const
+  {
+    base_type::process_row (params, state, iter, roi, area, y);
+
+    gint             mask_offset = (y              - roi->y) * this->mask_stride +
+                                   (iter->roi[0].x - roi->x);
+    const mask_type *mask_pixel  = &this->mask_data[mask_offset];
+    gint             x;
+
+    for (x = 0; x < iter->roi[0].width; x++)
+      {
+        if (base_type::stipple)
+          {
+            state->canvas_pixel[0] += (1.0 - state->canvas_pixel[0]) *
+                                      value_to_float (*mask_pixel)   *
+                                      params->paint_opacity;
+          }
+        else
+          {
+            if (params->paint_opacity > state->canvas_pixel[0])
+              {
+                state->canvas_pixel[0] += (params->paint_opacity - state->canvas_pixel[0]) *
+                                          value_to_float (*mask_pixel)                     *
+                                          params->paint_opacity;
+              }
+          }
+
+        mask_pixel          += 1;
+        state->canvas_pixel += 1;
+      }
+  }
+};
+
+static AlgorithmDispatch<
+  CombinePaintMaskToCanvasMask,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK,
+  decltype (dispatch_paint_mask),
+  decltype (dispatch_stipple)
+>
+dispatch_combine_paint_mask_to_canvas_mask;
+
+
+/* CanvasBufferToPaintBufAlpha, dispatch_canvas_buffer_to_paint_buf_alpha():
+ *
+ * An algorithm class, implementing the CANVAS_BUFFER_TO_PAINT_BUF_ALPHA
+ * algorithm.
+ */
+
+template <class Base>
+struct CanvasBufferToPaintBufAlpha : CanvasBufferIterator<Base,
+                                                          GEGL_BUFFER_READ>
+{
+  using base_type = CanvasBufferIterator<Base, GEGL_BUFFER_READ>;
+
+  static constexpr guint filter =
+    base_type::filter                                                |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER;
+
+  using base_type::base_type;
+
+  template <class Derived>
+  struct State : base_type::template State<Derived>
+  {
+    const gfloat *canvas_pixel;
+  };
+
+  template <class Derived>
+  void
+  init_step (const GimpPaintCoreLoopsParams *params,
+             State<Derived>                 *state,
+             GeglBufferIterator             *iter,
+             const GeglRectangle            *roi,
+             const GeglRectangle            *area) const
+  {
+    base_type::init_step (params, state, iter, roi, area);
+
+    state->canvas_pixel =
+      (const gfloat *) iter->data[base_type::canvas_buffer_iterator];
+  }
+
+  template <class Derived>
+  void
+  process_row (const GimpPaintCoreLoopsParams *params,
+               State<Derived>                 *state,
+               GeglBufferIterator             *iter,
+               const GeglRectangle            *roi,
+               const GeglRectangle            *area,
+               gint                            y) const
+  {
+    base_type::process_row (params, state, iter, roi, area, y);
+
+    /* Copy the canvas buffer in rect to the paint buffer's alpha channel */
+
+    gint    paint_offset = (y              - roi->y) * this->paint_stride +
+                           (iter->roi[0].x - roi->x) * 4;
+    gfloat *paint_pixel  = &this->paint_data[paint_offset];
+    gint    x;
+
+    for (x = 0; x < iter->roi[0].width; x++)
+      {
+        paint_pixel[3] *= *state->canvas_pixel;
+
+        state->canvas_pixel += 1;
+        paint_pixel         += 4;
+      }
+  }
+};
+
+static AlgorithmDispatch<
+  CanvasBufferToPaintBufAlpha,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA,
+  decltype (dispatch_paint_buf)
+>
+dispatch_canvas_buffer_to_paint_buf_alpha;
+
+
+/* PaintMaskToPaintBuffer, dispatch_paint_mask_to_paint_buffer():
+ *
+ * An algorithm class, implementing the PAINT_MASK_TO_PAINT_BUFFER algorithm.
+ */
+
+template <class Base>
+struct PaintMaskToPaintBuffer : Base
+{
+  using mask_type = typename Base::mask_type;
+
+  static constexpr guint filter =
+    Base::filter |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER;
+
+  explicit
+  PaintMaskToPaintBuffer (const GimpPaintCoreLoopsParams *params) :
+    Base (params)
+  {
+    /* Validate that the paint buffer is withing the bounds of the paint mask */
+    g_return_if_fail (gimp_temp_buf_get_width (params->paint_buf) <=
+                      gimp_temp_buf_get_width (params->paint_mask) -
+                      params->paint_mask_offset_x);
+    g_return_if_fail (gimp_temp_buf_get_height (params->paint_buf) <=
+                      gimp_temp_buf_get_height (params->paint_mask) -
+                      params->paint_mask_offset_y);
+  }
+
+  template <class Derived>
+  using State = typename Base::template State<Derived>;
+
+  template <class Derived>
+  void
+  process_row (const GimpPaintCoreLoopsParams *params,
+               State<Derived>                 *state,
+               GeglBufferIterator             *iter,
+               const GeglRectangle            *roi,
+               const GeglRectangle            *area,
+               gint                            y) const
+  {
+    Base::process_row (params, state, iter, roi, area, y);
+
+    gint             paint_offset = (y              - roi->y) * this->paint_stride +
+                                    (iter->roi[0].x - roi->x) * 4;
+    gfloat          *paint_pixel  = &this->paint_data[paint_offset];
+    gint             mask_offset  = (y              - roi->y) * this->mask_stride +
+                                    (iter->roi[0].x - roi->x);
+    const mask_type *mask_pixel   = &this->mask_data[mask_offset];
+    gint             x;
+
+    for (x = 0; x < iter->roi[0].width; x++)
+      {
+        paint_pixel[3] *= value_to_float (*mask_pixel) * params->paint_opacity;
+
+        mask_pixel  += 1;
+        paint_pixel += 4;
+      }
+  }
+};
+
+static AlgorithmDispatch<
+  PaintMaskToPaintBuffer,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER,
+  decltype (dispatch_paint_buf),
+  decltype (dispatch_paint_mask)
+>
+dispatch_paint_mask_to_paint_buffer;
+
+
+/* DoLayerBlend, dispatch_do_layer_blend():
+ *
+ * An algorithm class, implementing the DO_LAYER_BLEND algorithm.
+ */
+
+template <class Base>
+struct DoLayerBlend : Base
+{
+  static constexpr guint filter =
+    Base::filter |
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND;
+
+  static constexpr gint iterator_base = Base::n_iterators;
+  static constexpr gint n_iterators   = Base::n_iterators + 3;
+
+  const Babl             *iterator_format;
+  GimpOperationLayerMode  layer_mode;
+
+  explicit
+  DoLayerBlend (const GimpPaintCoreLoopsParams *params) :
+    Base (params)
+  {
+    layer_mode.layer_mode          = params->paint_mode;
+    layer_mode.opacity             = params->image_opacity;
+    layer_mode.function            = gimp_layer_mode_get_function (params->paint_mode);
+    layer_mode.blend_function      = gimp_layer_mode_get_blend_function (params->paint_mode);
+    layer_mode.blend_space         = gimp_layer_mode_get_blend_space (params->paint_mode);
+    layer_mode.composite_space     = gimp_layer_mode_get_composite_space (params->paint_mode);
+    layer_mode.composite_mode      = gimp_layer_mode_get_paint_composite_mode (params->paint_mode);
+    layer_mode.real_composite_mode = layer_mode.composite_mode;
+
+    iterator_format = gimp_layer_mode_get_format (params->paint_mode,
+                                                  layer_mode.composite_space,
+                                                  layer_mode.blend_space,
+                                                  gimp_temp_buf_get_format (params->paint_buf));
+
+    g_return_if_fail (gimp_temp_buf_get_format (params->paint_buf) == iterator_format);
+  }
+
+  template <class Derived>
+  struct State : Base::template State<Derived>
+  {
+    GeglRectangle  process_roi;
+
+    gfloat        *out_pixel;
+    gfloat        *in_pixel;
+    gfloat        *mask_pixel;
+    gfloat        *paint_pixel;
+  };
+
+  template <class Derived>
+  void
+  init (const GimpPaintCoreLoopsParams *params,
+        State<Derived>                 *state,
+        GeglBufferIterator             *iter,
+        const GeglRectangle            *roi,
+        const GeglRectangle            *area) const
+  {
+    Base::init (params, state, iter, roi, area);
+
+    GeglRectangle mask_area = *area;
+
+    mask_area.x -= params->mask_offset_x;
+    mask_area.y -= params->mask_offset_y;
+
+    gegl_buffer_iterator_add (iter, params->dest_buffer, area, 0,
+                              iterator_format,
+                              GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+    gegl_buffer_iterator_add (iter, params->src_buffer, area, 0,
+                              iterator_format,
+                              GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+
+    if (params->mask_buffer)
+      {
+        gegl_buffer_iterator_add (iter, params->mask_buffer, &mask_area, 0,
+                                  babl_format ("Y float"),
+                                  GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+      }
+  }
+
+  template <class Derived>
+  void
+  init_step (const GimpPaintCoreLoopsParams *params,
+             State<Derived>                 *state,
+             GeglBufferIterator             *iter,
+             const GeglRectangle            *roi,
+             const GeglRectangle            *area) const
+  {
+    Base::init_step (params, state, iter, roi, area);
+
+    state->out_pixel  = (gfloat *) iter->data[iterator_base + 0];
+    state->in_pixel   = (gfloat *) iter->data[iterator_base + 1];
+    state->mask_pixel = NULL;
+
+    state->paint_pixel = this->paint_data                               +
+                         (iter->roi[0].y - roi->y) * this->paint_stride +
+                         (iter->roi[0].x - roi->x) * 4;
+
+    if (params->mask_buffer)
+      state->mask_pixel = (gfloat *) iter->data[iterator_base + 2];
+
+    state->process_roi.x      = iter->roi[0].x;
+    state->process_roi.width  = iter->roi[0].width;
+    state->process_roi.height = 1;
+  }
+
+  template <class Derived>
+  void
+  process_row (const GimpPaintCoreLoopsParams *params,
+               State<Derived>                 *state,
+               GeglBufferIterator             *iter,
+               const GeglRectangle            *roi,
+               const GeglRectangle            *area,
+               gint                            y) const
+  {
+    Base::process_row (params, state, iter, roi, area, y);
+
+    state->process_roi.y = y;
+
+    layer_mode.function ((GeglOperation*) &layer_mode,
+                         state->in_pixel,
+                         state->paint_pixel,
+                         state->mask_pixel,
+                         state->out_pixel,
+                         iter->roi[0].width,
+                         &state->process_roi,
+                         0);
+
+    state->in_pixel     += iter->roi[0].width * 4;
+    state->out_pixel    += iter->roi[0].width * 4;
+    if (params->mask_buffer)
+      state->mask_pixel += iter->roi[0].width;
+    state->paint_pixel  += this->paint_stride;
+  }
+};
+
+static AlgorithmDispatch<
+  DoLayerBlend,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND,
+  decltype (dispatch_paint_buf)
+>
+dispatch_do_layer_blend;
+
+
+/* gimp_paint_core_loops_process():
+ *
+ * Performs the set of algorithms requested in 'algorithms', specified as a
+ * bitwise-OR of 'GimpPaintCoreLoopsAlgorithm' values, given the set of
+ * parameters 'params'.
+ *
+ * Note that the order in which the algorithms are performed is currently
+ * fixed, and follows their order of appearance in the
+ * 'GimpPaintCoreLoopsAlgorithm' enum.
+ */
+
+void
+gimp_paint_core_loops_process (const GimpPaintCoreLoopsParams *params,
+                               GimpPaintCoreLoopsAlgorithm     algorithms)
+{
+  GeglRectangle roi;
+
+  if (params->paint_buf)
     {
-      GeglBufferIterator *iter;
+      roi.x      = params->paint_buf_offset_x;
+      roi.y      = params->paint_buf_offset_y;
+      roi.width  = gimp_temp_buf_get_width  (params->paint_buf);
+      roi.height = gimp_temp_buf_get_height (params->paint_buf);
+    }
+  else
+    {
+      roi.x      = params->paint_buf_offset_x;
+      roi.y      = params->paint_buf_offset_y;
+      roi.width  = gimp_temp_buf_get_width (params->paint_mask) -
+                   params->paint_mask_offset_x;
+      roi.height = gimp_temp_buf_get_height (params->paint_mask) -
+                   params->paint_mask_offset_y;
+    }
+
+  dispatch (
+    [&] (auto algorithm_type)
+    {
+      using Algorithm = typename decltype (algorithm_type)::type;
+      using State     = typename Algorithm::template State<Algorithm>;
 
-      iter = gegl_buffer_iterator_new (canvas_buffer, area, 0,
-                                       babl_format ("Y float"),
-                                       GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE);
+      Algorithm algorithm (params);
 
-      if (stipple)
+      gimp_parallel_distribute_area (&roi, MIN_PARALLEL_SUB_AREA,
+                                     [=] (const GeglRectangle *area)
         {
-          if (mask_format == babl_format ("Y u8"))
+          State state;
+          gint  y;
+
+          if (Algorithm::n_iterators > 0)
             {
-              const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (paint_mask);
-              mask_data += mask_start_offset;
+              GeglBufferIterator *iter;
+
+              iter = gegl_buffer_iterator_empty_new ();
+
+              algorithm.init (params, &state, iter, &roi, area);
 
               while (gegl_buffer_iterator_next (iter))
                 {
-                  gfloat *out_pixel = (gfloat *)iter->data[0];
-                  int iy, ix;
+                  algorithm.init_step (params, &state, iter, &roi, area);
 
-                  for (iy = 0; iy < iter->roi[0].height; iy++)
+                  for (y = 0; y < iter->roi[0].height; y++)
                     {
-                      int mask_offset = (iy + iter->roi[0].y - roi.y) * mask_stride + iter->roi[0].x - roi.x;
-                      const guint8 *mask_pixel = &mask_data[mask_offset];
-
-                      for (ix = 0; ix < iter->roi[0].width; ix++)
-                        {
-                          out_pixel[0] += (1.0 - out_pixel[0]) * (*mask_pixel / 255.0f) * opacity;
-
-                          mask_pixel += 1;
-                          out_pixel  += 1;
-                        }
+                      algorithm.process_row (params, &state,
+                                             iter, &roi, area,
+                                             iter->roi[0].y + y);
                     }
                 }
             }
-          else if (mask_format == babl_format ("Y float"))
+          else
             {
-              const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (paint_mask);
-              mask_data += mask_start_offset;
-
-              while (gegl_buffer_iterator_next (iter))
-                {
-                  gfloat *out_pixel = (gfloat *)iter->data[0];
-                  int iy, ix;
+              GeglBufferIterator iter;
 
-                  for (iy = 0; iy < iter->roi[0].height; iy++)
-                    {
-                      int mask_offset = (iy + iter->roi[0].y - roi.y) * mask_stride + iter->roi[0].x - roi.x;
-                      const gfloat *mask_pixel = &mask_data[mask_offset];
+              iter.roi[0] = *area;
 
-                      for (ix = 0; ix < iter->roi[0].width; ix++)
-                        {
-                          out_pixel[0] += (1.0 - out_pixel[0]) * (*mask_pixel) * opacity;
+              algorithm.init      (params, &state, &iter, &roi, area);
+              algorithm.init_step (params, &state, &iter, &roi, area);
 
-                          mask_pixel += 1;
-                          out_pixel  += 1;
-                        }
-                    }
+              for (y = 0; y < iter.roi[0].height; y++)
+                {
+                  algorithm.process_row (params, &state,
+                                         &iter, &roi, area,
+                                         iter.roi[0].y + y);
                 }
             }
-          else
-            {
-              g_warning("Mask format not supported: %s", babl_get_name (mask_format));
-            }
-        }
-      else
-        {
-          if (mask_format == babl_format ("Y u8"))
-            {
-              const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (paint_mask);
-              mask_data += mask_start_offset;
+        });
+    },
+    params, algorithms, identity<AlgorithmBase> (),
+    dispatch_combine_paint_mask_to_canvas_mask_to_paint_buf_alpha,
+    dispatch_combine_paint_mask_to_canvas_mask,
+    dispatch_canvas_buffer_to_paint_buf_alpha,
+    dispatch_paint_mask_to_paint_buffer,
+    dispatch_do_layer_blend);
+}
 
-              while (gegl_buffer_iterator_next (iter))
-                {
-                  gfloat *out_pixel = (gfloat *)iter->data[0];
-                  int iy, ix;
 
-                  for (iy = 0; iy < iter->roi[0].height; iy++)
-                    {
-                      int mask_offset = (iy + iter->roi[0].y - roi.y) * mask_stride + iter->roi[0].x - roi.x;
-                      const guint8 *mask_pixel = &mask_data[mask_offset];
+/* combine_paint_mask_to_canvas_mask():
+ *
+ * A convenience wrapper around 'gimp_paint_core_loops_process()', performing
+ * just the COMBINE_PAINT_MASK_TO_CANVAS_MASK algorithm.
+ */
 
-                      for (ix = 0; ix < iter->roi[0].width; ix++)
-                        {
-                          if (opacity > out_pixel[0])
-                            out_pixel[0] += (opacity - out_pixel[0]) * (*mask_pixel / 255.0f) * opacity;
+void
+combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
+                                   gint               mask_x_offset,
+                                   gint               mask_y_offset,
+                                   GeglBuffer        *canvas_buffer,
+                                   gint               x_offset,
+                                   gint               y_offset,
+                                   gfloat             opacity,
+                                   gboolean           stipple)
+{
+  GimpPaintCoreLoopsParams params = {};
 
-                          mask_pixel += 1;
-                          out_pixel  += 1;
-                        }
-                    }
-                }
-            }
-          else if (mask_format == babl_format ("Y float"))
-            {
-              const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (paint_mask);
-              mask_data += mask_start_offset;
+  params.canvas_buffer       = canvas_buffer;
 
-              while (gegl_buffer_iterator_next (iter))
-                {
-                  gfloat *out_pixel = (gfloat *)iter->data[0];
-                  int iy, ix;
+  params.paint_buf_offset_x  = x_offset;
+  params.paint_buf_offset_y  = y_offset;
 
-                  for (iy = 0; iy < iter->roi[0].height; iy++)
-                    {
-                      int mask_offset = (iy + iter->roi[0].y - roi.y) * mask_stride + iter->roi[0].x - roi.x;
-                      const gfloat *mask_pixel = &mask_data[mask_offset];
+  params.paint_mask          = paint_mask;
+  params.paint_mask_offset_x = mask_x_offset;
+  params.paint_mask_offset_y = mask_y_offset;
 
-                      for (ix = 0; ix < iter->roi[0].width; ix++)
-                        {
-                          if (opacity > out_pixel[0])
-                            out_pixel[0] += (opacity - out_pixel[0]) * (*mask_pixel) * opacity;
+  params.stipple             = stipple;
 
-                          mask_pixel += 1;
-                          out_pixel  += 1;
-                        }
-                    }
-                }
-            }
-          else
-            {
-              g_warning("Mask format not supported: %s", babl_get_name (mask_format));
-            }
-        }
-    });
+  params.paint_opacity       = opacity;
+
+  gimp_paint_core_loops_process (
+    &params,
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK);
 }
 
+
+/* canvas_buffer_to_paint_buf_alpha():
+ *
+ * A convenience wrapper around 'gimp_paint_core_loops_process()', performing
+ * just the CANVAS_BUFFER_TO_PAINT_BUF_ALPHA algorithm.
+ */
+
 void
 canvas_buffer_to_paint_buf_alpha (GimpTempBuf  *paint_buf,
                                   GeglBuffer   *canvas_buffer,
                                   gint          x_offset,
                                   gint          y_offset)
 {
-  /* Copy the canvas buffer in rect to the paint buffer's alpha channel */
-  GeglRectangle roi;
+  GimpPaintCoreLoopsParams params = {};
 
-  const guint paint_stride = gimp_temp_buf_get_width (paint_buf);
-  gfloat *paint_data       = (gfloat *) gimp_temp_buf_get_data (paint_buf);
+  params.canvas_buffer      = canvas_buffer;
 
-  roi.x = x_offset;
-  roi.y = y_offset;
-  roi.width  = gimp_temp_buf_get_width (paint_buf);
-  roi.height = gimp_temp_buf_get_height (paint_buf);
+  params.paint_buf          = paint_buf;
+  params.paint_buf_offset_x = x_offset;
+  params.paint_buf_offset_y = y_offset;
 
-  gimp_parallel_distribute_area (&roi, MIN_PARALLEL_SUB_AREA,
-                                 [=] (const GeglRectangle *area)
-    {
-      GeglBufferIterator *iter;
-
-      iter = gegl_buffer_iterator_new (canvas_buffer, area, 0,
-                                       babl_format ("Y float"),
-                                       GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
-
-      while (gegl_buffer_iterator_next (iter))
-        {
-          gfloat *canvas_pixel = (gfloat *)iter->data[0];
-          int iy, ix;
-
-          for (iy = 0; iy < iter->roi[0].height; iy++)
-            {
-              int paint_offset = (iy + iter->roi[0].y - roi.y) * paint_stride + iter->roi[0].x - roi.x;
-              float *paint_pixel = &paint_data[paint_offset * 4];
+  gimp_paint_core_loops_process (
+    &params,
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA);
+}
 
-              for (ix = 0; ix < iter->roi[0].width; ix++)
-                {
-                  paint_pixel[3] *= *canvas_pixel;
 
-                  canvas_pixel += 1;
-                  paint_pixel  += 4;
-                }
-            }
-        }
-    });
-}
+/* paint_mask_to_paint_buffer():
+ *
+ * A convenience wrapper around 'gimp_paint_core_loops_process()', performing
+ * just the PAINT_MASK_TO_PAINT_BUFFER algorithm.
+ */
 
 void
 paint_mask_to_paint_buffer (const GimpTempBuf  *paint_mask,
@@ -248,64 +1247,27 @@ paint_mask_to_paint_buffer (const GimpTempBuf  *paint_mask,
                             GimpTempBuf        *paint_buf,
                             gfloat              paint_opacity)
 {
-  gint width  = gimp_temp_buf_get_width (paint_buf);
-  gint height = gimp_temp_buf_get_height (paint_buf);
-
-  const gint mask_stride       = gimp_temp_buf_get_width (paint_mask);
-  const gint mask_start_offset = mask_y_offset * mask_stride + mask_x_offset;
-  const Babl *mask_format      = gimp_temp_buf_get_format (paint_mask);
-
-  /* Validate that the paint buffer is withing the bounds of the paint mask */
-  g_return_if_fail (width <= gimp_temp_buf_get_width (paint_mask) - mask_x_offset);
-  g_return_if_fail (height <= gimp_temp_buf_get_height (paint_mask) - mask_y_offset);
-
-  gimp_parallel_distribute_range (height, MIN_PARALLEL_SUB_SIZE,
-                                  [=] (gint y, gint height)
-    {
-      int iy, ix;
-      gfloat *paint_pixel = (gfloat *)gimp_temp_buf_get_data (paint_buf) +
-                            y * width * 4;
-
-      if (mask_format == babl_format ("Y u8"))
-        {
-          const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (paint_mask);
-          mask_data += mask_start_offset + y * mask_stride;
+  GimpPaintCoreLoopsParams params = {};
 
-          for (iy = 0; iy < height; iy++)
-            {
-              int mask_offset = iy * mask_stride;
-              const guint8 *mask_pixel = &mask_data[mask_offset];
+  params.paint_buf           = paint_buf;
 
-              for (ix = 0; ix < width; ix++)
-                {
-                  paint_pixel[3] *= (((gfloat)*mask_pixel) / 255.0f) * paint_opacity;
+  params.paint_mask          = paint_mask;
+  params.paint_mask_offset_x = mask_x_offset;
+  params.paint_mask_offset_y = mask_y_offset;
 
-                  mask_pixel  += 1;
-                  paint_pixel += 4;
-                }
-            }
-        }
-      else if (mask_format == babl_format ("Y float"))
-        {
-          const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (paint_mask);
-          mask_data += mask_start_offset + y * mask_stride;
+  params.paint_opacity       = paint_opacity;
 
-          for (iy = 0; iy < height; iy++)
-            {
-              int mask_offset = iy * mask_stride;
-              const gfloat *mask_pixel = &mask_data[mask_offset];
+  gimp_paint_core_loops_process (
+    &params,
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER);
+}
 
-              for (ix = 0; ix < width; ix++)
-                {
-                  paint_pixel[3] *= (*mask_pixel) * paint_opacity;
 
-                  mask_pixel  += 1;
-                  paint_pixel += 4;
-                }
-            }
-        }
-    });
-}
+/* do_layer_blend():
+ *
+ * A convenience wrapper around 'gimp_paint_core_loops_process()', performing
+ * just the DO_LAYER_BLEND algorithm.
+ */
 
 void
 do_layer_blend (GeglBuffer    *src_buffer,
@@ -319,100 +1281,42 @@ do_layer_blend (GeglBuffer    *src_buffer,
                 gint           mask_y_offset,
                 GimpLayerMode  paint_mode)
 {
-  GeglRectangle           roi;
-  const Babl             *iterator_format;
-  guint                   paint_stride;
-  gfloat                 *paint_data;
-  GimpOperationLayerMode  layer_mode;
-
-  paint_stride = gimp_temp_buf_get_width (paint_buf);
-  paint_data   = (gfloat *) gimp_temp_buf_get_data (paint_buf);
-
-  layer_mode.layer_mode          = paint_mode;
-  layer_mode.opacity             = opacity;
-  layer_mode.function            = gimp_layer_mode_get_function (paint_mode);
-  layer_mode.blend_function      = gimp_layer_mode_get_blend_function (paint_mode);
-  layer_mode.blend_space         = gimp_layer_mode_get_blend_space (paint_mode);
-  layer_mode.composite_space     = gimp_layer_mode_get_composite_space (paint_mode);
-  layer_mode.composite_mode      = gimp_layer_mode_get_paint_composite_mode (paint_mode);
-  layer_mode.real_composite_mode = layer_mode.composite_mode;
-
-  iterator_format = gimp_layer_mode_get_format (paint_mode,
-                                                layer_mode.composite_space,
-                                                layer_mode.blend_space,
-                                                gimp_temp_buf_get_format (paint_buf));
+  GimpPaintCoreLoopsParams params = {};
 
-  roi.x = x_offset;
-  roi.y = y_offset;
-  roi.width  = gimp_temp_buf_get_width (paint_buf);
-  roi.height = gimp_temp_buf_get_height (paint_buf);
-
-  g_return_if_fail (gimp_temp_buf_get_format (paint_buf) == iterator_format);
-
-  gimp_parallel_distribute_area (&roi, MIN_PARALLEL_SUB_AREA,
-                                 [=] (const GeglRectangle *area)
-    {
-      GeglBufferIterator     *iter;
-      GeglRectangle           mask_area = *area;
+  params.paint_buf          = paint_buf;
+  params.paint_buf_offset_x = x_offset;
+  params.paint_buf_offset_y = y_offset;
 
-      mask_area.x -= mask_x_offset;
-      mask_area.y -= mask_y_offset;
+  params.src_buffer         = src_buffer;
+  params.dest_buffer        = dst_buffer;
 
-      iter = gegl_buffer_iterator_new (dst_buffer, area, 0,
-                                       iterator_format,
-                                       GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+  params.mask_buffer        = mask_buffer;
+  params.mask_offset_x      = mask_x_offset;
+  params.mask_offset_y      = mask_y_offset;
 
-      gegl_buffer_iterator_add (iter, src_buffer, area, 0,
-                                iterator_format,
-                                GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+  params.image_opacity      = opacity;
 
-      if (mask_buffer)
-        {
-          gegl_buffer_iterator_add (iter, mask_buffer, &mask_area, 0,
-                                    babl_format ("Y float"),
-                                    GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
-        }
+  params.paint_mode         = paint_mode;
 
-      while (gegl_buffer_iterator_next (iter))
-        {
-          GeglRectangle  process_roi;
-          gfloat        *out_pixel  = (gfloat *) iter->data[0];
-          gfloat        *in_pixel   = (gfloat *) iter->data[1];
-          gfloat        *mask_pixel = NULL;
-          gfloat        *paint_pixel;
-          gint           iy;
-
-          paint_pixel = paint_data + ((iter->roi[0].y - roi.y) * paint_stride + iter->roi[0].x - roi.x) * 4;
-
-          if (mask_buffer)
-            mask_pixel  = (gfloat *)iter->data[2];
+  gimp_paint_core_loops_process (
+    &params,
+    GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND);
+}
 
-          process_roi.x = iter->roi[0].x;
-          process_roi.width  = iter->roi[0].width;
-          process_roi.height = 1;
 
-          for (iy = 0; iy < iter->roi[0].height; iy++)
-            {
-              process_roi.y = iter->roi[0].y + iy;
-
-              layer_mode.function ((GeglOperation*) &layer_mode,
-                                   in_pixel,
-                                   paint_pixel,
-                                   mask_pixel,
-                                   out_pixel,
-                                   iter->roi[0].width,
-                                   &process_roi,
-                                   0);
-
-              in_pixel    += iter->roi[0].width * 4;
-              out_pixel   += iter->roi[0].width * 4;
-              if (mask_buffer)
-                mask_pixel  += iter->roi[0].width;
-              paint_pixel += paint_stride * 4;
-            }
-        }
-    });
-}
+/* mask_components_onto():
+ *
+ * Copies the contents of 'src_buffer' and 'aux_buffer' into 'dst_buffer', over
+ * 'roi'.  Components set in 'mask' are copied from 'aux_buffer', while those
+ * not set in 'mask' are copied from 'src_buffer'.  'linear_mode' specifies
+ * whether to iterate over the buffers use a linear format.  It should match
+ * the linear mode of the painted-to drawable, to avoid modifying masked-out
+ * components.
+ *
+ * Note that we don't integrate this function into the rest of the algorithm
+ * framework, since it uses a (potentially) different format when iterating
+ * over the buffers than the rest of the algorithms.
+ */
 
 void
 mask_components_onto (GeglBuffer        *src_buffer,
@@ -467,5 +1371,3 @@ mask_components_onto (GeglBuffer        *src_buffer,
         }
     });
 }
-
-} /* extern "C" */
diff --git a/app/paint/gimppaintcore-loops.h b/app/paint/gimppaintcore-loops.h
index 746d2a5..1e2c1fd 100644
--- a/app/paint/gimppaintcore-loops.h
+++ b/app/paint/gimppaintcore-loops.h
@@ -19,43 +19,85 @@
 #define __GIMP_PAINT_CORE_LOOPS_H__
 
 
-void combine_paint_mask_to_canvas_mask  (const GimpTempBuf *paint_mask,
-                                         gint               mask_x_offset,
-                                         gint               mask_y_offset,
-                                         GeglBuffer        *canvas_buffer,
-                                         gint               x_offset,
-                                         gint               y_offset,
-                                         gfloat             opacity,
-                                         gboolean           stipple);
-
-void canvas_buffer_to_paint_buf_alpha   (GimpTempBuf  *paint_buf,
-                                         GeglBuffer   *canvas_buffer,
-                                         gint          x_offset,
-                                         gint          y_offset);
-
-void paint_mask_to_paint_buffer         (const GimpTempBuf  *paint_mask,
-                                         gint                mask_x_offset,
-                                         gint                mask_y_offset,
-                                         GimpTempBuf        *paint_buf,
-                                         gfloat              paint_opacity);
-
-void do_layer_blend                     (GeglBuffer    *src_buffer,
-                                         GeglBuffer    *dst_buffer,
-                                         GimpTempBuf   *paint_buf,
-                                         GeglBuffer    *mask_buffer,
-                                         gfloat         opacity,
-                                         gint           x_offset,
-                                         gint           y_offset,
-                                         gint           mask_x_offset,
-                                         gint           mask_y_offset,
-                                         GimpLayerMode  paint_mode);
-
-void mask_components_onto               (GeglBuffer        *src_buffer,
-                                         GeglBuffer        *aux_buffer,
-                                         GeglBuffer        *dst_buffer,
-                                         GeglRectangle     *roi,
-                                         GimpComponentMask  mask,
-                                         gboolean           linear_mode);
+typedef enum
+{
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_NONE                              = 0,
+
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK = 1 << 0,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA  = 1 << 1,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER        = 1 << 2,
+  GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND                    = 1 << 3
+} GimpPaintCoreLoopsAlgorithm;
+
+
+typedef struct
+{
+  GeglBuffer        *canvas_buffer;
+
+  GimpTempBuf       *paint_buf;
+  gint               paint_buf_offset_x;
+  gint               paint_buf_offset_y;
+
+  const GimpTempBuf *paint_mask;
+  gint               paint_mask_offset_x;
+  gint               paint_mask_offset_y;
+
+  gboolean           stipple;
+
+  GeglBuffer        *src_buffer;
+  GeglBuffer        *dest_buffer;
+
+  GeglBuffer        *mask_buffer;
+  gint               mask_offset_x;
+  gint               mask_offset_y;
+
+  gdouble            paint_opacity;
+  gdouble            image_opacity;
+
+  GimpLayerMode      paint_mode;
+} GimpPaintCoreLoopsParams;
+
+
+void gimp_paint_core_loops_process      (const GimpPaintCoreLoopsParams *params,
+                                         GimpPaintCoreLoopsAlgorithm     algorithms);
+
+void combine_paint_mask_to_canvas_mask  (const GimpTempBuf              *paint_mask,
+                                         gint                            mask_x_offset,
+                                         gint                            mask_y_offset,
+                                         GeglBuffer                     *canvas_buffer,
+                                         gint                            x_offset,
+                                         gint                            y_offset,
+                                         gfloat                          opacity,
+                                         gboolean                        stipple);
+
+void canvas_buffer_to_paint_buf_alpha   (GimpTempBuf                    *paint_buf,
+                                         GeglBuffer                     *canvas_buffer,
+                                         gint                            x_offset,
+                                         gint                            y_offset);
+
+void paint_mask_to_paint_buffer         (const GimpTempBuf              *paint_mask,
+                                         gint                            mask_x_offset,
+                                         gint                            mask_y_offset,
+                                         GimpTempBuf                    *paint_buf,
+                                         gfloat                          paint_opacity);
+
+void do_layer_blend                     (GeglBuffer                     *src_buffer,
+                                         GeglBuffer                     *dst_buffer,
+                                         GimpTempBuf                    *paint_buf,
+                                         GeglBuffer                     *mask_buffer,
+                                         gfloat                          opacity,
+                                         gint                            x_offset,
+                                         gint                            y_offset,
+                                         gint                            mask_x_offset,
+                                         gint                            mask_y_offset,
+                                         GimpLayerMode                   paint_mode);
+
+void mask_components_onto               (GeglBuffer                     *src_buffer,
+                                         GeglBuffer                     *aux_buffer,
+                                         GeglBuffer                     *dst_buffer,
+                                         GeglRectangle                  *roi,
+                                         GimpComponentMask               mask,
+                                         gboolean                        linear_mode);
 
 
 #endif /* __GIMP_PAINT_CORE_LOOPS_H__ */
diff --git a/app/paint/gimppaintcore.c b/app/paint/gimppaintcore.c
index 79b8e48..f8e6986 100644
--- a/app/paint/gimppaintcore.c
+++ b/app/paint/gimppaintcore.c
@@ -864,77 +864,78 @@ gimp_paint_core_paste (GimpPaintCore            *core,
     }
   else
     {
-      GimpTempBuf *paint_buf = gimp_gegl_buffer_get_temp_buf (core->paint_buffer);
-      GeglBuffer  *dest_buffer;
-      GeglBuffer  *src_buffer;
+      GimpPaintCoreLoopsParams    params = {};
+      GimpPaintCoreLoopsAlgorithm algorithms = GIMP_PAINT_CORE_LOOPS_ALGORITHM_NONE;
 
-      if (! paint_buf)
+      params.paint_buf          = gimp_gegl_buffer_get_temp_buf (core->paint_buffer);
+      params.paint_buf_offset_x = core->paint_buffer_x;
+      params.paint_buf_offset_y = core->paint_buffer_y;
+
+      if (! params.paint_buf)
         return;
 
       if (core->comp_buffer)
-        dest_buffer = core->comp_buffer;
+        params.dest_buffer = core->comp_buffer;
       else
-        dest_buffer = gimp_drawable_get_buffer (drawable);
+        params.dest_buffer = gimp_drawable_get_buffer (drawable);
 
       if (mode == GIMP_PAINT_CONSTANT)
         {
+          params.canvas_buffer = core->canvas_buffer;
+
           /* This step is skipped by the ink tool, which writes
            * directly to canvas_buffer
            */
           if (paint_mask != NULL)
             {
               /* Mix paint mask and canvas_buffer */
-              combine_paint_mask_to_canvas_mask (paint_mask,
-                                                 paint_mask_offset_x,
-                                                 paint_mask_offset_y,
-                                                 core->canvas_buffer,
-                                                 core->paint_buffer_x,
-                                                 core->paint_buffer_y,
-                                                 paint_opacity,
-                                                 GIMP_IS_AIRBRUSH (core));
+              params.paint_mask          = paint_mask;
+              params.paint_mask_offset_x = paint_mask_offset_x;
+              params.paint_mask_offset_y = paint_mask_offset_y;
+              params.stipple             = GIMP_IS_AIRBRUSH (core);
+              params.paint_opacity       = paint_opacity;
+
+              algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_MASK;
             }
 
           /* Write canvas_buffer to paint_buf */
-          canvas_buffer_to_paint_buf_alpha (paint_buf,
-                                            core->canvas_buffer,
-                                            core->paint_buffer_x,
-                                            core->paint_buffer_y);
+          algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA;
 
           /* undo buf -> paint_buf -> dest_buffer */
-          src_buffer = core->undo_buffer;
+          params.src_buffer = core->undo_buffer;
         }
       else
         {
           g_return_if_fail (paint_mask);
 
           /* Write paint_mask to paint_buf, does not modify canvas_buffer */
-          paint_mask_to_paint_buffer (paint_mask,
-                                      paint_mask_offset_x,
-                                      paint_mask_offset_y,
-                                      paint_buf,
-                                      paint_opacity);
+          params.paint_mask          = paint_mask;
+          params.paint_mask_offset_x = paint_mask_offset_x;
+          params.paint_mask_offset_y = paint_mask_offset_y;
+          params.paint_opacity       = paint_opacity;
+
+          algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUFFER;
 
           /* dest_buffer -> paint_buf -> dest_buffer */
           if (core->comp_buffer)
-            src_buffer = gimp_drawable_get_buffer (drawable);
+            params.src_buffer = gimp_drawable_get_buffer (drawable);
           else
-            src_buffer = dest_buffer;
+            params.src_buffer = params.dest_buffer;
         }
 
-      do_layer_blend (src_buffer,
-                      dest_buffer,
-                      paint_buf,
-                      core->mask_buffer,
-                      image_opacity,
-                      core->paint_buffer_x,
-                      core->paint_buffer_y,
-                      core->mask_x_offset,
-                      core->mask_y_offset,
-                      paint_mode);
+      params.mask_buffer   = core->mask_buffer;
+      params.mask_offset_x = core->mask_x_offset;
+      params.mask_offset_y = core->mask_y_offset;
+      params.image_opacity = image_opacity;
+      params.paint_mode    = paint_mode;
+
+      algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND;
+
+      gimp_paint_core_loops_process (&params, algorithms);
 
       if (core->comp_buffer)
         {
-          mask_components_onto (src_buffer,
+          mask_components_onto (params.src_buffer,
                                 core->comp_buffer,
                                 gimp_drawable_get_buffer (drawable),
                                 GEGL_RECTANGLE (core->paint_buffer_x,


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