[gimp] Bug 761795 - Performance improvement for the flood operation



commit a11dd0e29a1f9b221673b2f713e7bec4621e6c7d
Author: Ell <ell_se yahoo com>
Date:   Mon Feb 8 18:42:18 2016 +0000

    Bug 761795 - Performance improvement for the flood operation
    
    Improve the performance of the "gimp:flood" operation and add plenty
    of comments.

 app/operations/gimpoperationflood.c | 1427 ++++++++++++++++++++++++++++++++---
 1 files changed, 1319 insertions(+), 108 deletions(-)
---
diff --git a/app/operations/gimpoperationflood.c b/app/operations/gimpoperationflood.c
index 59d4b93..699ce38 100644
--- a/app/operations/gimpoperationflood.c
+++ b/app/operations/gimpoperationflood.c
@@ -18,38 +18,545 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+
+/* Flood
+ * =====
+ *
+ * The flood operation eliminates "holes" -- darker areas surrounded by lighter
+ * areas -- in single-component (grayscale) images.  It is particularly useful
+ * for eliminating such holes from selections; see GIMP bug #761060 for more
+ * details.
+ *
+ * The conceptual model considers the input image as the height-map of a hilly
+ * terrain.  After heavy rain completely floods the terrain, the remaining
+ * water form lakes in its depressions.
+ *
+ *                                 _______
+ *                                /.\    /\_____________
+ *                   ____________/...\  /..\  Water  /..\
+ *                  /....\      /.....\/....\__     /....\____
+ *                 /......\____/....Ground.....\___/......\  /\
+ *              __/........................................\/..\_
+ *
+ *                  A depiction of a one-dimensional flood.
+ *                  Valleys correspond to "holes" in the input
+ *                  image, filled with "water" according to
+ *                  their surrounding.
+ *
+ * The result of the flood operation is the height-map of the terrain after the
+ * flood, taking both ground- and water-level into account.
+ *
+ * More formally, the flood operation assigns to each pixel the minimum of the
+ * maximal input-image values along all paths from the pixel to the "outside".
+ * That is, the output value at pixel `x`, `o(x)`, is given by
+ *
+ *                    o(x)   =      min       max     i(y),
+ *                               p in P(x)   y in p
+ *
+ * where `P(x)` is the set of all paths from `x` to the outside, and `i(y)` is
+ * the value of the input image at pixel `y`.
+ *
+ * Algorithm
+ * ---------
+ *
+ * In accord with the conceptual flood model, we refer to the values of the
+ * input image as the "ground level", and to the values of the output image as
+ * the "water level"; these values range from 0 to 1, and are considered to
+ * be 0 outside the bounds of the image.  Note not to confuse "water level"
+ * with "water depth"; we use the term "water level" simply to refer to the
+ * elevation of either the ground or the water at a certain point.
+ *
+ * Our starting point is modeling the problem as a cellular automaton.  The
+ * state of each cell (pixel) is its current water level, which is initially 1
+ * everywhere inside the image.  The water level at each cell is updated
+ * according to the rule
+ *
+ *                 w   (x)   =   max { g(x),    min     w (y) },
+ *                  n+1                      y in N(x)   n
+ *
+ * where `w_n(x)` is the water level at pixel `x` on generation `n`, `g(x)` is
+ * the ground level at `x`, and `N(x)` is the set of (orthogonal) neighbors of
+ * `x`, including itself.  In other words, the new water level at each pixel is
+ * the maximum of its ground level, and the minimum of its own water level, and
+ * that of its neighbors.  This automaton converges to the output of the
+ * operation.
+ *
+ * The automaton converges after, at most, `n` generations, where `n` is the
+ * number of pixels in the image.  Therefore, a naive implementation, where
+ * at most `O(n)` cells are processed on each generation, has a worst-case
+ * time complexity of `O(n^2)`.  By making a few observations, we can do
+ * better, at least in the most common cases.
+ *
+ * First, note that the final state doesn't depend on the order of the
+ * individual steps.  That is, we don't actually have to update the water level
+ * an entire generation at a time, but rather we can apply the transformation
+ * rule to any pixel, at any time, arbitrarily, until convergence.
+ * Furthermore, we don't even have to consider all the neighbors of a pixel
+ * each time we apply the rule: as long as we make sure to never increase the
+ * water level of a pixel, i.e., as long as we consider the pixel's own water
+ * level as part of the minimum term, we can take a different subset of the
+ * neighbors into account each time.
+ *
+ * Second, using the above observation, note that we can solve a one-
+ * dimensional automaton (i.e., compute its final state) in linear time, using
+ * two passes:  On the first pass, we iterate over the pixels left-to-right,
+ * applying the transformation rule while considering only the left neighbor of
+ * each pixel (using the water level assigned to the neighbor on the previous
+ * iteration; recall that the water level of the left neighbor of the leftmost
+ * pixel of the image is considered to be 0.)  On the second pass, we work in
+ * reverse -- we iterate over the pixels right-to-left, applying the rule while
+ * considering only the right neighbors.
+ *
+ *                 _________________________________________________
+ *                                   /.\    /\           __
+ *                      ____        /...\  /..\         /..\
+ *            (a)      /....\      /.....\/....\__     /....\
+ *                    /......\____/...............\___/......\  /\
+ *                 __/........................................\/..\_
+ *                                    ______________________________
+ *                                   /.\    /\           __
+ *                      ____________/...\  /..\         /..\
+ *            (b)      /....\      /.....\/....\__     /....\
+ *                    /......\____/...............\___/......\  /\
+ *                 __/........................................\/..\_
+ *                                    _______
+ *                                   /.\    /\_____________
+ *                      ____________/...\  /..\         /..\
+ *            (c)      /....\      /.....\/....\__     /....\____
+ *                    /......\____/...............\___/......\  /\
+ *                 __/........................................\/..\_
+ *
+ *                  Water level of a one-dimensional automaton
+ *                  (a) initially; (b) after the first pass;
+ *                  (c) after the second pass.
+ *
+ * While this technique doesn't extend directly to two dimensions, we can
+ * leverage it by processing one-dimensional strips of pixels in batch, as
+ * described above, instead of transforming pixels individually.
+ *
+ * Finally, another obvious way to speed things up is to minimize the amount of
+ * unnecessary work we're doing.  In particular, we only need to process pixels
+ * whose neighbors' state changed.
+ *
+ * Taking all of the above into consideration, this is what we do:
+ *
+ * We maintain a queue of "segments" -- one-dimensional, contiguous, strips of
+ * pixels -- whose water level needs to be updated, as a result of a change in
+ * the water level of the pixels of a neighboring segment, referred to as the
+ * "source segment".  Although, for efficiency reasons, we allow segments to be
+ * either horizontal or vertical, for simplicity, we treat all segments as
+ * though they're horizontal, and perform the necessary coordinate-system
+ * transformation when dealing with vertical segments, as explained later.
+ *
+ * Each segment is processed using the following steps:
+ *
+ *   1. Vertical propagation:  The segment's pixels are updated, using the
+ *      above transformation rule, considering only the corresponding
+ *      neighboring pixels of the source segment.  During the process, we
+ *      inspect which of the segment's pixels actually changed, and create a
+ *      list of "dirty ranges" of modified pixels.  We construct the ranges
+ *      such that all pixels of each range have the same water level; this
+ *      becomes important in the next step.
+ *
+ *                 - - -+-----+-----+-----+-----+-----+-----+-----+- - -
+ *        Source        |     |     |     |     |     |     |     |
+ *        Segment       |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+ *                 - - -+--|--+--|--+--|--+--|--+--|--+--|--+--|--+- - -
+ *        Current       |  V  |  V  |  V  |  V  |  V  |  V  |  V  |
+ *        Segment       |     |  x  |  x  |     |  y  |  z  |     |
+ *                 - - -+-----+-----+-----+-----+-----+-----+-----+- - -
+ *        Dirty
+ *        Ranges              |-----------|     |-----|-----|
+ *
+ *                  The current segment's pixels are updated
+ *                  according to the neighboring pixels of the
+ *                  source segment, and contiguous runs of
+ *                  modified, equivalent pixels form a list of
+ *                  dirty ranges.
+ *
+ *   2. Horizontal propagation:  The segment's pixels are updated, considering
+ *      only their left and right neighbors, using the two-pass process
+ *      described above.  Though semantically equivalent, this process is
+ *      slightly more intricate than the one described above, since we use the
+ *      dirty ranges from the previous step to take a few shortcuts.
+ *
+ *      Recall that all pixels of a single dirty range are equal, and
+ *      therefore, unless modified as part of the current pass, don't affect
+ *      each other's state.  On the other hand, all pixels outside any dirty
+ *      range didn't change, and therefore, unless modified as part of the
+ *      current pass, don't affect each other's state either.  As a result,
+ *      initially, only the pixels that directly neighbor a dirty range, in the
+ *      direction of the pass, need to be updated.  If the water level of such
+ *      pixel changes, we need to update the following pixel, and so on.  Once
+ *      the water level of a pixel remains the same, we don't have to update
+ *      the next pixel, but can rather jump directly to the pixel at the edge
+ *      of the next dirty range, and so on.
+ *
+ *      For example, when scanning left-to-right, we start at the pixel
+ *      directly to the right of the first (leftmost) dirty range.  We apply
+ *      the transformation rule to this pixel, and to the pixels to its right,
+ *      until the water level of one of them is unaffected.  At this point, we
+ *      jump directly to the pixel to the right of the next dirty range.
+ *
+ *             - -+---+---+---+---+---+---+---+---+---+---+---+- -
+ *                |   |   |   | 1 | 2 |(3)|   |   | 4 |(5)|   |
+ *             - -+---+---+---+---+---+---+---+---+---+---+---+- -
+ *
+ *                    |-------|               |---|
+ *
+ *                  Pixel traversal order on a left-to-right
+ *                  pass.  Traversal starts to the right of
+ *                  the first dirty range, at pixel `1`.
+ *                  Pixel `(3)` is unaffected, and so we jump
+ *                  directly to the right of the second dirty
+ *                  range.
+ *
+ *      Of course, when scanning right-to-left, it all reverses, and we start
+ *      to the left of the last (rightmost) dirty range, etc.
+ *
+ *      During each pass, we extend the dirty ranges, in the direction of the
+ *      scan, to include the newly modified pixels.  Note that, while scanning
+ *      a sequence of pixels next to one of the dirty ranges, we may reach the
+ *      edge of the next range.  In such case, we keep scanning the pixels of
+ *      the second range, but we don't extend the previous range any further,
+ *      so that the two ranges meet, but don't overlap.
+ *
+ *                  - -+---+---+---+---+---+---+---+---+---+---+---+- -
+ *                     |   |   |   | 1 | 2 |(3)|   |   | 4 | 5 | 6 |
+ *                  - -+---+---+---+---+---+---+---+---+---+---+---+- -
+ *
+ *        Original         |-------|               |---|   |---|
+ *        Extended         |---------------|       |-------|-------|
+ *
+ *                  The dirty ranges are extended, in the
+ *                  direction of the scan, to include the
+ *                  newly modified pixels.  The scan can
+ *                  "leak" into existing ranges (notice the
+ *                  third range in the illustration), in which
+ *                  case the previous range is only extended
+ *                  as far as the leaked-into range.
+ *
+ *      Note that the rightmost and leftmost ranges may be extended past the
+ *      bounds of the segment, during the left-to-right and right-to-left
+ *      passes, respectively (recall that a segment doesn't necessarily span an
+ *      entire row.)
+ *
+ *      Also note that, if a dirty range is extended, or if its existing pixels
+ *      are modified, during the first, left-to-right, pass, then it's possible
+ *      that its water level will not be uniform come the second, right-to-
+ *      left, pass; this seems to break our assumption about the state of the
+ *      dirty ranges, which allowed us to take the shortcut described above.
+ *      This shortcut is still valid on the second pass, though.  It turns out
+ *      that we only need the ranges to meet a weaker condition -- it's enough
+ *      for the water level of the pixels of each dirty range to be
+ *      monotonically decreasing in the direction of the scan (right-to-left,
+ *      in our case).  This condition is still met at the end of the first
+ *      pass.
+ *
+ *      One final detail: each dirty range has an associated `modified` flag,
+ *      which is initially cleared.  If, during the above process, the range is
+ *      extended, or its existing pixels are modified, then its `modified` flag
+ *      is set.  This flag is used by the next step.
+ *
+ *   3. Distribution:  The changes to the current segment's water level may
+ *      affect the two neighboring rows.  For each dirty range, we push two new
+ *      segments into the queue -- one for each neighboring row -- using the
+ *      current row as their source segment.
+ *
+ *      There's one exception to this, however: if a dirty range hasn't been
+ *      modified during the horizontal propagation step, i.e., if its
+ *      `modified` flag is clear, then it necessarily doesn't affect the
+ *      neighboring pixels of the source segment.  Hence, in this case, we can
+ *      avoid pushing a corresponding segment for the row of the source
+ *      segment.
+ *
+ *
+ *                   +---+---+---+---+   .   .   .   +---+---+   .   .
+ *          Source   |   |   |   |   |               |   |   |
+ *                   +---+---+---+---+---+---+---+---+---+---+   .   .
+ *          Current          |   |   |   |   |   |   |   |   |
+ *                   +---+---+---+---+---+---+---+---+---+---+   .   .
+ *                   |   |   |   |   |       |   |   |   |   |
+ *                   +---+---+---+---+   .   +---+   +---+---+   .   .
+ *
+ *                   |---------------|       |---|   |-------|
+ *                       Modified                    Modified
+ *
+ *                  New segments, corresponding to the dirty
+ *                  ranges, are pushed into the queue for each
+ *                  of the current segment's neighboring rows.
+ *                  No segments are pushed for the row of the
+ *                  source segment for non-modified dirty
+ *                  ranges.
+ *
+ *      To amortize the cost of maintaining and processing multiple separate
+ *      segments, dirty ranges that are separated by a small-enough gap are
+ *      coalesced into a single range prior to this step; the gap between the
+ *      ranges, if exists, becomes part of the coalesced range; the `modified`
+ *      flag of the coalesced range is the logical-OR of the `modified` flags
+ *      of the individual ranges.
+ *
+ * Start and Termination
+ * ---------------------
+ *
+ * Recall that segments are pushed into the queue as a result of a change in
+ * the water level of a neighboring segment.  To kick this process off, we
+ * pretend that the water level outside the image instantaneously dropped from
+ * 1 to 0, and push four segments, referred to as the "seed segments",
+ * corresponding to the four edges of the image (there may, in fact, be less
+ * than four seed segments, if the image is 1- or 2-pixel wide or high.)  The
+ * source segment of the seed segments, hypothetically, lies outside the image;
+ * in particular, the water level of the neighboring pixels in the vertical
+ * propagation step is taken to be 0 for the seed segments.
+ *
+ *                    +-----------------------------------+
+ *                    |                                   |
+ *                    +---+---------------------------+---+
+ *                    |   |                           |   |
+ *                    |   |                           |   |
+ *                    |   |                           |   |
+ *                    |   |                           |   |
+ *                    |   |                           |   |
+ *                    |   |                           |   |
+ *                    |   |                           |   |
+ *                    +---+---------------------------+---+
+ *                    |                                   |
+ *                    +-----------------------------------+
+ *
+ *                  The four seed segments -- one for each
+ *                  edge of the image.
+ *
+ * The process terminates when there are no more segments left in the queue.
+ * At this point, the automaton has converged, and the water level corresponds
+ * to the output of the flood operation.
+ *
+ * Coordinate Systems
+ * ------------------
+ *
+ * As mentioned above, segments can be either horizontal or vertical, but are
+ * treated internally as horizontal.  Additionally, the region-of-interest
+ * (ROI) of the operation might not span the entire image; in this case, the
+ * operation is performed on the ROI in isolation, and what we've been calling
+ * the "image" up until now is in fact the ROI (in particular, the ground level
+ * outside the ROI is considered to be 0, even if the input image isn't
+ * completely black outside the ROI.)
+ *
+ * To deal with this, we employ three coordinate systems:
+ *
+ *   - Image-physical:  This is the "real" coordinate system of the operation,
+ *     used when talking to the outside world (i.e., GEGL).  Its origin is at
+ *     the top-left corner of the image, its x-axis points right, and its y-
+ *     axis points down.
+ *
+ *   - Image-virtual:  This is the same as the image-physical coordinate
+ *     system, except that the x- and y-coordinates are swapped when dealing
+ *     with vertical segments.  In other words, when processing a vertical
+ *     segment, we pretend that image is transposed (i.e., reflected along the
+ *     south-east diagonal).  We transform to/from this coordinate system on
+ *     the boundary between GEGL and the rest of the algorithm.
+ *
+ *   - ROI-virtual:  This is the same as the image-virtual coordinate system,
+ *     except that its origin is translated to the top-left corner of the ROI.
+ *     Internal coordinates, that aren't communicated to GEGL, are given in
+ *     this coordinate system.
+ *
+ *      x                        y
+ *    +----> - - - - - - -+    +----> - - - - - - -+    +- - - - - - - - - -+
+ *  y |                   |  x |                   |    |     y             |
+ *    |   +- - - - -+          |   +- - - - -+              +----> - -+
+ *    v   |         |     |    v   |         |     |    | x |         |     |
+ *            ROI                      ROI                  |   ROI
+ *    |   |         |     |    |   |         |     |    |   v         |     |
+ *        +- - - - -+              +- - - - -+              +- - - - -+
+ *    |                   |    |                   |    |                   |
+ *    +- - - - - - - - - -+    +- - - - - - - - - -+    +- - - - - - - - - -+
+ *
+ *             (a)                      (b)                      (c)
+ *
+ *                  The three coordinate systems: (a) image-
+ *                  physical, (b) image-virtual (here shown
+ *                  transposed), and (c) ROI-virtual.
+ *
+ * To sum it up, internal coordinates (e.g., the y-coordinate of the current
+ * segment, or the x-coordinates of the dirty ranges) are given in the ROI-
+ * virtual coordinate system.  Coordinates of `GeglRectangle`s (such as the ROI
+ * rectangle, or the rectangles used when reading and writing to the GEGL
+ * buffers) are given in the image-virtual coordinate system, but are
+ * transformed to/from the image-physical coordinate system before being
+ * passed-to/received-from GEGL.
+ */
+
+
 #include "config.h"
 
+#include <string.h> /* For `memcpy()`. */
+
 #include <cairo.h>
 #include <gegl.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
-#include "libgimpcolor/gimpcolor.h"
-#include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
 
 #include "operations-types.h"
 
 #include "gimpoperationflood.h"
 
 
-static void          gimp_operation_flood_prepare      (GeglOperation       *operation);
-static GeglRectangle
-          gimp_operation_flood_get_required_for_output (GeglOperation       *self,
-                                                        const gchar         *input_pad,
-                                                        const GeglRectangle *roi);
-static GeglRectangle
-                gimp_operation_flood_get_cached_region (GeglOperation       *self,
-                                                        const GeglRectangle *roi);
+/* Maximal gap, in pixels, between consecutive dirty ranges, below (and
+ * including) which they are coalesced, at the beginning of the distribution
+ * step.
+ */
+#define GIMP_OPERATION_FLOOD_COALESCE_MAX_GAP 32
+
+
+typedef struct _GimpOperationFloodSegment    GimpOperationFloodSegment;
+typedef struct _GimpOperationFloodDirtyRange GimpOperationFloodDirtyRange;
+typedef struct _GimpOperationFloodContext    GimpOperationFloodContext;
+
+
+/* A segment. */
+struct _GimpOperationFloodSegment
+{
+  /* A boolean flag indicating whether the image- and ROI-virtual coordinate
+   * systems should be transposed when processing this segment.  TRUE iff the
+   * segment is vertical.
+   */
+  guint  transpose      : 1;
+
+  /* The y-coordinate of the segment, in the ROI-virtual coordinate system. */
+  guint  y              : 8 * sizeof (guint) - 3;
+  /* The difference between the y-coordinates of the source segment and this
+   * segment, in the ROI-virtual coordinate system.  Either -1 or +1 for
+   * ordinary segments, and 0 for seed segments, as a special case.
+   *
+   * Note the use of `signed` as the type specifier.  The C standard doesn't
+   * specify the signedness of bit-fields whose type specifier is `int`, or a
+   * typedef-name defined as `int`, such as `gint`.
+   */
+  signed source_y_delta : 2;
+
+  /* The x-coordinates of the first and last pixels of the segment, in the ROI-
+   * virtual coordinate system.  Note that this is a closed range:
+   * [x[0], x[1]].
+   */
+  gint   x[2];
+};
+/* Make sure the maximal image dimension fits in
+ * `GimpOperationFloodSegment::y`.
+ */
+G_STATIC_ASSERT (GIMP_MAX_IMAGE_SIZE <= (1 << (8 * sizeof (guint) - 3)));
+
+/* A dirty range of the current segment. */
+struct _GimpOperationFloodDirtyRange
+{
+  /* A boolean flag indicating whether the range was extended, or its existing
+   * pixels were modified, during the horizontal propagation step.
+   */
+  gboolean modified;
+
+  /* The x-coordinates of the first and last pixels of the range, in the ROI-
+   * virtual coordinate system.  Note that this is a closed range:
+   * [x[0], x[1]].
+   */
+  gint     x[2];
+};
+
+/* Common parameters for the various parts of the algorithm. */
+struct _GimpOperationFloodContext
+{
+  /* Input image. */
+  GeglBuffer                *input;
+  /* Input image format. */
+  const Babl                *input_format;
+  /* Output image. */
+  GeglBuffer                *output;
+  /* Output image format. */
+  const Babl                *output_format;
 
-static gboolean gimp_operation_flood_process_segment   (const gfloat        *src,
-                                                        gfloat              *dest,
-                                                        gchar               *changed,
-                                                        gint                 size);
-static gboolean gimp_operation_flood_process           (GeglOperation       *operation,
-                                                        GeglBuffer          *input,
-                                                        GeglBuffer          *output,
-                                                        const GeglRectangle *roi,
-                                                        gint                 level);
+  /* Region of interset. */
+  GeglRectangle  roi;
+
+  /* Current segment. */
+  GimpOperationFloodSegment  segment;
+
+  /* The following arrays hold the ground- and water-level of the current- and
+   * source-segments.  The vertical- and horizontal-propagation steps don't
+   * generally access the input and output GEGL buffers directly, but rather
+   * read from, and write to, these arrays, for efficiency.  These arrays are
+   * read-from, and written-to, the corresponding GEGL buffers before and after
+   * these steps.
+   */
+
+  /* Ground level of the current segment, indexed by x-coordinate in the ROI-
+   * virtual coordinate system.  Only valid inside the range
+   * `[segment.x[0], segment.x[1]]`.
+   */
+  gfloat                    *ground;
+  /* Water level of the current segment, indexed by x-coordinate in the ROI-
+   * virtual coordinate system.  Initially only valid inside the range
+   * `[segment.x[0], segment.x[1]]`, but may be written-to outside this range
+   * during horizontal propagation, if the dirty ranges are extended past the
+   * bounds of the segment.
+   */
+  gfloat                    *water;
+  /* Water level of the source segment, indexed by x-coordinate in the ROI-
+   * virtual coordinate system.  Only valid inside the range
+   * `[segment.x[0], segment.x[1]]`.
+   */
+  gfloat                    *source_water;
+
+  /* A common buffer for the water level of the current- and source-segments.
+   * `water` and `source_water` are pointers into this buffer.  This buffer is
+   * used as an optimization, in order to read the water level of both segments
+   * from the output GEGL buffer in a single call, and is otherwise not used
+   * directly (`water` and `source_water` are used to access the water level
+   * instead.)
+   */
+  gfloat                    *water_buffer;
+};
+
+
+static void          gimp_operation_flood_prepare                      (GeglOperation                     
*operation);
+static GeglRectangle gimp_operation_flood_get_required_for_output      (GeglOperation                     
*self,
+                                                                        const gchar                       
*input_pad,
+                                                                        const GeglRectangle               
*roi);
+static GeglRectangle gimp_operation_flood_get_cached_region            (GeglOperation                     
*self,
+                                                                        const GeglRectangle               
*roi);
+
+static void          gimp_operation_flood_process_push                 (GQueue                            
*queue,
+                                                                        gboolean                           
transpose,
+                                                                        gint                               y,
+                                                                        gint                               
source_y_delta,
+                                                                        gint                               
x0,
+                                                                        gint                               
x1);
+static void          gimp_operation_flood_process_seed                 (GQueue                            
*queue,
+                                                                        const GeglRectangle               
*roi);
+static void          gimp_operation_flood_process_transform_rect       (const GimpOperationFloodContext    
*ctx,
+                                                                        GeglRectangle                      
*dest,
+                                                                        const GeglRectangle                
*src);
+static void          gimp_operation_flood_process_fetch                (GimpOperationFloodContext          
*ctx);
+static gint          gimp_operation_flood_process_propagate_vertical   (GimpOperationFloodContext          
*ctx,
+                                                                        GimpOperationFloodDirtyRange       
*dirty_ranges);
+static void          gimp_operation_flood_process_propagate_horizontal (GimpOperationFloodContext          
*ctx,
+                                                                        gint                                
dir,
+                                                                        GimpOperationFloodDirtyRange       
*dirty_ranges,
+                                                                        gint                                
range_count);
+static gint         gimp_operation_flood_process_coalesce              (const GimpOperationFloodContext    
*ctx,
+                                                                        GimpOperationFloodDirtyRange       
*dirty_ranges,
+                                                                        gint                                
range_count,
+                                                                        gint                                
gap);
+static void          gimp_operation_flood_process_commit               (const GimpOperationFloodContext    
*ctx,
+                                                                        const GimpOperationFloodDirtyRange 
*dirty_ranges,
+                                                                        gint                                
range_count);
+static void          gimp_operation_flood_process_distribute           (const GimpOperationFloodContext    
*ctx,
+                                                                        GQueue                             
*queue,
+                                                                        const GimpOperationFloodDirtyRange 
*dirty_ranges,
+                                                                        gint                                
range_count);
+static gboolean      gimp_operation_flood_process                      (GeglOperation                      
*operation,
+                                                                        GeglBuffer                         
*input,
+                                                                        GeglBuffer                         
*output,
+                                                                        const GeglRectangle                
*roi,
+                                                                        gint                                
level);
 
 
 G_DEFINE_TYPE (GimpOperationFlood, gimp_operation_flood,
@@ -64,8 +571,19 @@ gimp_operation_flood_class_init (GimpOperationFloodClass *klass)
   GeglOperationClass       *operation_class = GEGL_OPERATION_CLASS (klass);
   GeglOperationFilterClass *filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);
 
+  /* The input and output buffers must be different, since we generally need to
+   * be able to access the input-image values after having written to the
+   * output buffer.
+   */
   operation_class->want_in_place = FALSE;
+  /* We don't want `GeglOperationFilter` to split the image across multiple
+   * threads, since this operation depends on, and affects, the image as a
+   * whole.
+   */
   operation_class->threaded      = FALSE;
+  /* Note that both of these options are the default; we set them here for
+   * explicitness.
+   */
 
   gegl_operation_class_set_keys (operation_class,
                                  "name",        "gimp:flood",
@@ -107,39 +625,715 @@ gimp_operation_flood_get_cached_region (GeglOperation       *self,
   return *gegl_operation_source_get_bounding_box (self, "input");
 }
 
-static gboolean
-gimp_operation_flood_process_segment (const gfloat *src,
-                                      gfloat       *dest,
-                                      gchar        *changed,
-                                      gint          size)
+
+/* Pushes a single segment into the queue. */
+static void
+gimp_operation_flood_process_push (GQueue   *queue,
+                                   gboolean  transpose,
+                                   gint      y,
+                                   gint      source_y_delta,
+                                   gint      x0,
+                                   gint      x1)
+{
+  GimpOperationFloodSegment *segment;
+
+  segment                 = g_slice_new (GimpOperationFloodSegment);
+
+  segment->transpose      = transpose;
+  segment->y              = y;
+  segment->source_y_delta = source_y_delta;
+  segment->x[0]           = x0;
+  segment->x[1]           = x1;
+
+  g_queue_push_tail (queue, segment);
+}
+
+/* Pushes the seed segments into the queue.  Recall that the seed segments are
+ * indicated by having their `source_y_delta` field equal 0.
+ *
+ * `roi` is given in the image-physical coordinate system.
+ */
+static void
+gimp_operation_flood_process_seed (GQueue              *queue,
+                                   const GeglRectangle *roi)
 {
-  gint     dir;
-  gboolean any_changed = FALSE;
+  if (roi->width == 0 || roi->height == 0)
+    return;
+
+  /* Top edge. */
+  gimp_operation_flood_process_push (queue,
+                                     /* transpose      = */ FALSE,
+                                     /* y              = */ 0,
+                                     /* source_y_delta = */ 0,
+                                     /* x0             = */ 0,
+                                     /* x1             = */ roi->width - 1);
+
+  if (roi->height == 1)
+    return;
+
+  /* Bottom edge. */
+  gimp_operation_flood_process_push (queue,
+                                     /* transpose      = */ FALSE,
+                                     /* y              = */ roi->height - 1,
+                                     /* source_y_delta = */ 0,
+                                     /* x0             = */ 0,
+                                     /* x1             = */ roi->width - 1);
+
+  if (roi->height == 2)
+    return;
+
+  /* Left edge. */
+  gimp_operation_flood_process_push (queue,
+                                     /* transpose      = */ TRUE,
+                                     /* y              = */ 0,
+                                     /* source_y_delta = */ 0,
+                                     /* x0             = */ 1,
+                                     /* x1             = */ roi->height - 2);
+
+  if (roi->width == 1)
+    return;
 
-  for (dir = 1; dir >= -1; dir -= 2) /*  for dir in [1, -1]: ...  */
+  /* Right edge. */
+  gimp_operation_flood_process_push (queue,
+                                     /* transpose      = */ TRUE,
+                                     /* y              = */ roi->width - 1,
+                                     /* source_y_delta = */ 0,
+                                     /* x0             = */ 1,
+                                     /* x1             = */ roi->height - 2);
+}
+
+/* Transforms a `GeglRectangle` between the image-physical and image-virtual
+ * coordinate systems, in either direction, based on the attributes of the
+ * current segment (namely, its `transpose` flag.)
+ *
+ * Takes the input rectangle through `src`, and stores the result in `dest`.
+ * Both parameters may refer to the same object.
+ */
+static void
+gimp_operation_flood_process_transform_rect (const GimpOperationFloodContext *ctx,
+                                             GeglRectangle                   *dest,
+                                             const GeglRectangle             *src)
+{
+  if (! ctx->segment.transpose)
+    *dest = *src;
+  else
     {
-      gint   i;
-      gfloat level = 0.0;
+      gint temp;
 
-      for (i = size; i; i--)
+      temp         = src->x;
+      dest->x      = src->y;
+      dest->y      = temp;
+
+      temp         = src->width;
+      dest->width  = src->height;
+      dest->height = temp;
+    }
+}
+
+/* Reads the ground- and water-level for the current- and source-segments from
+ * the GEGL buffers into the corresponding arrays.  Sets up the `water` and
+ * `source_water` pointers of `ctx` to point to the right location in
+ * `water_buffer`.
+ */
+static void
+gimp_operation_flood_process_fetch (GimpOperationFloodContext *ctx)
+{
+  /* Image-virtual and image-physical rectangles, respectively. */
+  GeglRectangle iv_rect, ip_rect;
+
+  /* Set the horizontal extent of the rectangle to span the entire segment. */
+  iv_rect.x     = ctx->roi.x + ctx->segment.x[0];
+  iv_rect.width = ctx->segment.x[1] - ctx->segment.x[0] + 1;
+
+  /* For reading the water level, we treat ordinary (non-seed) and seed
+   * segments differently.
+   */
+  if (ctx->segment.source_y_delta != 0)
+    {
+      /* Ordinary segment. */
+
+      /* We set the vertical extent of the rectangle to span both the current-
+       * and the source-segments, and set the `water` and `source_water`
+       * pointers to point to two consecutive rows of the `water_buffer` array
+       * (the y-coordinate of the rectangle, and which row is above which,
+       * depends on whether the source segment is above, or below, the current
+       * one.)
+       */
+      if (ctx->segment.source_y_delta < 0)
+        {
+          iv_rect.y         = ctx->roi.y + ctx->segment.y - 1;
+          ctx->water        = ctx->water_buffer + ctx->roi.width;
+          ctx->source_water = ctx->water_buffer;
+        }
+      else
         {
-          if      (*src  > level) { level    = *src;               }
-          if      (*dest < level) { level    = *dest;              }
-          else if (level < *dest) { *dest    = level;
-                                    *changed = any_changed = TRUE; }
+          iv_rect.y         = ctx->roi.y + ctx->segment.y;
+          ctx->water        = ctx->water_buffer;
+          ctx->source_water = ctx->water_buffer + ctx->roi.width;
+        }
+      iv_rect.height        = 2;
+
+      /* Transform `iv_rect` to the image-physical coordinate system, and store
+       * the result in `ip_rect`.
+       */
+      gimp_operation_flood_process_transform_rect (ctx, &ip_rect, &iv_rect);
 
-          if (i > 1)
+      /* Read the water level from the output GEGL buffer into `water_buffer`.
+       *
+       * Notice the stride:  If the current segment is horizontal, then we're
+       * reading a pair of rows directly into the correct locations inside
+       * `water_buffer` (i.e., `water` and `source_water`).  On the other hand,
+       * if the current segment is vertical, then we're reading a pair of
+       * *columns*; we set the stride to 2-pixels so that the current- and
+       * source-water levels are interleaved in `water_buffer`, and reorder
+       * them below.
+       */
+      gegl_buffer_get (ctx->output, &ip_rect, 1.0, ctx->output_format,
+                       ctx->water_buffer + ctx->segment.x[0],
+                       sizeof (gfloat) *
+                       (ctx->segment.transpose ? 2 : ctx->roi.width),
+                       GEGL_ABYSS_NONE);
+
+      /* As mentioned above, if the current segment is vertical, then the
+       * water levels of the current- and source-segments are interleaved in
+       * `water_buffer`.  We deinterleave the water levels into `water` and
+       * `source_water`, using the yet-to-be-written-to `ground` array as a
+       * temporary buffer, as necessary.
+       */
+      if (ctx->segment.transpose)
+        {
+          const gfloat *src;
+          gfloat       *dest1, *dest2, *temp;
+          gint          size, temp_size;
+          gint          i;
+
+          src       = ctx->water_buffer + ctx->segment.x[0];
+
+          dest1     = ctx->water_buffer + ctx->segment.x[0];
+          dest2     = ctx->water_buffer + ctx->roi.width + ctx->segment.x[0];
+          temp      = ctx->ground;
+
+          size      = ctx->segment.x[1] - ctx->segment.x[0] + 1;
+          temp_size = MAX (0, 2 * size - ctx->roi.width);
+
+          for (i = 0; i < temp_size; i++)
             {
-              src     += dir;
-              dest    += dir;
-              changed += dir;
+              dest1[i] = src[2 * i];
+              temp[i]  = src[2 * i + 1];
+            }
+          for (; i < size; i++)
+            {
+              dest1[i] = src[2 * i];
+              dest2[i] = src[2 * i + 1];
+            }
+
+          memcpy (dest2, temp, sizeof (gfloat) * temp_size);
+        }
+    }
+  else
+    {
+      /* Seed segment. */
+
+      gint x;
+
+      /* Set the `water` and `source_water` pointers to point to consecutive
+       * rows of the `water_buffer` array.
+       */
+      ctx->water        = ctx->water_buffer;
+      ctx->source_water = ctx->water_buffer + ctx->roi.width;
+
+      /* Set the vertical extent of the rectangle to span a the current
+       * segment's row.
+       */
+      iv_rect.y      = ctx->roi.y + ctx->segment.y;
+      iv_rect.height = 1;
+
+      /* Transform `iv_rect` to the image-physical coordinate system, and store
+       * the result in `ip_rect`.
+       */
+      gimp_operation_flood_process_transform_rect (ctx, &ip_rect, &iv_rect);
+
+      /* Read the water level of the current segment from the output GEGL
+       * buffer into `water`.
+       */
+      gegl_buffer_get (ctx->output, &ip_rect, 1.0, ctx->output_format,
+                       ctx->water + ctx->segment.x[0],
+                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+      /* Initialize `source_water` to 0, as this is a seed segment. */
+      for (x = ctx->segment.x[0]; x <= ctx->segment.x[1]; x++)
+        ctx->source_water[x] = 0.0;
+    }
+
+  /* Set the vertical extent of the rectangle to span a the current segment's
+   * row.
+   */
+  iv_rect.y      = ctx->roi.y + ctx->segment.y;
+  iv_rect.height = 1;
+
+  /* Transform `iv_rect` to the image-physical coordinate system, and store the
+   * result in `ip_rect`.
+   */
+  gimp_operation_flood_process_transform_rect (ctx, &ip_rect, &iv_rect);
+
+  /* Read the ground level of the current segment from the input GEGL buffer
+   * into `ground`.
+   */
+  gegl_buffer_get (ctx->input, &ip_rect, 1.0, ctx->input_format,
+                   ctx->ground + ctx->segment.x[0],
+                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+}
+
+/* Performs the vertical propagation step of the algorithm.  Writes the dirty
+ * ranges to the `dirty_ranges` parameter, and returns the number of dirty
+ * ranges as the function's result.
+ */
+static gint
+gimp_operation_flood_process_propagate_vertical (GimpOperationFloodContext    *ctx,
+                                                 GimpOperationFloodDirtyRange *dirty_ranges)
+{
+  GimpOperationFloodDirtyRange *range = dirty_ranges;
+  gint                          x;
+
+  for (x = ctx->segment.x[0]; x <= ctx->segment.x[1]; x++)
+    {
+      /* Scan the segment until we find a pixel whose water level needs to be
+       * updated.
+       */
+      if (ctx->source_water[x] < ctx->water[x] &&
+          ctx->ground[x]       < ctx->water[x])
+        {
+          /* Compute and update the water level. */
+          gfloat level = MAX (ctx->source_water[x], ctx->ground[x]);
+
+          ctx->water[x] = level;
+
+          /* Start a new dirty range at the current pixel. */
+          range->x[0]     = x;
+          range->modified = FALSE;
+
+          for (x++; x <= ctx->segment.x[1]; x++)
+            {
+              /* Keep scanning the segment while the water level of consecutive
+               * pixels needs to be updated.
+               */
+              if (ctx->source_water[x] < ctx->water[x] &&
+                  ctx->ground[x]       < ctx->water[x])
+                {
+                  /* Compute and update the water level. */
+                  gfloat other_level = MAX (ctx->source_water[x],
+                                            ctx->ground[x]);
+
+                  ctx->water[x] = other_level;
+
+                  /* If the water level of the current pixel, `other_level`,
+                   * equals the water level of the current dirty range,
+                   * `level`, we keep scanning, making the current pixel part
+                   * of the current range.  On the other hand, if the current
+                   * pixel's water level is different than the that of the
+                   * current range, we finalize the range, and start a new one
+                   * at the current pixel.
+                   */
+                  if (other_level != level)
+                    {
+                      range->x[1]     = x - 1;
+                      range++;
+
+                      range->x[0]     = x;
+                      range->modified = FALSE;
+                      level           = other_level;
+                    }
+                }
+              else
+                break;
             }
+
+          /* Finalize the current dirty range. */
+          range->x[1] = x - 1;
+          range++;
+
+          /* Make sure we don't over-increment `x` on the continuation of the
+           * loop.
+           */
+          if (x > ctx->segment.x[1])
+            break;
         }
     }
 
-  return any_changed;
+  /* Return the number of dirty ranges. */
+  return range - dirty_ranges;
 }
 
+/* Performs a single pass of the horizontal propagation step of the algorithm.
+ * `dir` controls the direction of the pass: either +1 for a left-to-right
+ * pass, or -1 for a right-to-left pass.  The dirty ranges are passed through
+ * the `dirty_ranges` array (and their number in `range_count`), and are
+ * modified in-place.
+ */
+static void
+gimp_operation_flood_process_propagate_horizontal (GimpOperationFloodContext    *ctx,
+                                                   gint                          dir,
+                                                   GimpOperationFloodDirtyRange *dirty_ranges,
+                                                   gint                          range_count)
+{
+  /* The index of the terminal (i.e., "`dir`-most") component of the `x[]`
+   * array of `GimpOperationFloodSegment` and `GimpOperationFloodDirtyRange`,
+   * based on the scan direction.  Equals 1 (i.e., the right component) when
+   * `dir` is +1 (i.e., left-to-right), and equals 0 (i.e., the left component)
+   * when `dir` is -1 (i.e., right-to-left).
+   */
+  gint          x_component;
+  /* One-past the final x-coordinate of the ROI, in the ROI-virtual coordinate
+   * system, based on the scan direction.  That is, the x-coordinate of the
+   * pixel to the right of the rightmost pixel, for a left-to-right scan, and
+   * of the pixel to the left of the leftmost pixel, for a right-to-left scan.
+   */
+  gint          roi_lim;
+  /* One-past the final x-coordinate of the segment, in the ROI-virtual
+   * coordinate system, based on the scan direction, in a similar fashion to
+   * `roi_lim`.
+   */
+  gint          segment_lim;
+  /* The indices of the first, and one-past-the-last dirty ranges, based on the
+   * direction of the scan.  Recall that when scanning right-to-left, we
+   * iterate over the ranges in reverse.
+   */
+  gint          first_range, last_range;
+  /* Index of the current dirty range. */
+  gint          range_index;
+  /* Image-virtual and image-physical rectangles, respectively. */
+  GeglRectangle iv_rect, ip_rect;
+
+  /* Initialize the above variables based on the scan direction. */
+  if (dir > 0)
+    {
+      /* Left-to-right. */
+      x_component = 1;
+      roi_lim     = ctx->roi.width;
+      first_range = 0;
+      last_range  = range_count;
+    }
+  else
+    {
+      /* Right-to-left. */
+      x_component = 0;
+      roi_lim     = -1;
+      first_range = range_count - 1;
+      last_range  = -1;
+    }
+  segment_lim     = ctx->segment.x[x_component] + dir;
+
+  /* We loop over the dirty ranges, in the direction of the scan.  For each
+   * range, we iterate over the pixels, in the scan direction, starting at the
+   * outer edge of the range, and update the water level, considering only the
+   * water level of the previous and current pixels, until we arrive at a pixel
+   * whose water level remains the same, at which point we move to the next
+   * range, as described in the algorithm overview.
+   */
+  for (range_index  = first_range;
+       range_index != last_range;
+       range_index += dir)
+    {
+      /* Current dirty range. */
+      GimpOperationFloodDirtyRange *range;
+      /* Current pixel, in the ROI-virtual coordinate system. */
+      gint                          x;
+      /* We use `level` to compute the water level of the current pixel.  At
+       * the beginning of each iteration, it holds the water level of the
+       * previous pixel.
+       */
+      gfloat                        level;
+      /* The `inside` flag indicates whether `x` is inside the current segment.
+       * Recall that we may iterate past the bounds of the current segment, in
+       * which case we need to read the ground- and water-levels from the GEGL
+       * buffers directly, instead of the corresponding arrays.
+       */
+      gboolean                      inside;
+      /* Loop limit. */
+      gint                          lim;
+
+      range  = &dirty_ranges[range_index];
+      /* Last x-coordinate of the range, in the direction of the scan. */
+      x      = range->x[x_component];
+      /* We start iterating on the pixel after `x`; initialize `level` to the
+       * water level of the previous pixel.
+       */
+      level  = ctx->water[x];
+      /* The ranges produced by the vertical propagation step are all within
+       * the bounds of the segment; the horizontal propagation step may only
+       * extend them in the direction of the scan.  Therefore, on both passes
+       * of the horizontal propagation step, the last pixel of each range, in
+       * the direction of the scan, is initially inside the segment.
+       */
+      inside = TRUE;
+      /* If this isn't the last range, break the loop at the beginning of the
+       * next range.  Otherwise, break the loop at the edge of the ROI.
+       */
+      if (range_index + dir != last_range)
+        lim  = (range + dir)->x[1 - x_component];
+      else
+        lim  = roi_lim;
+
+      /* Loop over the pixels between the edge of the current range, and the
+       * beginning of the next range (or the edge of the ROI).
+       */
+      for (x += dir; x != lim; x += dir)
+        {
+          gfloat ground_level, water_level;
+
+          /* Recall that `segment_lim` is one-past the last pixel of the
+           * segment.  If we hit it, we've gone outside the segment bounds.
+           */
+          if (x == segment_lim)
+            {
+              inside         = FALSE;
+              /* Initialize the rectangle to sample pixels directly from the
+               * GEGL buffers.
+               */
+              iv_rect.y      = ctx->roi.y + ctx->segment.y;
+              iv_rect.width  = 1;
+              iv_rect.height = 1;
+            }
+
+          /* If we're inside the segment, read the ground- and water-levels
+           * from the corresponding arrays; otherwise, read them from the GEGL
+           * buffers directly.  Note that, on each pass, we may only write to
+           * pixels outside the segment *in direction of the scan* (in which
+           * case, the new values are written to the `water` array, but not
+           * directly to the output GEGL buffer), hence, when reading from the
+           * GEGL buffers, there's no danger of reading stale values, that were
+           * changed on the previous pass.
+           */
+          if (inside)
+            {
+              ground_level = ctx->ground[x];
+              water_level  = ctx->water[x];
+            }
+          else
+            {
+              iv_rect.x = ctx->roi.x + x;
+
+              /* Transform `iv_rect` to the image-physical coordinate system,
+               * and store the result in `ip_rect`.
+               */
+              gimp_operation_flood_process_transform_rect (ctx,
+                                                           &ip_rect, &iv_rect);
+
+              /* Read the current pixel's ground level. */
+              gegl_buffer_get (ctx->input, &ip_rect, 1.0, ctx->input_format,
+                               &ground_level,
+                               GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+              /* Read the current pixel's water level. */
+              gegl_buffer_get (ctx->output, &ip_rect, 1.0, ctx->output_format,
+                               &water_level,
+                               GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+            }
+
+          /* The new water level is the maximum of the curernt ground level,
+           * and the minimum of the current and previous water levels.  Recall
+           * that `level` holds the previous water level, and that the current
+           * water level is never less than the ground level.
+           */
+          if (level < ground_level)
+            level             = ground_level;
+          if (level < water_level)
+            {
+              /* The water level changed.  Update the current pixel, and set
+               * the `modified` flag of the current range, since it will be
+               * extended to include the current pixel.
+               */
+              ctx->water[x]   = level;
+              range->modified = TRUE;
+            }
+          else
+            /* The water level stayed the same.  Break the loop. */
+            break;
+        }
+
+      /* Extend the current dirty range to include the last modified pixel, if
+       * any.
+       */
+      range->x[x_component] = x - dir;
+
+      /* If we stopped the loop before hitting the edge of the next range, or
+       * if we're at the last range, continue to the next range (or quit).
+       */
+      if (x != lim || range_index + dir == last_range)
+        continue;
+
+      /* If we hit the edge of the next range, we keep propagating the changes
+       * *inside* the next range, until we hit its other edge, or until the
+       * water level stays the same.
+       */
+      range += dir;
+      lim    = range->x[x_component] + dir;
+
+      for (; x != lim; x += dir)
+        {
+          /* Note that we're necessarily inside the segment right now, since
+           * the only range that could have been extended past the edge of the
+           * segment by the previous pass, is the first range of the current
+           * pass, while the range we're currently inside is at least the
+           * second.
+           */
+          if (level < ctx->ground[x])
+            level             = ctx->ground[x];
+          if (level < ctx->water[x])
+            {
+              ctx->water[x]   = level;
+              /* Set the `modified` flag of the range, since the water level of
+               * its existing pixels changed.
+               */
+              range->modified = TRUE;
+            }
+          else
+            break;
+        }
+    }
+}
+
+/* Coalesces consecutive dirty ranges that are separated by a gap less-than or
+ * equal-to `max_gap`, in-place, and returns the new number of ranges.
+ */
+static gint
+gimp_operation_flood_process_coalesce (const GimpOperationFloodContext *ctx,
+                                       GimpOperationFloodDirtyRange    *dirty_ranges,
+                                       gint                             range_count,
+                                       gint                             max_gap)
+{
+  /* First and last ranges to coalesce, respectively. */
+  const GimpOperationFloodDirtyRange *first_range, *last_range;
+  /* Destination range. */
+  GimpOperationFloodDirtyRange       *range = dirty_ranges;
+
+  for (first_range  = dirty_ranges;
+       first_range != dirty_ranges + range_count;
+       first_range++)
+    {
+      /* The `modified` flag of the coalesced range -- the logical-OR of the
+       * `modified` flags of the individual ranges.
+       */
+      gboolean modified = first_range->modified;
+
+      /* Find all consecutive ranges with a small-enough gap. */
+      for (last_range      = first_range;
+           last_range + 1 != dirty_ranges + range_count;
+           last_range++)
+        {
+          if ((last_range + 1)->x[0] - last_range->x[1] > max_gap)
+            break;
+
+          modified |= (last_range + 1)->modified;
+        }
+
+      /* Write the coalesced range, or copy the current range, to the
+       * destination range.
+       */
+      if (first_range != last_range || first_range != range)
+        {
+          range->x[0]     = first_range->x[0];
+          range->x[1]     = last_range->x[1];
+          range->modified = modified;
+        }
+
+      first_range = last_range;
+      range++;
+    }
+
+  /* Return the new range count. */
+  return range - dirty_ranges;
+}
+
+/* Writes the updated water level of the dirty ranges back to the output GEGL
+ * buffer.
+ */
+static void
+gimp_operation_flood_process_commit (const GimpOperationFloodContext    *ctx,
+                                     const GimpOperationFloodDirtyRange *dirty_ranges,
+                                     gint                                range_count)
+{
+  const GimpOperationFloodDirtyRange *range;
+  /* Image-virtual and image-physical rectangles, respectively. */
+  GeglRectangle                       iv_rect, ip_rect;
+
+  /* Set the vertical extent of the rectangle to span a the current segment's
+   * row.
+   */
+  iv_rect.y      = ctx->roi.y + ctx->segment.y;
+  iv_rect.height = 1;
+
+  for (range = dirty_ranges; range != dirty_ranges + range_count; range++)
+    {
+      /* Set the horizontal extent of the rectangle to span the dirty range. */
+      iv_rect.x     = ctx->roi.x + range->x[0];
+      iv_rect.width = range->x[1] - range->x[0] + 1;
+
+      /* Transform `iv_rect` to the image-physical coordinate system, and store
+       * the result in `ip_rect`.
+       */
+      gimp_operation_flood_process_transform_rect (ctx, &ip_rect, &iv_rect);
+
+      /* Write the updated water level to the output GEGL buffer. */
+      gegl_buffer_set (ctx->output, &ip_rect, 0, ctx->output_format,
+                       ctx->water + range->x[0],
+                       GEGL_AUTO_ROWSTRIDE);
+    }
+}
+
+/* Pushes the new segments, corresponding to the dirty ranges of the current
+ * segment, into the queue.
+ */
+static void
+gimp_operation_flood_process_distribute (const GimpOperationFloodContext    *ctx,
+                                         GQueue                             *queue,
+                                         const GimpOperationFloodDirtyRange *dirty_ranges,
+                                         gint                                range_count)
+{
+  const GimpOperationFloodDirtyRange *range;
+  static const gint                   y_deltas[] = {-1, +1};
+  gint                                i;
+
+  /* For each neighboring row... */
+  for (i = 0; i < G_N_ELEMENTS (y_deltas); i++)
+    {
+      /* The difference between the negihboring row's y-coordinate and the
+       * current row's y-corindate, in the ROI-virtual coordinate system.
+       */
+      gint y_delta = y_deltas[i];
+      /* The negihboring row's y-coordinate in the ROI-virtual coordinate
+       * system.
+       */
+      gint y = ctx->segment.y + y_delta;
+
+      /* If the neighboring row is outside the ROI, skip it. */
+      if (y < 0 || y >= ctx->roi.height)
+        continue;
+
+      /* For each dirty range... */
+      for (range = dirty_ranges; range != dirty_ranges + range_count; range++)
+        {
+          /* If the range was modified during horizontal propagation, or if the
+           * neighboring row is not the source segment's row... (note that the
+           * latter is always true for seed segments.)
+           */
+          if (range->modified || y_delta != ctx->segment.source_y_delta)
+            {
+              /* Push a new segment into the queue, spanning the same pixels as
+               * the dirty range on the neighboring row, using the current row
+               * as its source segment.
+               */
+              gimp_operation_flood_process_push (queue,
+                                                 ctx->segment.transpose,
+                                                 y,
+                                                 -y_delta,
+                                                 range->x[0],
+                                                 range->x[1]);
+            }
+        }
+    }
+}
+
+/* Main algorithm. */
 static gboolean
 gimp_operation_flood_process (GeglOperation       *operation,
                               GeglBuffer          *input,
@@ -147,94 +1341,111 @@ gimp_operation_flood_process (GeglOperation       *operation,
                               const GeglRectangle *roi,
                               gint                 level)
 {
-  const Babl *input_format  = babl_format ("Y float");
-  const Babl *output_format = babl_format ("Y float");
-  gfloat     *src_buffer, *dest_buffer;
-  gchar      *horz_changed, *vert_changed;
-  gboolean    any_changed;
-  gint        forced;
-  gboolean    horz;
-  GeglColor  *color;
+  const Babl                   *input_format  = babl_format ("Y float");
+  const Babl                   *output_format = babl_format ("Y float");
+  GeglColor                    *color;
+  gint                          max_size;
+  GimpOperationFloodContext     ctx;
+  GimpOperationFloodDirtyRange *dirty_ranges;
+  GQueue                       *queue;
 
+  /* Make sure the input- and output-buffers are different. */
   g_return_val_if_fail (input != output, FALSE);
 
-  src_buffer   = g_new (gfloat, MAX (roi->width, roi->height));
-  dest_buffer  = g_new (gfloat, MAX (roi->width, roi->height));
-  horz_changed = g_new (gchar, roi->width);
-  vert_changed = g_new (gchar, roi->height);
+  /* Make sure the ROI is small enough for the `GimpOperationFloodSegment::y`
+   * field.
+   */
+  g_return_val_if_fail (roi->width  <= GIMP_MAX_IMAGE_SIZE &&
+                        roi->height <= GIMP_MAX_IMAGE_SIZE, FALSE);
+
+  ctx.input         = input;
+  ctx.input_format  = input_format;
+  ctx.output        = output;
+  ctx.output_format = output_format;
+
+  /* All buffers need to have enough capacity to process a full row, or a full
+   * column, since, when processing vertical segments, we treat the image as
+   * transposed.
+   */
+  max_size          = MAX (roi->width, roi->height);
+  ctx.ground        = g_new (gfloat, max_size);
+  /* The `water_buffer` array needs to be able to hold two rows (or columns). */
+  ctx.water_buffer  = g_new (gfloat, 2 * max_size);
+  dirty_ranges      = g_new (GimpOperationFloodDirtyRange, max_size);
 
+  /* Initialize the water level to 1 everywhere. */
   color = gegl_color_new ("#fff");
   gegl_buffer_set_color (output, roi, color);
   g_object_unref (color);
 
-  forced = 2; /*  Force at least one horizontal and one vertical iterations.  */
-  horz = TRUE;
-  do
-    {
-      gint           count, size;
-      GeglRectangle  rect = *roi;
-      gint          *coord;
-      gchar         *prev_changed, *curr_changed;
-      gint           i;
+  /* Create the queue and push the seed segments. */
+  queue  = g_queue_new ();
+  gimp_operation_flood_process_seed (queue, roi);
 
-      any_changed = FALSE;
+  /* While there are segments to process in the queue... */
+  while (! g_queue_is_empty (queue))
+    {
+      GimpOperationFloodSegment *segment;
+      gint                       range_count;
 
-      if (horz)
-        {
-          count        = roi->height;
-          size         = roi->width;
-          rect.height  = 1;
-          coord        = &rect.y;
-          prev_changed = vert_changed;
-          curr_changed = horz_changed;
-        }
-      else
-        {
-          count        = roi->width;
-          size         = roi->height;
-          rect.width   = 1;
-          coord        = &rect.x;
-          prev_changed = horz_changed;
-          curr_changed = vert_changed;
-        }
+      /* Pop a segment off the top of the queue, copy it to `ctx.segment`, and
+       * free its memory.
+       */
+      segment     = (GimpOperationFloodSegment *) g_queue_pop_head (queue);
+      ctx.segment = *segment;
+      g_slice_free (GimpOperationFloodSegment, segment);
 
-      for (i = 0; i < count; i++, (*coord)++)
-        {
-          gboolean this_changed;
+      /* Transform the ROI from the image-physical coordinate system to the
+       * image-virtual coordinate system, and store the result in `ctx.roi`.
+       */
+      gimp_operation_flood_process_transform_rect (&ctx, &ctx.roi, roi);
 
-          if (! (forced || prev_changed[i]))
-            continue;
-          prev_changed[i] = FALSE;
+      /* Read the ground- and water-levels of the current- and source-segments
+       * from the corresponding GEGL buffers to the corresponding arrays.
+       */
+      gimp_operation_flood_process_fetch (&ctx);
 
-          gegl_buffer_get (input, &rect, 1.0, input_format, src_buffer,
-                           GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
-          gegl_buffer_get (output, &rect, 1.0, output_format, dest_buffer,
-                           GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+      /* Perform the vertical propagation step. */
+      range_count = gimp_operation_flood_process_propagate_vertical (&ctx,
+                                                                     dirty_ranges);
+      /* If no dirty ranges were produced during vertical propagation, then the
+       * water level of the current segment didn't change, and we can short-
+       * circuit early.
+       */
+      if (range_count == 0)
+        continue;
 
-          this_changed = gimp_operation_flood_process_segment (src_buffer,
-                                                               dest_buffer,
-                                                               curr_changed,
-                                                               size);
+      /* Perform both passes of the horizontal propagation step. */
+      gimp_operation_flood_process_propagate_horizontal (&ctx,
+                                                         /* Left-to-right */ +1,
+                                                         dirty_ranges,
+                                                         range_count);
+      gimp_operation_flood_process_propagate_horizontal (&ctx,
+                                                         /* Right-to-left */ -1,
+                                                         dirty_ranges,
+                                                         range_count);
 
-          if (this_changed)
-            {
-              gegl_buffer_set (output, &rect, 0, output_format, dest_buffer,
-                               GEGL_AUTO_ROWSTRIDE);
+      /* Coalesce consecutive dirty ranges separated by a gap less-than or
+       * equal-to `GIMP_OPERATION_FLOOD_COALESCE_MAX_GAP`.
+       */
+      range_count = gimp_operation_flood_process_coalesce (&ctx,
+                                                           dirty_ranges,
+                                                           range_count,
+                                                           GIMP_OPERATION_FLOOD_COALESCE_MAX_GAP);
 
-              any_changed = TRUE;
-            }
-        }
+      /* Write the updated water level back to the output GEGL buffer. */
+      gimp_operation_flood_process_commit (&ctx, dirty_ranges, range_count);
 
-      horz = ! horz;
-      if (forced)
-        forced--;
+      /* Push the new segments into the queue. */
+      gimp_operation_flood_process_distribute (&ctx, queue,
+                                               dirty_ranges, range_count);
     }
-  while (forced || any_changed);
 
-  g_free (src_buffer);
-  g_free (dest_buffer);
-  g_free (horz_changed);
-  g_free (vert_changed);
+  g_queue_free (queue);
+
+  g_free (dirty_ranges);
+  g_free (ctx.water_buffer);
+  g_free (ctx.ground);
 
   return TRUE;
 }


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