1109 lines
45 KiB
C
1109 lines
45 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikaoperationflood.c
|
|
* Copyright (C) 2016 Ell
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
/* Implementation of the Flood algorithm.
|
|
* See https://wiki.pika.org/wiki/Algorithms:Flood for details.
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h> /* For `memcpy()`. */
|
|
|
|
#include <cairo.h>
|
|
#include <gegl.h>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
|
|
#include "operations-types.h"
|
|
|
|
#include "pikaoperationflood.h"
|
|
|
|
|
|
/* Maximal gap, in pixels, between consecutive dirty ranges, below (and
|
|
* including) which they are coalesced, at the beginning of the distribution
|
|
* step.
|
|
*/
|
|
#define PIKA_OPERATION_FLOOD_COALESCE_MAX_GAP 32
|
|
|
|
|
|
typedef struct _PikaOperationFloodSegment PikaOperationFloodSegment;
|
|
typedef struct _PikaOperationFloodDirtyRange PikaOperationFloodDirtyRange;
|
|
typedef struct _PikaOperationFloodContext PikaOperationFloodContext;
|
|
|
|
|
|
/* A segment. */
|
|
struct _PikaOperationFloodSegment
|
|
{
|
|
/* 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
|
|
* `PikaOperationFloodSegment::y`.
|
|
*/
|
|
G_STATIC_ASSERT (PIKA_MAX_IMAGE_SIZE <= (1 << (8 * sizeof (guint) - 3)));
|
|
|
|
/* A dirty range of the current segment. */
|
|
struct _PikaOperationFloodDirtyRange
|
|
{
|
|
/* 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 _PikaOperationFloodContext
|
|
{
|
|
/* Input image. */
|
|
GeglBuffer *input;
|
|
/* Input image format. */
|
|
const Babl *input_format;
|
|
/* Output image. */
|
|
GeglBuffer *output;
|
|
/* Output image format. */
|
|
const Babl *output_format;
|
|
|
|
/* Region of interset. */
|
|
GeglRectangle roi;
|
|
|
|
/* Current segment. */
|
|
PikaOperationFloodSegment 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 pika_operation_flood_prepare (GeglOperation *operation);
|
|
static GeglRectangle pika_operation_flood_get_required_for_output (GeglOperation *self,
|
|
const gchar *input_pad,
|
|
const GeglRectangle *roi);
|
|
static GeglRectangle pika_operation_flood_get_cached_region (GeglOperation *self,
|
|
const GeglRectangle *roi);
|
|
|
|
static void pika_operation_flood_process_push (GQueue *queue,
|
|
gboolean transpose,
|
|
gint y,
|
|
gint source_y_delta,
|
|
gint x0,
|
|
gint x1);
|
|
static void pika_operation_flood_process_seed (GQueue *queue,
|
|
const GeglRectangle *roi);
|
|
static void pika_operation_flood_process_transform_rect (const PikaOperationFloodContext *ctx,
|
|
GeglRectangle *dest,
|
|
const GeglRectangle *src);
|
|
static void pika_operation_flood_process_fetch (PikaOperationFloodContext *ctx);
|
|
static gint pika_operation_flood_process_propagate_vertical (PikaOperationFloodContext *ctx,
|
|
PikaOperationFloodDirtyRange *dirty_ranges);
|
|
static void pika_operation_flood_process_propagate_horizontal (PikaOperationFloodContext *ctx,
|
|
gint dir,
|
|
PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count);
|
|
static gint pika_operation_flood_process_coalesce (const PikaOperationFloodContext *ctx,
|
|
PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count,
|
|
gint gap);
|
|
static void pika_operation_flood_process_commit (const PikaOperationFloodContext *ctx,
|
|
const PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count);
|
|
static void pika_operation_flood_process_distribute (const PikaOperationFloodContext *ctx,
|
|
GQueue *queue,
|
|
const PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count);
|
|
static gboolean pika_operation_flood_process (GeglOperation *operation,
|
|
GeglBuffer *input,
|
|
GeglBuffer *output,
|
|
const GeglRectangle *roi,
|
|
gint level);
|
|
|
|
|
|
G_DEFINE_TYPE (PikaOperationFlood, pika_operation_flood,
|
|
GEGL_TYPE_OPERATION_FILTER)
|
|
|
|
#define parent_class pika_operation_flood_parent_class
|
|
|
|
|
|
/* GEGL graph for the test case. */
|
|
static const gchar* reference_xml = "<?xml version='1.0' encoding='UTF-8'?>"
|
|
"<gegl>"
|
|
"<node operation='pika:flood'> </node>"
|
|
"<node operation='gegl:load'>"
|
|
" <params>"
|
|
" <param name='path'>flood-input.png</param>"
|
|
" </params>"
|
|
"</node>"
|
|
"</gegl>";
|
|
|
|
|
|
static void
|
|
pika_operation_flood_class_init (PikaOperationFloodClass *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", "pika:flood",
|
|
"categories", "pika",
|
|
"description", "PIKA Flood operation",
|
|
"reference", "https://wiki.pika.org/wiki/Algorithms:Flood",
|
|
"reference-image", "flood-output.png",
|
|
"reference-composition", reference_xml,
|
|
NULL);
|
|
|
|
operation_class->prepare = pika_operation_flood_prepare;
|
|
operation_class->get_required_for_output = pika_operation_flood_get_required_for_output;
|
|
operation_class->get_cached_region = pika_operation_flood_get_cached_region;
|
|
|
|
filter_class->process = pika_operation_flood_process;
|
|
}
|
|
|
|
static void
|
|
pika_operation_flood_init (PikaOperationFlood *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
pika_operation_flood_prepare (GeglOperation *operation)
|
|
{
|
|
const Babl *space = gegl_operation_get_source_space (operation, "input");
|
|
gegl_operation_set_format (operation, "input", babl_format_with_space ("Y float", space));
|
|
gegl_operation_set_format (operation, "output", babl_format_with_space ("Y float", space));
|
|
}
|
|
|
|
static GeglRectangle
|
|
pika_operation_flood_get_required_for_output (GeglOperation *self,
|
|
const gchar *input_pad,
|
|
const GeglRectangle *roi)
|
|
{
|
|
return *gegl_operation_source_get_bounding_box (self, "input");
|
|
}
|
|
|
|
static GeglRectangle
|
|
pika_operation_flood_get_cached_region (GeglOperation *self,
|
|
const GeglRectangle *roi)
|
|
{
|
|
return *gegl_operation_source_get_bounding_box (self, "input");
|
|
}
|
|
|
|
|
|
/* Pushes a single segment into the queue. */
|
|
static void
|
|
pika_operation_flood_process_push (GQueue *queue,
|
|
gboolean transpose,
|
|
gint y,
|
|
gint source_y_delta,
|
|
gint x0,
|
|
gint x1)
|
|
{
|
|
PikaOperationFloodSegment *segment;
|
|
|
|
segment = g_slice_new (PikaOperationFloodSegment);
|
|
|
|
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
|
|
pika_operation_flood_process_seed (GQueue *queue,
|
|
const GeglRectangle *roi)
|
|
{
|
|
if (roi->width == 0 || roi->height == 0)
|
|
return;
|
|
|
|
/* Top edge. */
|
|
pika_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. */
|
|
pika_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. */
|
|
pika_operation_flood_process_push (queue,
|
|
/* transpose = */ TRUE,
|
|
/* y = */ 0,
|
|
/* source_y_delta = */ 0,
|
|
/* x0 = */ 1,
|
|
/* x1 = */ roi->height - 2);
|
|
|
|
if (roi->width == 1)
|
|
return;
|
|
|
|
/* Right edge. */
|
|
pika_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
|
|
pika_operation_flood_process_transform_rect (const PikaOperationFloodContext *ctx,
|
|
GeglRectangle *dest,
|
|
const GeglRectangle *src)
|
|
{
|
|
if (! ctx->segment.transpose)
|
|
*dest = *src;
|
|
else
|
|
{
|
|
gint temp;
|
|
|
|
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
|
|
pika_operation_flood_process_fetch (PikaOperationFloodContext *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
|
|
{
|
|
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`.
|
|
*/
|
|
pika_operation_flood_process_transform_rect (ctx, &ip_rect, &iv_rect);
|
|
|
|
/* 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++)
|
|
{
|
|
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`.
|
|
*/
|
|
pika_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`.
|
|
*/
|
|
pika_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
|
|
pika_operation_flood_process_propagate_vertical (PikaOperationFloodContext *ctx,
|
|
PikaOperationFloodDirtyRange *dirty_ranges)
|
|
{
|
|
PikaOperationFloodDirtyRange *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 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
|
|
pika_operation_flood_process_propagate_horizontal (PikaOperationFloodContext *ctx,
|
|
gint dir,
|
|
PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count)
|
|
{
|
|
/* The index of the terminal (i.e., "`dir`-most") component of the `x[]`
|
|
* array of `PikaOperationFloodSegment` and `PikaOperationFloodDirtyRange`,
|
|
* 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. */
|
|
PikaOperationFloodDirtyRange *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`.
|
|
*/
|
|
pika_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 current 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
|
|
pika_operation_flood_process_coalesce (const PikaOperationFloodContext *ctx,
|
|
PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count,
|
|
gint max_gap)
|
|
{
|
|
/* First and last ranges to coalesce, respectively. */
|
|
const PikaOperationFloodDirtyRange *first_range, *last_range;
|
|
/* Destination range. */
|
|
PikaOperationFloodDirtyRange *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
|
|
pika_operation_flood_process_commit (const PikaOperationFloodContext *ctx,
|
|
const PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count)
|
|
{
|
|
const PikaOperationFloodDirtyRange *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`.
|
|
*/
|
|
pika_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
|
|
pika_operation_flood_process_distribute (const PikaOperationFloodContext *ctx,
|
|
GQueue *queue,
|
|
const PikaOperationFloodDirtyRange *dirty_ranges,
|
|
gint range_count)
|
|
{
|
|
const PikaOperationFloodDirtyRange *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.
|
|
*/
|
|
pika_operation_flood_process_push (queue,
|
|
ctx->segment.transpose,
|
|
y,
|
|
-y_delta,
|
|
range->x[0],
|
|
range->x[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Main algorithm. */
|
|
static gboolean
|
|
pika_operation_flood_process (GeglOperation *operation,
|
|
GeglBuffer *input,
|
|
GeglBuffer *output,
|
|
const GeglRectangle *roi,
|
|
gint level)
|
|
{
|
|
const Babl *input_format = gegl_operation_get_format (operation, "input");
|
|
const Babl *output_format = gegl_operation_get_format (operation, "output");
|
|
GeglColor *color;
|
|
gint max_size;
|
|
PikaOperationFloodContext ctx;
|
|
PikaOperationFloodDirtyRange *dirty_ranges;
|
|
GQueue *queue;
|
|
|
|
/* Make sure the input- and output-buffers are different. */
|
|
g_return_val_if_fail (input != output, FALSE);
|
|
|
|
/* Make sure the ROI is small enough for the `PikaOperationFloodSegment::y`
|
|
* field.
|
|
*/
|
|
g_return_val_if_fail (roi->width <= PIKA_MAX_IMAGE_SIZE &&
|
|
roi->height <= PIKA_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 (PikaOperationFloodDirtyRange, 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);
|
|
|
|
/* Create the queue and push the seed segments. */
|
|
queue = g_queue_new ();
|
|
pika_operation_flood_process_seed (queue, roi);
|
|
|
|
/* While there are segments to process in the queue... */
|
|
while (! g_queue_is_empty (queue))
|
|
{
|
|
PikaOperationFloodSegment *segment;
|
|
gint range_count;
|
|
|
|
/* Pop a segment off the top of the queue, copy it to `ctx.segment`, and
|
|
* free its memory.
|
|
*/
|
|
segment = (PikaOperationFloodSegment *) g_queue_pop_head (queue);
|
|
ctx.segment = *segment;
|
|
g_slice_free (PikaOperationFloodSegment, segment);
|
|
|
|
/* Transform the ROI from the image-physical coordinate system to the
|
|
* image-virtual coordinate system, and store the result in `ctx.roi`.
|
|
*/
|
|
pika_operation_flood_process_transform_rect (&ctx, &ctx.roi, roi);
|
|
|
|
/* Read the ground- and water-levels of the current- and source-segments
|
|
* from the corresponding GEGL buffers to the corresponding arrays.
|
|
*/
|
|
pika_operation_flood_process_fetch (&ctx);
|
|
|
|
/* Perform the vertical propagation step. */
|
|
range_count = pika_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;
|
|
|
|
/* Perform both passes of the horizontal propagation step. */
|
|
pika_operation_flood_process_propagate_horizontal (&ctx,
|
|
/* Left-to-right */ +1,
|
|
dirty_ranges,
|
|
range_count);
|
|
pika_operation_flood_process_propagate_horizontal (&ctx,
|
|
/* Right-to-left */ -1,
|
|
dirty_ranges,
|
|
range_count);
|
|
|
|
/* Coalesce consecutive dirty ranges separated by a gap less-than or
|
|
* equal-to `PIKA_OPERATION_FLOOD_COALESCE_MAX_GAP`.
|
|
*/
|
|
range_count = pika_operation_flood_process_coalesce (&ctx,
|
|
dirty_ranges,
|
|
range_count,
|
|
PIKA_OPERATION_FLOOD_COALESCE_MAX_GAP);
|
|
|
|
/* Write the updated water level back to the output GEGL buffer. */
|
|
pika_operation_flood_process_commit (&ctx, dirty_ranges, range_count);
|
|
|
|
/* Push the new segments into the queue. */
|
|
pika_operation_flood_process_distribute (&ctx, queue,
|
|
dirty_ranges, range_count);
|
|
}
|
|
|
|
g_queue_free (queue);
|
|
|
|
g_free (dirty_ranges);
|
|
g_free (ctx.water_buffer);
|
|
g_free (ctx.ground);
|
|
|
|
return TRUE;
|
|
}
|