Commit a11dd0e2 authored by Ell's avatar Ell Committed by Michael Natterer

Bug 761795 - Performance improvement for the flood operation

Improve the performance of the "gimp:flood" operation and add plenty
of comments.
parent a28964c6
......@@ -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; }