Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gimp/app/core/   (Gimp Bildeditor Version 3.2.0©)  Datei vom 14.2.2026 mit Größe 42 kB image not shown  

Quelle  gimppickable-contiguous-region.cc   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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/>.
 */


#include "config.h"

#include <stdlib.h>

#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"

extern "C"
{

#include "core-types.h"

#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-utils.h"

#include "gimp-parallel.h"
#include "gimp-utils.h" /* GIMP_TIMER */
#include "gimpasync.h"
#include "gimplineart.h"
#include "gimppickable.h"
#include "gimppickable-contiguous-region.h"


#define EPSILON 1e-6

#define PIXELS_PER_THREAD \
  (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)


typedef struct
{
  gint   x;
  gint   y;
  gint   level;
} BorderPixel;


/*  local function prototypes  */

static const Babl * choose_format         (GeglBuffer          *buffer,
                                           GimpSelectCriterion  select_criterion,
                                           gint                *n_components,
                                           gboolean            *has_alpha);
static gfloat   pixel_difference          (const gfloat        *col1,
                                           const gfloat        *col2,
                                           gboolean             antialias,
                                           gfloat               threshold,
                                           gint                 n_components,
                                           gboolean             has_alpha,
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion);
static void     push_segment              (GQueue              *segment_queue,
                                           gint                 y,
                                           gint                 old_y,
                                           gint                 start,
                                           gint                 end,
                                           gint                 new_y,
                                           gint                 new_start,
                                           gint                 new_end);
static void     pop_segment               (GQueue              *segment_queue,
                                           gint                *y,
                                           gint                *old_y,
                                           gint                *start,
                                           gint                *end);
static gboolean find_contiguous_segment   (const gfloat        *col,
                                           GeglBuffer          *src_buffer,
                                           GeglSampler         *src_sampler,
                                           const GeglRectangle *src_extent,
                                           GeglBuffer          *mask_buffer,
                                           const Babl          *src_format,
                                           const Babl          *mask_format,
                                           gint                 n_components,
                                           gboolean             has_alpha,
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion,
                                           gboolean             antialias,
                                           gfloat               threshold,
                                           gint                 initial_x,
                                           gint                 initial_y,
                                           gint                *start,
                                           gint                *end,
                                           gfloat              *row);
static void     find_contiguous_region    (GeglBuffer          *src_buffer,
                                           GeglBuffer          *mask_buffer,
                                           const Babl          *format,
                                           gint                 n_components,
                                           gboolean             has_alpha,
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion,
                                           gboolean             antialias,
                                           gfloat               threshold,
                                           gboolean             diagonal_neighbors,
                                           gint                 x,
                                           gint                 y,
                                           const gfloat        *col);

static void            line_art_queue_pixel (GQueue              *queue,
                                             gint                 x,
                                             gint                 y,
                                             gint                 level);


/*  public functions  */

GeglBuffer *
gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
                                         gboolean             antialias,
                                         gfloat               threshold,
                                         gboolean             select_transparent,
                                         GimpSelectCriterion  select_criterion,
                                         gboolean             diagonal_neighbors,
                                         gint                 x,
                                         gint                 y)
{
  GeglBuffer    *src_buffer = NULL;
  GeglBuffer    *mask_buffer;
  const Babl    *format;
  GeglRectangle  extent;
  gint           n_components;
  gboolean       has_alpha;
  gfloat         start_col[MAX_CHANNELS];

  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);

  gimp_pickable_flush (pickable);
  src_buffer = gimp_pickable_get_buffer_with_effects (pickable);

  format = choose_format (src_buffer, select_criterion,
                          &n_components, &has_alpha);
  gegl_buffer_sample (src_buffer, x, y, NULL, start_col, format,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);

  if (has_alpha)
    {
      if (select_transparent)
        {
          /*  don't select transparent regions if the start pixel isn't
           *  fully transparent
           */

          if (start_col[n_components - 1] > 0)
            select_transparent = FALSE;
        }
    }
  else
    {
      select_transparent = FALSE;
    }

  extent = *gegl_buffer_get_extent (src_buffer);

  mask_buffer = gegl_buffer_new (&extent, babl_format ("Y float"));

  if (x >= extent.x && x < (extent.x + extent.width) &&
      y >= extent.y && y < (extent.y + extent.height))
    {
      GIMP_TIMER_START();

      find_contiguous_region (src_buffer, mask_buffer,
                              format, n_components, has_alpha,
                              select_transparent, select_criterion,
                              antialias, threshold, diagonal_neighbors,
                              x, y, start_col);

      GIMP_TIMER_END("foo");
    }
  g_clear_object (&src_buffer);

  return mask_buffer;
}

GeglBuffer *
gimp_pickable_contiguous_region_by_color (GimpPickable        *pickable,
                                          gboolean             antialias,
                                          gfloat               threshold,
                                          gboolean             select_transparent,
                                          GimpSelectCriterion  select_criterion,
                                          GeglColor           *color)
{
  /*  Scan over the pickable's active layer, finding pixels within the
   *  specified threshold from the given R, G, & B values.  If
   *  antialiasing is on, use the same antialiasing scheme as in
   *  fuzzy_select.  Modify the pickable's mask to reflect the
   *  additional selection
   */

  GeglBuffer *src_buffer = NULL;
  GeglBuffer *mask_buffer;
  const Babl *format;
  gint        n_components;
  gboolean    has_alpha;
  gfloat      start_col[MAX_CHANNELS];

  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
  g_return_val_if_fail (GEGL_IS_COLOR (color), NULL);

  /* increase the threshold by EPSILON, to allow for conversion errors,
   * especially when threshold == 0 (see issue #1554.)  we need to do this
   * here, but not in the other functions, since the input color gets converted
   * to the format in which we perform the comparison through a different path
   * than the pickable's pixels, which can introduce error.
   */

  threshold += EPSILON;

  gimp_pickable_flush (pickable);

  src_buffer = gimp_pickable_get_buffer_with_effects (pickable);

  format = choose_format (src_buffer, select_criterion,
                          &n_components, &has_alpha);

  gegl_color_get_pixel (color, format, start_col);

  if (has_alpha)
    {
      if (select_transparent)
        {
          /*  don't select transparency if "color" isn't fully transparent
           */

          if (start_col[n_components - 1] > 0.0)
            select_transparent = FALSE;
        }
    }
  else
    {
      select_transparent = FALSE;
    }

  mask_buffer = gegl_buffer_new (gegl_buffer_get_extent (src_buffer),
                                 babl_format ("Y float"));

  gegl_parallel_distribute_area (
    gegl_buffer_get_extent (src_buffer), PIXELS_PER_THREAD,
    [=] (const GeglRectangle *area)
    {
      GeglBufferIterator *iter;

      iter = gegl_buffer_iterator_new (src_buffer,
                                       area, 0, format,
                                       GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);

      gegl_buffer_iterator_add (iter, mask_buffer,
                                area, 0, babl_format ("Y float"),
                                GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);

      while (gegl_buffer_iterator_next (iter))
        {
          const gfloat *src   = (const gfloat *) iter->items[0].data;
          gfloat       *dest  = (      gfloat *) iter->items[1].data;
          gint          count = iter->length;

          while (count--)
            {
              /*  Find how closely the colors match  */
              *dest = pixel_difference (start_col, src,
                                        antialias,
                                        threshold,
                                        n_components,
                                        has_alpha,
                                        select_transparent,
                                        select_criterion);

              src  += n_components;
              dest += 1;
            }
        }
    });
  g_clear_object (&src_buffer);

  return mask_buffer;
}

GeglBuffer *
gimp_pickable_contiguous_region_by_line_art (GimpPickable  *pickable,
                                             GimpLineArt   *line_art,
                                             GeglBuffer    *fill_buffer,
                                             GeglColor     *fill_color,
                                             gfloat         fill_threshold,
                                             gint           fill_offset_x,
                                             gint           fill_offset_y,
                                             gint           x,
                                             gint           y)
{
  GeglBuffer    *src_buffer;
  GeglBuffer    *mask_buffer;
  const Babl    *format  = babl_format ("Y float");
  gfloat        *distmap = NULL;
  GeglRectangle  extent;
  gboolean       free_line_art   = FALSE;
  gboolean       free_src_buffer = FALSE;
  gboolean       filled          = FALSE;
  guchar         start_col;

  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable) || GIMP_IS_LINE_ART (line_art), NULL);

  if (! line_art)
    {
      /* It is much better experience to pre-compute the line art,
       * but it may not be always possible (for instance when
       * selecting/filling through a PDB call).
       */

      line_art = gimp_line_art_new ();
      gimp_line_art_set_input (line_art, pickable);
      free_line_art = TRUE;
    }

  src_buffer = gimp_line_art_get (line_art, &distmap);
  g_return_val_if_fail (src_buffer && distmap, NULL);

  if (fill_buffer != NULL)
    {
      GeglBufferIterator  *gi;
      const Babl          *fill_format;
      const GeglRectangle *fill_extent;
      gfloat               fill_col[MAX_CHANNELS];
      gboolean             has_alpha;
      gint                 n_components;

      fill_extent = gegl_buffer_get_extent (fill_buffer);
      fill_format = choose_format (fill_buffer,
                                   GIMP_SELECT_CRITERION_COMPOSITE,
                                   &n_components, &has_alpha);
      fill_format = babl_format_with_space (babl_format_get_encoding (fill_format),
                                            gegl_buffer_get_format (fill_buffer));
      gegl_color_get_pixel (fill_color, fill_format, fill_col);

      src_buffer = gimp_gegl_buffer_dup (src_buffer);
      gi = gegl_buffer_iterator_new (src_buffer, NULL, 0, NULL,
                                     GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 2);
      gegl_buffer_iterator_add (gi, fill_buffer,
                                GEGL_RECTANGLE (-fill_offset_x, -fill_offset_y,
                                                fill_extent->width + fill_offset_x,
                                                fill_extent->height + fill_offset_y),
                                0, fill_format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
      while (gegl_buffer_iterator_next (gi))
        {
          guchar       *data = (guchar*) gi->items[0].data;
          const gfloat *fill = (const gfloat *) gi->items[1].data;
          gint          k;

          for (k = 0; k < gi->length; k++)
            {
              /* Only consider if the line art source hasn't filled yet,
               * and also if this the fill target has full opacity.
               */

              if (! *data &&
                  ( ! has_alpha ||
                   fill[n_components - 1] == 1.0))
                {
                  gfloat diff;

                  diff = pixel_difference (fill, fill_col,
                                           FALSE,
                                           fill_threshold,
                                           n_components, has_alpha, FALSE,
                                           GIMP_SELECT_CRITERION_COMPOSITE);

                  /* Make the additional fill pixel a closure pixel. */
                  if (diff == 1.0)
                    *data = 2;
                }

              data++;
              fill += n_components;
            }
        }

      free_src_buffer = TRUE;
    }

  gegl_buffer_sample (src_buffer, x, y, NULL, &start_col, NULL,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);

  extent = *gegl_buffer_get_extent (src_buffer);

  mask_buffer = gegl_buffer_new (&extent, format);

  if (start_col)
    {
      if (start_col == 1)
        {
          /* As a special exception, if you fill over a line art pixel, only
           * fill the pixel and exit
           */

          gfloat col = 1.0;

          gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (x, y, 1, 1),
                           0, format, &col, GEGL_AUTO_ROWSTRIDE);
        }
      else /* start_col == 2 */
        {
          /* If you fill over a closure pixel, let's fill on all sides
           * of the start point. Otherwise we get a very weird result
           * with only a single pixel filled in the middle of an empty
           * region (since closure pixels are invisible by nature).
           */

          gfloat col = 0.0;

          if (x - 1 >= extent.x && x - 1 < extent.x + extent.width &&
              y - 1 >= extent.y && y - 1 < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x - 1, y - 1, &col);

          if (x - 1 >= extent.x && x - 1 < extent.x + extent.width &&
              y >= extent.y && y < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x - 1, y, &col);

          if (x - 1 >= extent.x && x - 1 < extent.x + extent.width &&
              y + 1 >= extent.y && y + 1 < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x - 1, y + 1, &col);

          if (x >= extent.x && x < extent.x + extent.width &&
              y - 1 >= extent.y && y - 1 < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x, y - 1, &col);

          if (x >= extent.x && x < extent.x + extent.width &&
              y + 1 >= extent.y && y + 1 < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x, y + 1, &col);

          if (x + 1 >= extent.x && x + 1 < extent.x + extent.width &&
              y - 1 >= extent.y && y - 1 < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x + 1, y - 1, &col);

          if (x + 1 >= extent.x && x + 1 < extent.x + extent.width &&
              y >= extent.y && y < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x + 1, y, &col);

          if (x + 1 >= extent.x && x + 1 < extent.x + extent.width &&
              y + 1 >= extent.y && y + 1 < (extent.y + extent.height))
            find_contiguous_region (src_buffer, mask_buffer,
                                    format, 1, FALSE,
                                    FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                                    FALSE, 0.0, FALSE,
                                    x + 1, y + 1, &col);

          filled = TRUE;
        }
    }
  else if (x >= extent.x && x < (extent.x + extent.width) &&
           y >= extent.y && y < (extent.y + extent.height))
    {
      gfloat col = 0.0;

      find_contiguous_region (src_buffer, mask_buffer,
                              format, 1, FALSE,
                              FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
                              FALSE, 0.0, FALSE,
                              x, y, &col);
      filled = TRUE;
    }

  if (filled)
    {
      GQueue *queue  = g_queue_new ();
      gfloat *mask;
      gint    width  = gegl_buffer_get_width (src_buffer);
      gint    height = gegl_buffer_get_height (src_buffer);
      gint    line_art_max_grow;
      gint    nx, ny;

      GIMP_TIMER_START();
      /* The last step of the line art algorithm is to make sure that
       * selections does not leave "holes" between its borders and the
       * line arts, while not stepping over as well.
       * I used to run the "gegl:watershed-transform" operation to flood
       * the stroke pixels, but for such simple need, this simple code
       * is so much faster while producing better results.
       */

      mask = g_new (gfloat, width * height);
      gegl_buffer_get (mask_buffer, NULL, 1.0, NULL,
                       mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

      for (y = 0; y < height; y++)
        for (x = 0; x < width; x++)
          {
            if (distmap[x + y * width] == 1.0)
              {
                if (x > 0)
                  {
                    nx = x - 1;
                    if (y > 0)
                      {
                        ny = y - 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
                            line_art_queue_pixel (queue, x, y, 1);
                            continue;
                          }
                      }
                    ny = y;
                    if (mask[nx + ny * width] != 0.0)
                      {
                        line_art_queue_pixel (queue, x, y, 1);
                        continue;
                      }
                    if (y < height - 1)
                      {
                        ny = y + 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
                            line_art_queue_pixel (queue, x, y, 1);
                            continue;
                          }
                      }
                  }
                if (x < width - 1)
                  {
                    nx = x + 1;
                    if (y > 0)
                      {
                        ny = y - 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
                            line_art_queue_pixel (queue, x, y, 1);
                            continue;
                          }
                      }
                    ny = y;
                    if (mask[nx + ny * width] != 0.0)
                      {
                        line_art_queue_pixel (queue, x, y, 1);
                        continue;
                      }
                    if (y < height - 1)
                      {
                        ny = y + 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
                            line_art_queue_pixel (queue, x, y, 1);
                            continue;
                          }
                      }
                  }
                nx = x;
                if (y > 0)
                  {
                    ny = y - 1;
                    if (mask[nx + ny * width] != 0.0)
                      {
                        line_art_queue_pixel (queue, x, y, 1);
                        continue;
                      }
                  }
                if (y < height - 1)
                  {
                    ny = y + 1;
                    if (mask[nx + ny * width] != 0.0)
                      {
                        line_art_queue_pixel (queue, x, y, 1);
                        continue;
                      }
                  }
              }
          }

      g_object_get (line_art,
                    "max-grow", &line_art_max_grow,
                    (void *) NULL);
      while (! g_queue_is_empty (queue))
        {
          BorderPixel *c = (BorderPixel *) g_queue_pop_head (queue);

          if (mask[c->x + c->y * width] != 1.0)
            {
              mask[c->x + c->y * width] = 1.0;
              if (c->level >= line_art_max_grow)
                {
                  /* Do not overflood under line arts. */
                  g_free (c);
                  continue;
                }
              if (c->x > 0)
                {
                  nx = c->x - 1;
                  if (c->y > 0)
                    {
                      ny = c->y - 1;
                      if (mask[nx + ny * width] == 0.0 &&
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
                    }
                  ny = c->y;
                  if (mask[nx + ny * width] == 0.0 &&
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
                  if (c->y < height - 1)
                    {
                      ny = c->y + 1;
                      if (mask[nx + ny * width] == 0.0 &&
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
                    }
                }
              if (c->x < width - 1)
                {
                  nx = c->x + 1;
                  if (c->y > 0)
                    {
                      ny = c->y - 1;
                      if (mask[nx + ny * width] == 0.0 &&
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
                    }
                  ny = c->y;
                  if (mask[nx + ny * width] == 0.0 &&
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
                  if (c->y < height - 1)
                    {
                      ny = c->y + 1;
                      if (mask[nx + ny * width] == 0.0 &&
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
                    }
                }
              nx = c->x;
              if (c->y > 0)
                {
                  ny = c->y - 1;
                  if (mask[nx + ny * width] == 0.0 &&
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
                }
              if (c->y < height - 1)
                {
                  ny = c->y + 1;
                  if (mask[nx + ny * width] == 0.0 &&
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
                }
            }
          g_free (c);
        }
      g_queue_free (queue);
      gegl_buffer_set (mask_buffer, gegl_buffer_get_extent (mask_buffer),
                       0, NULL, mask, GEGL_AUTO_ROWSTRIDE);
      g_free (mask);

      GIMP_TIMER_END("watershed line art");
    }
  if (free_line_art)
    g_clear_object (&line_art);
  if (free_src_buffer)
    g_object_unref (src_buffer);

  return mask_buffer;
}

/*  private functions  */

static const Babl *
choose_format (GeglBuffer          *buffer,
               GimpSelectCriterion  select_criterion,
               gint                *n_components,
               gboolean            *has_alpha)
{
  const Babl *format = gegl_buffer_get_format (buffer);

  *has_alpha = babl_format_has_alpha (format);

  switch (select_criterion)
    {
    case GIMP_SELECT_CRITERION_COMPOSITE:
      if (babl_format_is_palette (format))
        format = babl_format ("R'G'B'A float");
      else
        format = gimp_babl_format (gimp_babl_format_get_base_type (format),
                                   GIMP_PRECISION_FLOAT_NON_LINEAR,
                                   *has_alpha,
                                   NULL);
      break;

    case GIMP_SELECT_CRITERION_RGB_RED:
    case GIMP_SELECT_CRITERION_RGB_GREEN:
    case GIMP_SELECT_CRITERION_RGB_BLUE:
    case GIMP_SELECT_CRITERION_ALPHA:
      format = babl_format ("R'G'B'A float");
      break;

    case GIMP_SELECT_CRITERION_HSV_HUE:
    case GIMP_SELECT_CRITERION_HSV_SATURATION:
    case GIMP_SELECT_CRITERION_HSV_VALUE:
      format = babl_format ("HSVA float");
      break;

    case GIMP_SELECT_CRITERION_LCH_LIGHTNESS:
      format = babl_format ("CIE L alpha float");
      break;

    case GIMP_SELECT_CRITERION_LCH_CHROMA:
    case GIMP_SELECT_CRITERION_LCH_HUE:
      format = babl_format ("CIE LCH(ab) alpha float");
      break;

    default:
      g_return_val_if_reached (NULL);
      break;
    }

  *n_components = babl_format_get_n_components (format);

  return format;
}

static gfloat
pixel_difference (const gfloat        *col1,
                  const gfloat        *col2,
                  gboolean             antialias,
                  gfloat               threshold,
                  gint                 n_components,
                  gboolean             has_alpha,
                  gboolean             select_transparent,
                  GimpSelectCriterion  select_criterion)
{
  gfloat max = 0.0;

  /*  if there is an alpha channel, never select transparent regions  */
  if (! select_transparent && has_alpha && col2[n_components - 1] == 0.0)
    return 0.0;

  if (select_transparent && has_alpha)
    {
      max = fabs (col1[n_components - 1] - col2[n_components - 1]);
    }
  else
    {
      gfloat diff;
      gint   b;

      if (has_alpha)
        n_components--;

      switch (select_criterion)
        {
        case GIMP_SELECT_CRITERION_COMPOSITE:
          for (b = 0; b < n_components; b++)
            {
              diff = fabs (col1[b] - col2[b]);
              if (diff > max)
                max = diff;
            }
          break;

        case GIMP_SELECT_CRITERION_RGB_RED:
          max = fabs (col1[0] - col2[0]);
          break;

        case GIMP_SELECT_CRITERION_RGB_GREEN:
          max = fabs (col1[1] - col2[1]);
          break;

        case GIMP_SELECT_CRITERION_RGB_BLUE:
          max = fabs (col1[2] - col2[2]);
          break;

        case GIMP_SELECT_CRITERION_ALPHA:
          max = fabs (col1[3] - col2[3]);
          break;

        case GIMP_SELECT_CRITERION_HSV_HUE:
          if (col1[1] > EPSILON)
            {
              if (col2[1] > EPSILON)
                {
                  max = fabs (col1[0] - col2[0]);
                  max = MIN (max, 1.0 - max);
                }
              else
                {
                  /* "infinite" difference.  anything >> 1 will do. */
                  max = 10.0;
                }
            }
          else
            {
              if (col2[1] > EPSILON)
                {
                  /* "infinite" difference.  anything >> 1 will do. */
                  max = 10.0;
                }
              else
                {
                  max = 0.0;
                }
            }
          break;

        case GIMP_SELECT_CRITERION_HSV_SATURATION:
          max = fabs (col1[1] - col2[1]);
          break;

        case GIMP_SELECT_CRITERION_HSV_VALUE:
          max = fabs (col1[2] - col2[2]);
          break;

        case GIMP_SELECT_CRITERION_LCH_LIGHTNESS:
          max = fabs (col1[0] - col2[0]) / 100.0;
          break;

        case GIMP_SELECT_CRITERION_LCH_CHROMA:
          max = fabs (col1[1] - col2[1]) / 100.0;
          break;

        case GIMP_SELECT_CRITERION_LCH_HUE:
          if (col1[1] > 100.0 * EPSILON)
            {
              if (col2[1] > 100.0 * EPSILON)
                {
                  max = fabs (col1[2] - col2[2]) / 360.0;
                  max = MIN (max, 1.0 - max);
                }
              else
                {
                  /* "infinite" difference.  anything >> 1 will do. */
                  max = 10.0;
                }
            }
          else
            {
              if (col2[1] > 100.0 * EPSILON)
                {
                  /* "infinite" difference.  anything >> 1 will do. */
                  max = 10.0;
                }
              else
                {
                  max = 0.0;
                }
            }
          break;
        }
    }

  if (antialias && threshold > 0.0)
    {
      gfloat aa = 1.5 - (max / threshold);

      if (aa <= 0.0)
        return 0.0;
      else if (aa < 0.5)
        return aa * 2.0;
      else
        return 1.0;
    }
  else
    {
      if (max > threshold)
        return 0.0;
      else
        return 1.0;
    }
}

static void
push_segment (GQueue *segment_queue,
              gint    y,
              gint    old_y,
              gint    start,
              gint    end,
              gint    new_y,
              gint    new_start,
              gint    new_end)
{
  /* To avoid excessive memory allocation (y, old_y, start, end) tuples are
   * stored in interleaved format:
   *
   * [y1] [old_y1] [start1] [end1] [y2] [old_y2] [start2] [end2]
   */


  if (new_y != old_y)
    {
      /* If the new segment's y-coordinate is different than the old (source)
       * segment's y-coordinate, push the entire segment.
       */

      g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_y));
      g_queue_push_tail (segment_queue, GINT_TO_POINTER (y));
      g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_start));
      g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_end));
    }
  else
    {
      /* Otherwise, only push the set-difference between the new segment and
       * the source segment (since we've already scanned the source segment.)
       * Note that the `+ 1` and `- 1` terms of the end/start coordinates below
       * are only necessary when `diagonal_neighbors` is on (and otherwise make
       * the segments slightly larger than necessary), but, meh...
       */

      if (new_start < start)
        {
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_y));
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (y));
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_start));
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (start + 1));
        }

      if (new_end > end)
        {
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_y));
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (y));
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (end - 1));
          g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_end));
        }
    }
}

static void
pop_segment (GQueue *segment_queue,
             gint   *y,
             gint   *old_y,
             gint   *start,
             gint   *end)
{
  *y     = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
  *old_y = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
  *start = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
  *end   = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
}

/* #define FETCH_ROW 1 */

static gboolean
find_contiguous_segment (const gfloat        *col,
                         GeglBuffer          *src_buffer,
                         GeglSampler         *src_sampler,
                         const GeglRectangle *src_extent,
                         GeglBuffer          *mask_buffer,
                         const Babl          *src_format,
                         const Babl          *mask_format,
                         gint                 n_components,
                         gboolean             has_alpha,
                         gboolean             select_transparent,
                         GimpSelectCriterion  select_criterion,
                         gboolean             antialias,
                         gfloat               threshold,
                         gint                 initial_x,
                         gint                 initial_y,
                         gint                *start,
                         gint                *end,
                         gfloat              *row)
{
  gfloat *s;
  gfloat  mask_row_buf[src_extent->width];
  gfloat *mask_row = mask_row_buf - src_extent->x;
  gfloat  diff;

#ifdef FETCH_ROW
  gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, initial_y, width, 1), 1.0,
                   src_format,
                   row, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
  s = row + initial_x * n_components;
#else
  s = (gfloat *) g_alloca (n_components * sizeof (gfloat));

  gegl_sampler_get (src_sampler,
                    initial_x, initial_y, NULL, s, GEGL_ABYSS_NONE);
#endif

  diff = pixel_difference (col, s, antialias, threshold,
                           n_components, has_alpha, select_transparent,
                           select_criterion);

  /* check the starting pixel */
  if (! diff)
    return FALSE;

  mask_row[initial_x] = diff;

  *start = initial_x - 1;
#ifdef FETCH_ROW
  s = row + *start * n_components;
#endif

  while (*start >= src_extent->x)
    {
#ifndef FETCH_ROW
      gegl_sampler_get (src_sampler,
                        *start, initial_y, NULL, s, GEGL_ABYSS_NONE);
#endif

      diff = pixel_difference (col, s, antialias, threshold,
                               n_components, has_alpha, select_transparent,
                               select_criterion);
      if (diff == 0.0)
        break;

      mask_row[*start] = diff;

      (*start)--;
#ifdef FETCH_ROW
      s -= n_components;
#endif
    }

  *end = initial_x + 1;
#ifdef FETCH_ROW
  s = row + *end * n_components;
#endif

  while (*end < src_extent->x + src_extent->width)
    {
#ifndef FETCH_ROW
      gegl_sampler_get (src_sampler,
                        *end, initial_y, NULL, s, GEGL_ABYSS_NONE);
#endif

      diff = pixel_difference (col, s, antialias, threshold,
                               n_components, has_alpha, select_transparent,
                               select_criterion);
      if (diff == 0.0)
        break;

      mask_row[*end] = diff;

      (*end)++;
#ifdef FETCH_ROW
      s += n_components;
#endif
    }

  gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (*start + 1, initial_y,
                                                *end - *start - 1, 1),
                   0, mask_format, &mask_row[*start + 1],
                   GEGL_AUTO_ROWSTRIDE);

  return TRUE;
}

static void
find_contiguous_region (GeglBuffer          *src_buffer,
                        GeglBuffer          *mask_buffer,
                        const Babl          *format,
                        gint                 n_components,
                        gboolean             has_alpha,
                        gboolean             select_transparent,
                        GimpSelectCriterion  select_criterion,
                        gboolean             antialias,
                        gfloat               threshold,
                        gboolean             diagonal_neighbors,
                        gint                 x,
                        gint                 y,
                        const gfloat        *col)
{
  const Babl          *mask_format = babl_format ("Y float");
  GeglSampler         *src_sampler;
  const GeglRectangle *src_extent;
  gint                 old_y;
  gint                 start, end;
  gint                 new_start, new_end;
  GQueue              *segment_queue;
  gfloat              *row = NULL;

  src_extent = gegl_buffer_get_extent (src_buffer);

#ifdef FETCH_ROW
  row = g_new (gfloat, src_extent->width * n_components);
#endif

  src_sampler = gegl_buffer_sampler_new (src_buffer,
                                         format, GEGL_SAMPLER_NEAREST);

  segment_queue = g_queue_new ();

  push_segment (segment_queue,
                y, /* dummy values: */ -1, 0, 0,
                y, x - 1, x + 1);

  do
    {
      pop_segment (segment_queue,
                   &y, &old_y, &start, &end);

      for (x = start + 1; x < end; x++)
        {
          gfloat val;

          gegl_buffer_get (mask_buffer, GEGL_RECTANGLE (x, y, 1, 1), 1.0,
                           mask_format, &val, GEGL_AUTO_ROWSTRIDE,
                           GEGL_ABYSS_NONE);

          if (val != 0.0)
            {
              /* If the current pixel is selected, then we've already visited
               * the next pixel.  (Note that we assume that the maximal image
               * width is sufficiently low that `x` won't overflow.)
               */

              x++;
              continue;
            }

          if (! find_contiguous_segment (col,
                                         src_buffer, src_sampler, src_extent,
                                         mask_buffer,
                                         format, mask_format,
                                         n_components,
                                         has_alpha,
                                         select_transparent, select_criterion,
                                         antialias, threshold, x, y,
                                         &new_start, &new_end,
                                         row))
            continue;

          /* We can skip directly to `new_end + 1` on the next iteration, since
           * we've just selected all pixels in the range `[x, new_end)`, and
           * the pixel at `new_end` is above threshold.  (Note that we assume
           * that the maximal image width is sufficiently low that `x` won't
           * overflow.)
           */

          x = new_end;

          if (diagonal_neighbors)
            {
              if (new_start >= src_extent->x)
                new_start--;

              if (new_end < src_extent->x + src_extent->width)
                new_end++;
            }

          if (y + 1 < src_extent->y + src_extent->height)
            {
              push_segment (segment_queue,
                            y, old_y, start, end,
                            y + 1, new_start, new_end);
            }

          if (y - 1 >= src_extent->y)
            {
              push_segment (segment_queue,
                            y, old_y, start, end,
                            y - 1, new_start, new_end);
            }

        }
    }
  while (! g_queue_is_empty (segment_queue));

  g_queue_free (segment_queue);

  g_object_unref (src_sampler);

#ifdef FETCH_ROW
  g_free (row);
#endif
}

static void
line_art_queue_pixel (GQueue *queue,
                      gint    x,
                      gint    y,
                      gint    level)
{
  BorderPixel *p = g_new (BorderPixel, 1);

  p->x      = x;
  p->y      = y;
  p->level  = level;

  g_queue_push_head (queue, p);
}

} /* extern "C" */

Messung
CHZG
829410092

¤ Dauer der Verarbeitung: 0.18 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.