gimppickable-contiguous-region.c 41.5 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7 8 9 10 11 12 13 14
 * (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
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 17 18 19
 */

#include "config.h"

20 21
#include <stdlib.h>

22
#include <cairo.h>
23
#include <gegl.h>
24
#include <gdk-pixbuf/gdk-pixbuf.h>
25 26

#include "libgimpcolor/gimpcolor.h"
27
#include "libgimpmath/gimpmath.h"
28 29 30

#include "core-types.h"

31 32
#include "gegl/gimp-babl.h"

33
#include "gimp-parallel.h"
34
#include "gimp-utils.h" /* GIMP_TIMER */
35
#include "gimpasync.h"
36
#include "gimplineart.h"
37
#include "gimppickable.h"
38
#include "gimppickable-contiguous-region.h"
39 40


41 42 43 44 45
typedef struct
{
  GeglBuffer *buffer;
  gboolean    select_transparent;
  gfloat      stroke_threshold;
46 47
  gint        segment_max_length;
  gint        spline_max_length;
48 49
} LineArtData;

50 51 52 53
typedef struct
{
  gint   x;
  gint   y;
54
  gint   level;
55 56
} BorderPixel;

57

58 59
/*  local function prototypes  */

60 61 62 63
static const Babl * choose_format         (GeglBuffer          *buffer,
                                           GimpSelectCriterion  select_criterion,
                                           gint                *n_components,
                                           gboolean            *has_alpha);
64 65
static gfloat   pixel_difference          (const gfloat        *col1,
                                           const gfloat        *col2,
66
                                           gboolean             antialias,
67 68
                                           gfloat               threshold,
                                           gint                 n_components,
69 70 71
                                           gboolean             has_alpha,
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion);
72 73 74 75 76 77 78 79 80 81 82 83 84
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);
85
static gboolean find_contiguous_segment   (const gfloat        *col,
86
                                           GeglBuffer          *src_buffer,
87
                                           GeglSampler         *src_sampler,
88 89
                                           GeglBuffer          *mask_buffer,
                                           const Babl          *src_format,
90
                                           const Babl          *mask_format,
91
                                           gint                 n_components,
92
                                           gboolean             has_alpha,
93
                                           gint                 width,
94 95 96
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion,
                                           gboolean             antialias,
97
                                           gfloat               threshold,
98 99
                                           gint                 initial_x,
                                           gint                 initial_y,
100
                                           gint                *start,
101 102 103
                                           gint                *end,
                                           gfloat              *row);
static void     find_contiguous_region    (GeglBuffer          *src_buffer,
104 105
                                           GeglBuffer          *mask_buffer,
                                           const Babl          *format,
106 107
                                           gint                 n_components,
                                           gboolean             has_alpha,
108 109 110
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion,
                                           gboolean             antialias,
111
                                           gfloat               threshold,
112
                                           gboolean             diagonal_neighbors,
113 114
                                           gint                 x,
                                           gint                 y,
115
                                           const gfloat        *col);
116

117 118
static LineArtData   * line_art_data_new    (GeglBuffer          *buffer,
                                             gboolean             select_transparent,
119 120 121
                                             gfloat               stroke_threshold,
                                             gint                 segment_max_length,
                                             gint                 spline_max_length);
122 123 124
static void            line_art_data_free   (LineArtData         *data);
static GimpPickableLineArtAsyncResult *
                       line_art_result_new  (GeglBuffer          *line_art,
125
                                             gfloat              *distmap);
126 127 128 129 130
static void            line_art_result_free (GimpPickableLineArtAsyncResult
                                                                 *data);
static void            line_art_queue_pixel (GQueue              *queue,
                                             gint                 x,
                                             gint                 y,
131
                                             gint                 level);
132

133 134 135

/*  public functions  */

136 137 138
static void
gimp_pickable_contiguous_region_prepare_line_art_async_func (GimpAsync   *async,
                                                             LineArtData *data)
Jehan's avatar
Jehan committed
139 140
{
  GeglBuffer *lineart;
141
  gfloat     *distmap;
Jehan's avatar
Jehan committed
142
  gboolean    has_alpha;
143
  gboolean    select_transparent = FALSE;
Jehan's avatar
Jehan committed
144

145
  has_alpha = babl_format_has_alpha (gegl_buffer_get_format (data->buffer));
Jehan's avatar
Jehan committed
146

147
  if (has_alpha)
Jehan's avatar
Jehan committed
148
    {
149
      if (data->select_transparent)
Jehan's avatar
Jehan committed
150 151 152 153 154 155
        {
          /*  don't select transparent regions if there are no fully
           *  transparent pixels.
           */
          GeglBufferIterator *gi;

156 157
          gi = gegl_buffer_iterator_new (data->buffer, NULL, 0,
                                         babl_format ("A u8"),
Jehan's avatar
Jehan committed
158 159 160 161 162 163
                                         GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
          while (gegl_buffer_iterator_next (gi))
            {
              guint8 *p = (guint8*) gi->items[0].data;
              gint    k;

164 165 166 167 168 169 170 171 172 173 174
              if (gimp_async_is_canceled (async))
                {
                  gegl_buffer_iterator_stop (gi);

                  gimp_async_abort (async);

                  line_art_data_free (data);

                  return;
                }

Jehan's avatar
Jehan committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
              for (k = 0; k < gi->length; k++)
                {
                  if (! *p)
                    {
                      select_transparent = TRUE;
                      break;
                    }
                  p++;
                }
              if (select_transparent)
                break;
            }
          if (select_transparent)
            gegl_buffer_iterator_stop (gi);
        }
    }

  /* For smart selection, we generate a binarized image with close
   * regions, then run a composite selection with no threshold on
   * this intermediate buffer.
   */
  GIMP_TIMER_START();

198
  lineart = gimp_lineart_close (data->buffer,
Jehan's avatar
Jehan committed
199
                                select_transparent,
200
                                data->stroke_threshold,
Jehan's avatar
Jehan committed
201 202 203 204 205 206
                                /*minimal_lineart_area,*/
                                5,
                                /*normal_estimate_mask_size,*/
                                5,
                                /*end_point_rate,*/
                                0.85,
207
                                data->spline_max_length,
Jehan's avatar
Jehan committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221
                                /*spline_max_angle,*/
                                90.0,
                                /*end_point_connectivity,*/
                                2,
                                /*spline_roundness,*/
                                1.0,
                                /*allow_self_intersections,*/
                                TRUE,
                                /*created_regions_significant_area,*/
                                4,
                                /*created_regions_minimum_area,*/
                                100,
                                /*small_segments_from_spline_sources,*/
                                TRUE,
222
                                data->segment_max_length,
223
                                &distmap);
Jehan's avatar
Jehan committed
224 225 226

  GIMP_TIMER_END("close line-art");

227
  gimp_async_finish_full (async,
228
                          line_art_result_new (lineart, distmap),
229
                          (GDestroyNotify) line_art_result_free);
230 231 232 233 234

  line_art_data_free (data);
}

GeglBuffer *
235 236 237
gimp_pickable_contiguous_region_prepare_line_art (GimpPickable  *pickable,
                                                  gboolean       select_transparent,
                                                  gfloat         stroke_threshold,
238 239
                                                  gint           segment_max_length,
                                                  gint           spline_max_length,
240
                                                  gfloat       **distmap)
241
{
242 243 244 245
  GimpAsync                      *async;
  LineArtData                    *data;
  GimpPickableLineArtAsyncResult *result;
  GeglBuffer                     *lineart;
246 247 248 249 250 251 252

  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);

  gimp_pickable_flush (pickable);

  async = gimp_async_new ();
  data  = line_art_data_new (gimp_pickable_get_buffer (pickable),
253 254
                             select_transparent, stroke_threshold,
                             segment_max_length, spline_max_length);
255 256 257

  gimp_pickable_contiguous_region_prepare_line_art_async_func (async, data);

258 259 260 261 262
  result = gimp_async_get_result (async);

  lineart   = g_object_ref (result->line_art);
  *distmap  = result->distmap;
  result->distmap  = NULL;
263 264 265

  g_object_unref (async);

Jehan's avatar
Jehan committed
266 267 268
  return lineart;
}

269 270 271 272
GimpAsync *
gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable *pickable,
                                                        gboolean      select_transparent,
                                                        gfloat        stroke_threshold,
273 274
                                                        gint          segment_max_length,
                                                        gint          spline_max_length,
275 276 277 278 279 280 281 282 283 284 285 286
                                                        gint          priority)
{
  GeglBuffer  *buffer;
  GimpAsync   *async;
  LineArtData *data;

  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);

  gimp_pickable_flush (pickable);

  buffer = gegl_buffer_dup (gimp_pickable_get_buffer (pickable));

287 288
  data  = line_art_data_new (buffer, select_transparent, stroke_threshold,
                             segment_max_length, spline_max_length);
289 290 291 292 293 294 295 296 297 298 299 300 301

  g_object_unref (buffer);

  async = gimp_parallel_run_async_full (
    priority,
    (GimpParallelRunAsyncFunc)
      gimp_pickable_contiguous_region_prepare_line_art_async_func,
    data,
    (GDestroyNotify) line_art_data_free);

  return async;
}

302
GeglBuffer *
303
gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
Jehan's avatar
Jehan committed
304
                                         GeglBuffer          *line_art,
305
                                         gfloat              *distmap,
306 307 308 309
                                         gboolean             antialias,
                                         gfloat               threshold,
                                         gboolean             select_transparent,
                                         GimpSelectCriterion  select_criterion,
310
                                         gboolean             diagonal_neighbors,
311
                                         gfloat               stroke_threshold,
312
                                         gint                 flooding_max,
313 314
                                         gint                 segment_max_length,
                                         gint                 spline_max_length,
315 316
                                         gint                 x,
                                         gint                 y)
317
{
318 319 320 321 322 323 324
  GeglBuffer    *src_buffer;
  GeglBuffer    *mask_buffer;
  const Babl    *format;
  GeglRectangle  extent;
  gint           n_components;
  gboolean       has_alpha;
  gfloat         start_col[MAX_CHANNELS];
325
  gboolean       smart_line_art = FALSE;
Jehan's avatar
Jehan committed
326
  gboolean       free_line_art  = FALSE;
327 328

  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
329

Jehan's avatar
Jehan committed
330
  if (select_criterion == GIMP_SELECT_CRITERION_LINE_ART)
331
    {
332 333
      g_return_val_if_fail ((line_art && distmap) ||
                            (! line_art && ! distmap),
334
                            NULL);
Jehan's avatar
Jehan committed
335
      if (line_art == NULL)
336
        {
Jehan's avatar
Jehan committed
337 338 339
          /* 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).
340
           */
341
          line_art      = gimp_pickable_contiguous_region_prepare_line_art (pickable, select_transparent,
342
                                                                            stroke_threshold,
343 344
                                                                            segment_max_length,
                                                                            spline_max_length,
345
                                                                            &distmap);
Jehan's avatar
Jehan committed
346
          free_line_art = TRUE;
347
        }
348

Jehan's avatar
Jehan committed
349 350
      src_buffer = line_art;

351
      smart_line_art     = TRUE;
352 353 354 355 356
      antialias          = FALSE;
      threshold          = 0.0;
      select_transparent = FALSE;
      select_criterion   = GIMP_SELECT_CRITERION_COMPOSITE;
      diagonal_neighbors = FALSE;
Jehan's avatar
Jehan committed
357 358 359 360 361

      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);
Jehan's avatar
Jehan committed
362 363 364 365 366
    }
  else
    {
      gimp_pickable_flush (pickable);
      src_buffer = gimp_pickable_get_buffer (pickable);
367

Jehan's avatar
Jehan committed
368 369 370 371 372
      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);

Jehan's avatar
Jehan committed
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
      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;
        }
388 389
    }

390
  extent = *gegl_buffer_get_extent (src_buffer);
391

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

394 395 396 397 398 399 400 401 402 403 404 405
  if (smart_line_art && start_col[0])
    {
      /* As a special exception, if you fill over a line art pixel, only
       * fill the pixel and exit
       */
      start_col[0] = 1.0;
      gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (x, y, 1, 1),
                       0, babl_format ("Y float"), start_col,
                       GEGL_AUTO_ROWSTRIDE);
      smart_line_art = FALSE;
    }
  else if (x >= extent.x && x < (extent.x + extent.width) &&
406 407 408 409 410 411 412
      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,
413
                              antialias, threshold, diagonal_neighbors,
414
                              x, y, start_col);
415

416 417
      GIMP_TIMER_END("foo");
    }
418 419 420 421 422 423

  if (smart_line_art)
    {
      /* 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.
424 425 426
       * 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.
427
       */
428 429 430 431 432
      gfloat *mask;
      GQueue *queue  = g_queue_new ();
      gint    width  = gegl_buffer_get_width (line_art);
      gint    height = gegl_buffer_get_height (line_art);
      gint    nx, ny;
433 434 435

      GIMP_TIMER_START();

436 437 438 439 440 441 442
      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++)
          {
443
            if (distmap[x + y * width] == 1.0)
444 445 446 447 448 449 450 451 452
              {
                if (x > 0)
                  {
                    nx = x - 1;
                    if (y > 0)
                      {
                        ny = y - 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
453
                            line_art_queue_pixel (queue, x, y, 1);
454 455 456 457 458 459
                            continue;
                          }
                      }
                    ny = y;
                    if (mask[nx + ny * width] != 0.0)
                      {
460
                        line_art_queue_pixel (queue, x, y, 1);
461 462 463 464 465 466 467
                        continue;
                      }
                    if (y < height - 1)
                      {
                        ny = y + 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
468
                            line_art_queue_pixel (queue, x, y, 1);
469 470 471 472 473 474 475 476 477 478 479 480
                            continue;
                          }
                      }
                  }
                if (x < width - 1)
                  {
                    nx = x + 1;
                    if (y > 0)
                      {
                        ny = y - 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
481
                            line_art_queue_pixel (queue, x, y, 1);
482 483 484 485 486 487
                            continue;
                          }
                      }
                    ny = y;
                    if (mask[nx + ny * width] != 0.0)
                      {
488
                        line_art_queue_pixel (queue, x, y, 1);
489 490 491 492 493 494 495
                        continue;
                      }
                    if (y < height - 1)
                      {
                        ny = y + 1;
                        if (mask[nx + ny * width] != 0.0)
                          {
496
                            line_art_queue_pixel (queue, x, y, 1);
497 498 499 500 501 502 503 504 505 506
                            continue;
                          }
                      }
                  }
                nx = x;
                if (y > 0)
                  {
                    ny = y - 1;
                    if (mask[nx + ny * width] != 0.0)
                      {
507
                        line_art_queue_pixel (queue, x, y, 1);
508 509 510 511 512 513 514 515
                        continue;
                      }
                  }
                if (y < height - 1)
                  {
                    ny = y + 1;
                    if (mask[nx + ny * width] != 0.0)
                      {
516
                        line_art_queue_pixel (queue, x, y, 1);
517 518 519 520 521 522 523
                        continue;
                      }
                  }
              }
          }

      while (! g_queue_is_empty (queue))
524
        {
525
          BorderPixel *c = g_queue_pop_head (queue);
526

527
          if (mask[c->x + c->y * width] != 1.0)
528
            {
529
              mask[c->x + c->y * width] = 1.0;
530 531 532
              if (c->level >= flooding_max)
                /* Do not overflood under line arts. */
                continue;
533 534 535 536 537 538 539
              if (c->x > 0)
                {
                  nx = c->x - 1;
                  if (c->y > 0)
                    {
                      ny = c->y - 1;
                      if (mask[nx + ny * width] == 0.0 &&
540 541
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
542 543 544
                    }
                  ny = c->y;
                  if (mask[nx + ny * width] == 0.0 &&
545 546
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
547 548 549 550
                  if (c->y < height - 1)
                    {
                      ny = c->y - 1;
                      if (mask[nx + ny * width] == 0.0 &&
551 552
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
553 554 555 556 557 558 559 560 561
                    }
                }
              if (c->x < width - 1)
                {
                  nx = c->x + 1;
                  if (c->y > 0)
                    {
                      ny = c->y - 1;
                      if (mask[nx + ny * width] == 0.0 &&
562 563
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
564 565 566
                    }
                  ny = c->y;
                  if (mask[nx + ny * width] == 0.0 &&
567 568
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
569 570 571 572
                  if (c->y < height - 1)
                    {
                      ny = c->y - 1;
                      if (mask[nx + ny * width] == 0.0 &&
573 574
                          distmap[nx + ny * width] > distmap[c->x + c->y * width])
                        line_art_queue_pixel (queue, nx, ny, c->level + 1);
575 576 577 578 579 580 581
                    }
                }
              nx = c->x;
              if (c->y > 0)
                {
                  ny = c->y - 1;
                  if (mask[nx + ny * width] == 0.0 &&
582 583
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
584 585 586 587 588
                }
              if (c->y < height - 1)
                {
                  ny = c->y + 1;
                  if (mask[nx + ny * width] == 0.0 &&
589 590
                      distmap[nx + ny * width] > distmap[c->x + c->y * width])
                    line_art_queue_pixel (queue, nx, ny, c->level + 1);
591
                }
592
            }
593
          g_free (c);
594
        }
595 596 597 598
      g_queue_free (queue);
      gegl_buffer_set (mask_buffer, gegl_buffer_get_extent (mask_buffer),
                       0, NULL, mask, GEGL_AUTO_ROWSTRIDE);
      g_free (mask);
599 600

      GIMP_TIMER_END("watershed line art");
601 602 603 604 605 606

      if (free_line_art)
        {
          g_object_unref (src_buffer);
          g_free (distmap);
        }
607
    }
608

609
  return mask_buffer;
610 611
}

612
GeglBuffer *
613 614 615 616 617 618
gimp_pickable_contiguous_region_by_color (GimpPickable        *pickable,
                                          gboolean             antialias,
                                          gfloat               threshold,
                                          gboolean             select_transparent,
                                          GimpSelectCriterion  select_criterion,
                                          const GimpRGB       *color)
619
{
620
  /*  Scan over the pickable's active layer, finding pixels within the
Sven Neumann's avatar
Sven Neumann committed
621 622
   *  specified threshold from the given R, G, & B values.  If
   *  antialiasing is on, use the same antialiasing scheme as in
623
   *  fuzzy_select.  Modify the pickable's mask to reflect the
Sven Neumann's avatar
Sven Neumann committed
624
   *  additional selection
625
   */
626 627 628
  GeglBufferIterator *iter;
  GeglBuffer         *src_buffer;
  GeglBuffer         *mask_buffer;
629 630
  const Babl         *format;
  gint                n_components;
631
  gboolean            has_alpha;
632
  gfloat              start_col[MAX_CHANNELS];
633

634
  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
635 636
  g_return_val_if_fail (color != NULL, NULL);

637 638
  gimp_pickable_flush (pickable);

639
  src_buffer = gimp_pickable_get_buffer (pickable);
640

641 642 643 644
  format = choose_format (src_buffer, select_criterion,
                          &n_components, &has_alpha);

  gimp_rgba_get_pixel (color, format, start_col);
645

646
  if (has_alpha)
647 648 649
    {
      if (select_transparent)
        {
650
          /*  don't select transparency if "color" isn't fully transparent
651
           */
652
          if (start_col[n_components - 1] > 0.0)
653 654 655 656 657 658 659 660
            select_transparent = FALSE;
        }
    }
  else
    {
      select_transparent = FALSE;
    }

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

664 665
  iter = gegl_buffer_iterator_new (src_buffer,
                                   NULL, 0, format,
666
                                   GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
667

668
  gegl_buffer_iterator_add (iter, mask_buffer,
669
                            NULL, 0, babl_format ("Y float"),
670
                            GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
Sven Neumann's avatar
Sven Neumann committed
671

672
  while (gegl_buffer_iterator_next (iter))
Sven Neumann's avatar
Sven Neumann committed
673
    {
674 675
      const gfloat *src   = iter->items[0].data;
      gfloat       *dest  = iter->items[1].data;
676
      gint          count = iter->length;
Sven Neumann's avatar
Sven Neumann committed
677

678
      while (count--)
Sven Neumann's avatar
Sven Neumann committed
679 680
        {
          /*  Find how closely the colors match  */
681
          *dest = pixel_difference (start_col, src,
682 683
                                    antialias,
                                    threshold,
684
                                    n_components,
685 686 687 688
                                    has_alpha,
                                    select_transparent,
                                    select_criterion);

689
          src  += n_components;
690
          dest += 1;
Sven Neumann's avatar
Sven Neumann committed
691 692
        }
    }
693

694
  return mask_buffer;
Sven Neumann's avatar
Sven Neumann committed
695 696
}

697 698 699

/*  private functions  */

700 701 702 703 704 705 706 707 708 709 710 711 712 713
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))
714
        format = babl_format ("R'G'B'A float");
715 716
      else
        format = gimp_babl_format (gimp_babl_format_get_base_type (format),
717 718 719
                                   GIMP_PRECISION_FLOAT_NON_LINEAR,
                                   *has_alpha,
                                   NULL);
720 721 722 723 724
      break;

    case GIMP_SELECT_CRITERION_R:
    case GIMP_SELECT_CRITERION_G:
    case GIMP_SELECT_CRITERION_B:
725
    case GIMP_SELECT_CRITERION_A:
726
      format = babl_format ("R'G'B'A float");
727 728 729 730 731 732 733 734
      break;

    case GIMP_SELECT_CRITERION_H:
    case GIMP_SELECT_CRITERION_S:
    case GIMP_SELECT_CRITERION_V:
      format = babl_format ("HSVA float");
      break;

735
    case GIMP_SELECT_CRITERION_LCH_L:
736 737 738
      format = babl_format ("CIE L alpha float");
      break;

739 740 741 742 743
    case GIMP_SELECT_CRITERION_LCH_C:
    case GIMP_SELECT_CRITERION_LCH_H:
      format = babl_format ("CIE LCH(ab) alpha float");
      break;

744
    case GIMP_SELECT_CRITERION_LINE_ART:
745
      format = babl_format ("Y'A float");
746 747
      break;

748 749 750 751 752 753 754 755 756 757
    default:
      g_return_val_if_reached (NULL);
      break;
    }

  *n_components = babl_format_get_n_components (format);

  return format;
}

758 759 760
static gfloat
pixel_difference (const gfloat        *col1,
                  const gfloat        *col2,
761
                  gboolean             antialias,
762 763
                  gfloat               threshold,
                  gint                 n_components,
764 765 766
                  gboolean             has_alpha,
                  gboolean             select_transparent,
                  GimpSelectCriterion  select_criterion)
767
{
768
  gfloat max = 0.0;
769 770

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

774
  if (select_transparent && has_alpha)
775
    {
776
      max = fabs (col1[n_components - 1] - col2[n_components - 1]);
777 778 779
    }
  else
    {
780 781
      gfloat diff;
      gint   b;
782 783

      if (has_alpha)
784
        n_components--;
785

786
      switch (select_criterion)
787
        {
788
        case GIMP_SELECT_CRITERION_COMPOSITE:
789
          for (b = 0; b < n_components; b++)
790
            {
791
              diff = fabs (col1[b] - col2[b]);
792 793 794 795 796 797
              if (diff > max)
                max = diff;
            }
          break;

        case GIMP_SELECT_CRITERION_R:
798
          max = fabs (col1[0] - col2[0]);
799 800 801
          break;

        case GIMP_SELECT_CRITERION_G:
802
          max = fabs (col1[1] - col2[1]);
803 804 805
          break;

        case GIMP_SELECT_CRITERION_B:
806
          max = fabs (col1[2] - col2[2]);
807 808
          break;

809 810 811 812
        case GIMP_SELECT_CRITERION_A:
          max = fabs (col1[3] - col2[3]);
          break;

813
        case GIMP_SELECT_CRITERION_H:
814 815
          max = fabs (col1[0] - col2[0]);
          max = MIN (max, 1.0 - max);
816 817 818
          break;

        case GIMP_SELECT_CRITERION_S:
819
          max = fabs (col1[1] - col2[1]);
820 821 822
          break;

        case GIMP_SELECT_CRITERION_V:
823
          max = fabs (col1[2] - col2[2]);
824
          break;
825 826 827 828 829 830 831 832 833 834 835 836 837

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

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

        case GIMP_SELECT_CRITERION_LCH_H:
          max = fabs (col1[2] - col2[2]) / 360.0;
          max = MIN (max, 1.0 - max);
          break;
838 839 840 841

        case GIMP_SELECT_CRITERION_LINE_ART:
          /* Smart selection is handled before. */
          g_return_val_if_reached (0.0);
842
        }
843 844
    }

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

849
      if (aa <= 0.0)
850
        return 0.0;
851
      else if (aa < 0.5)
852
        return aa * 2.0;
853
      else
854
        return 1.0;
855 856 857 858
    }
  else
    {
      if (max > threshold)
859
        return 0.0;
860
      else
861
        return 1.0;
862 863 864
    }
}

865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
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));
}

930
/* #define FETCH_ROW 1 */
931

Michael Natterer's avatar
Michael Natterer committed
932
static gboolean
933
find_contiguous_segment (const gfloat        *col,
934
                         GeglBuffer          *src_buffer,
935
                         GeglSampler         *src_sampler,
936
                         GeglBuffer          *mask_buffer,
937 938
                         const Babl          *src_format,
                         const Babl          *mask_format,
939
                         gint                 n_components,
940
                         gboolean             has_alpha,
941
                         gint                 width,
942 943 944
                         gboolean             select_transparent,
                         GimpSelectCriterion  select_criterion,
                         gboolean             antialias,
945
                         gfloat               threshold,
946 947
                         gint                 initial_x,
                         gint                 initial_y,
948
                         gint                *start,
949 950
                         gint                *end,
                         gfloat              *row)
951
{
952 953 954 955 956 957
  gfloat *s;
  gfloat  mask_row[width];
  gfloat  diff;

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

964 965
  gegl_sampler_get (src_sampler,
                    initial_x, initial_y, NULL, s, GEGL_ABYSS_NONE);
966
#endif
967

968
  diff = pixel_difference (col, s, antialias, threshold,
969
                           n_components, has_alpha, select_transparent,
970
                           select_criterion);
971

972
  /* check the starting pixel */
973
  if (! diff)
974 975 976
    return FALSE;

  mask_row[initial_x] = diff;
977

978
  *start = initial_x - 1;
979 980 981
#ifdef FETCH_ROW
  s = row + *start * n_components;
#endif
982

983
  while (*start >= 0)
984
    {
985
#ifndef FETCH_ROW
986 987
      gegl_sampler_get (src_sampler,
                        *start, initial_y, NULL, s, GEGL_ABYSS_NONE);
988
#endif
989

990
      diff = pixel_difference (col, s, antialias, threshold,
991
                               n_components, has_alpha, select_transparent,
992
                               select_criterion);
993 994
      if (diff == 0.0)
        break;
995

996
      mask_row[*start] = diff;
997

998
      (*start)--;
999
#ifdef FETCH_ROW
1000
      s -= n_components;
1001
#endif