gimpimage-contiguous-region.c 17.7 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 <http://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 25

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

#include "core-types.h"

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

32 33 34
#include "gimpchannel.h"
#include "gimpimage.h"
#include "gimpimage-contiguous-region.h"
35
#include "gimppickable.h"
36 37 38 39


/*  local function prototypes  */

40 41
static gfloat   pixel_difference          (const gfloat        *col1,
                                           const gfloat        *col2,
42
                                           gboolean             antialias,
43 44
                                           gfloat               threshold,
                                           gint                 n_components,
45 46 47
                                           gboolean             has_alpha,
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion);
48
static gboolean find_contiguous_segment   (const gfloat        *col,
49 50 51
                                           GeglBuffer          *src_buffer,
                                           GeglBuffer          *mask_buffer,
                                           const Babl          *src_format,
52
                                           gint                 n_components,
53
                                           gboolean             has_alpha,
54
                                           gint                 width,
55 56 57
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion,
                                           gboolean             antialias,
58
                                           gfloat               threshold,
59 60
                                           gint                 initial_x,
                                           gint                 initial_y,
61 62
                                           gint                *start,
                                           gint                *end);
63 64 65
static void find_contiguous_region_helper (GeglBuffer          *src_buffer,
                                           GeglBuffer          *mask_buffer,
                                           const Babl          *format,
66 67 68
                                           gboolean             select_transparent,
                                           GimpSelectCriterion  select_criterion,
                                           gboolean             antialias,
69
                                           gfloat               threshold,
70 71
                                           gint                 x,
                                           gint                 y,
72
                                           const gfloat        *col);
73 74 75 76 77


/*  public functions  */

GimpChannel *
78 79 80 81
gimp_image_contiguous_region_by_seed (GimpImage           *image,
                                      GimpDrawable        *drawable,
                                      gboolean             sample_merged,
                                      gboolean             antialias,
82
                                      gfloat               threshold,
83 84 85 86
                                      gboolean             select_transparent,
                                      GimpSelectCriterion  select_criterion,
                                      gint                 x,
                                      gint                 y)
87
{
88 89 90 91 92
  GimpPickable *pickable;
  GeglBuffer   *src_buffer;
  GimpChannel  *mask;
  GeglBuffer   *mask_buffer;
  const Babl   *src_format;
93
  gfloat        start_col[MAX_CHANNELS];
94

95
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
96 97 98
  g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);

  if (sample_merged)
99
    pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
100 101
  else
    pickable = GIMP_PICKABLE (drawable);
102

103 104
  gimp_pickable_flush (pickable);

105
  src_format = gimp_pickable_get_format (pickable);
106
  if (babl_format_is_palette (src_format))
107 108 109 110 111
    src_format = babl_format ("RGBA float");
  else
    src_format = gimp_babl_format (gimp_babl_format_get_base_type (src_format),
                                   GIMP_PRECISION_FLOAT,
                                   babl_format_has_alpha (src_format));
112

113
  src_buffer = gimp_pickable_get_buffer (pickable);
114

115 116 117
  mask = gimp_channel_new_mask (image,
                                gegl_buffer_get_width  (src_buffer),
                                gegl_buffer_get_height (src_buffer));
118

119
  mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
120

121 122 123 124 125 126
  gegl_buffer_sample (src_buffer, x, y, NULL, start_col, src_format,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);

  if (babl_format_has_alpha (src_format))
    {
      if (select_transparent)
127
        {
128
          gint n_components = babl_format_get_n_components (src_format);
129

130 131 132
          /*  don't select transparent regions if the start pixel isn't
           *  fully transparent
           */
133
          if (start_col[n_components - 1] > 0)
134
            select_transparent = FALSE;
135
        }
136
    }
137 138 139 140 141 142 143 144 145
  else
    {
      select_transparent = FALSE;
    }

  find_contiguous_region_helper (src_buffer, mask_buffer, src_format,
                                 select_transparent, select_criterion,
                                 antialias, threshold,
                                 x, y, start_col);
146 147 148 149 150

  return mask;
}

GimpChannel *
151 152 153 154
gimp_image_contiguous_region_by_color (GimpImage            *image,
                                       GimpDrawable         *drawable,
                                       gboolean              sample_merged,
                                       gboolean              antialias,
155
                                       gfloat                threshold,
156 157 158
                                       gboolean              select_transparent,
                                       GimpSelectCriterion  select_criterion,
                                       const GimpRGB        *color)
159
{
160
  /*  Scan over the image's active layer, finding pixels within the
Sven Neumann's avatar
Sven Neumann committed
161 162
   *  specified threshold from the given R, G, & B values.  If
   *  antialiasing is on, use the same antialiasing scheme as in
163
   *  fuzzy_select.  Modify the image's mask to reflect the
Sven Neumann's avatar
Sven Neumann committed
164
   *  additional selection
165
   */
166 167 168 169 170 171 172
  GeglBufferIterator *iter;
  GimpPickable       *pickable;
  GimpChannel        *mask;
  GeglBuffer         *src_buffer;
  GeglBuffer         *mask_buffer;
  gint                width, height;
  gboolean            has_alpha;
173
  gfloat              col[MAX_CHANNELS];
174

175
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
176 177 178
  g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
  g_return_val_if_fail (color != NULL, NULL);

179
  gimp_rgba_get_pixel (color, babl_format ("RGBA float"), col);
180 181

  if (sample_merged)
182
    pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
183
  else
184 185
    pickable = GIMP_PICKABLE (drawable);

186 187
  gimp_pickable_flush (pickable);

188
  has_alpha = babl_format_has_alpha (gimp_pickable_get_format (pickable));
189

190 191 192
  src_buffer = gimp_pickable_get_buffer (pickable);
  width  = gegl_buffer_get_width (src_buffer);
  height = gegl_buffer_get_height (src_buffer);
193

194
  iter = gegl_buffer_iterator_new (src_buffer,
195
                                   NULL, 0, babl_format ("RGBA float"),
196
                                   GEGL_BUFFER_READ, GEGL_ABYSS_NONE);
197

198
  if (has_alpha)
199 200 201 202 203
    {
      if (select_transparent)
        {
          /*  don't select transparancy if "color" isn't fully transparent
           */
204
          if (col[3] > 0.0)
205 206 207 208 209 210 211 212
            select_transparent = FALSE;
        }
    }
  else
    {
      select_transparent = FALSE;
    }

213
  mask = gimp_channel_new_mask (image, width, height);
214

215
  mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
216

217
  gegl_buffer_iterator_add (iter, mask_buffer,
218
                            NULL, 0, babl_format ("Y float"),
219
                            GEGL_BUFFER_WRITE, GEGL_ABYSS_NONE);
Sven Neumann's avatar
Sven Neumann committed
220

221
  while (gegl_buffer_iterator_next (iter))
Sven Neumann's avatar
Sven Neumann committed
222
    {
223 224
      const gfloat *src  = iter->data[0];
      gfloat       *dest = iter->data[1];
Sven Neumann's avatar
Sven Neumann committed
225

226
      while (iter->length--)
Sven Neumann's avatar
Sven Neumann committed
227 228
        {
          /*  Find how closely the colors match  */
229 230 231 232 233 234 235 236 237 238
          *dest = pixel_difference (col, src,
                                    antialias,
                                    threshold,
                                    has_alpha ? 4 : 3,
                                    has_alpha,
                                    select_transparent,
                                    select_criterion);

          src  += 4;
          dest += 1;
Sven Neumann's avatar
Sven Neumann committed
239 240
        }
    }
241 242

  return mask;
Sven Neumann's avatar
Sven Neumann committed
243 244
}

245 246 247

/*  private functions  */

248 249 250
static gfloat
pixel_difference (const gfloat        *col1,
                  const gfloat        *col2,
251
                  gboolean             antialias,
252 253
                  gfloat               threshold,
                  gint                 n_components,
254 255 256
                  gboolean             has_alpha,
                  gboolean             select_transparent,
                  GimpSelectCriterion  select_criterion)
257
{
258
  gfloat max = 0.0;
259 260

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

264
  if (select_transparent && has_alpha)
265
    {
266
      max = fabs (col1[n_components - 1] - col2[n_components - 1]);
267 268 269
    }
  else
    {
270 271
      gfloat  diff;
      gint    b;
272 273

      if (has_alpha)
274
        n_components--;
275

276
      switch (select_criterion)
277
        {
278
        case GIMP_SELECT_CRITERION_COMPOSITE:
279
          for (b = 0; b < n_components; b++)
280
            {
281
              diff = fabs (col1[b] - col2[b]);
282 283 284 285 286 287
              if (diff > max)
                max = diff;
            }
          break;

        case GIMP_SELECT_CRITERION_R:
288
          max = fabs (col1[0] - col2[0]);
289 290 291
          break;

        case GIMP_SELECT_CRITERION_G:
292
          max = fabs (col1[1] - col2[1]);
293 294 295
          break;

        case GIMP_SELECT_CRITERION_B:
296
          max = fabs (col1[2] - col2[2]);
297 298
          break;

299
#if 0
300
        case GIMP_SELECT_CRITERION_H:
301 302 303 304 305 306
          av0 = (gint) col1[0];
          av1 = (gint) col1[1];
          av2 = (gint) col1[2];
          bv0 = (gint) col2[0];
          bv1 = (gint) col2[1];
          bv2 = (gint) col2[2];
307 308
          gimp_rgb_to_hsv_int (&av0, &av1, &av2);
          gimp_rgb_to_hsv_int (&bv0, &bv1, &bv2);
309 310 311 312 313 314 315 316 317
          /* wrap around candidates for the actual distance */
          {
            gint dist1 = abs (av0 - bv0);
            gint dist2 = abs (av0 - 360 - bv0);
            gint dist3 = abs (av0 - bv0 + 360);
            max = MIN (dist1, dist2);
            if (max > dist3)
              max = dist3;
          }
318 319 320
          break;

        case GIMP_SELECT_CRITERION_S:
321 322 323 324 325 326
          av0 = (gint) col1[0];
          av1 = (gint) col1[1];
          av2 = (gint) col1[2];
          bv0 = (gint) col2[0];
          bv1 = (gint) col2[1];
          bv2 = (gint) col2[2];
327 328 329 330 331 332
          gimp_rgb_to_hsv_int (&av0, &av1, &av2);
          gimp_rgb_to_hsv_int (&bv0, &bv1, &bv2);
          max = abs (av1 - bv1);
          break;

        case GIMP_SELECT_CRITERION_V:
333 334 335 336 337 338
          av0 = (gint) col1[0];
          av1 = (gint) col1[1];
          av2 = (gint) col1[2];
          bv0 = (gint) col2[0];
          bv1 = (gint) col2[1];
          bv2 = (gint) col2[2];
339 340 341 342
          gimp_rgb_to_hsv_int (&av0, &av1, &av2);
          gimp_rgb_to_hsv_int (&bv0, &bv1, &bv2);
          max = abs (av2 - bv2);
          break;
343
#endif
344
        }
345 346
    }

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

351
      if (aa <= 0.0)
352
        return 0.0;
353
      else if (aa < 0.5)
354
        return aa * 2.0;
355
      else
356
        return 1.0;
357 358 359 360
    }
  else
    {
      if (max > threshold)
361
        return 0.0;
362
      else
363
        return 1.0;
364 365 366
    }
}

367
static gboolean
368
find_contiguous_segment (const gfloat        *col,
369 370 371
                         GeglBuffer          *src_buffer,
                         GeglBuffer          *mask_buffer,
                         const Babl          *src_format,
372
                         gint                 n_components,
373
                         gboolean             has_alpha,
374
                         gint                 width,
375 376 377
                         gboolean             select_transparent,
                         GimpSelectCriterion  select_criterion,
                         gboolean             antialias,
378
                         gfloat               threshold,
379 380
                         gint                 initial_x,
                         gint                 initial_y,
381 382
                         gint                *start,
                         gint                *end)
383
{
384 385 386
  gfloat s[MAX_CHANNELS];
  gfloat mask_row[width];
  gfloat diff;
387

388 389
  gegl_buffer_sample (src_buffer, initial_x, initial_y, NULL, s, src_format,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
390

391
  diff = pixel_difference (col, s, antialias, threshold,
392
                           n_components, has_alpha, select_transparent,
393
                           select_criterion);
394

395
  /* check the starting pixel */
396
  if (! diff)
397 398 399
    return FALSE;

  mask_row[initial_x] = diff;
400

401
  *start = initial_x - 1;
402 403 404

  while (*start >= 0 && diff)
    {
405 406
      gegl_buffer_sample (src_buffer, *start, initial_y, NULL, s, src_format,
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
407

408
      diff = pixel_difference (col, s, antialias, threshold,
409
                               n_components, has_alpha, select_transparent,
410
                               select_criterion);
411

412
      mask_row[*start] = diff;
413

414 415
      if (diff)
        (*start)--;
416 417 418
    }

  diff = 1;
419
  *end = initial_x + 1;
420 421 422

  while (*end < width && diff)
    {
423 424
      gegl_buffer_sample (src_buffer, *end, initial_y, NULL, s, src_format,
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
425

426
      diff = pixel_difference (col, s, antialias, threshold,
427
                               n_components, has_alpha, select_transparent,
428
                               select_criterion);
429

430
      mask_row[*end] = diff;
431

432 433
      if (diff)
        (*end)++;
434 435
    }

436 437
  gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (*start, initial_y,
                                                *end - *start, 1),
438
                   0, babl_format ("Y float"), &mask_row[*start],
439 440 441 442
                   GEGL_AUTO_ROWSTRIDE);

  /* XXX this should now be needed and is a performance killer */
  gegl_buffer_sample_cleanup (mask_buffer);
443 444 445 446 447

  return TRUE;
}

static void
448 449 450
find_contiguous_region_helper (GeglBuffer          *src_buffer,
                               GeglBuffer          *mask_buffer,
                               const Babl          *format,
451 452 453
                               gboolean             select_transparent,
                               GimpSelectCriterion  select_criterion,
                               gboolean             antialias,
454
                               gfloat               threshold,
455 456
                               gint                 x,
                               gint                 y,
457
                               const gfloat        *col)
458
{
459 460
  gint    start, end;
  gint    new_start, new_end;
461 462
  GQueue *coord_stack;

463
  coord_stack = g_queue_new ();
464 465 466 467 468 469 470 471 472

  /* To avoid excessive memory allocation (y, start, end) tuples are
   * stored in interleaved format:
   *
   * [y1] [start1] [end1] [y2] [start2] [end2]
   */
  g_queue_push_tail (coord_stack, GINT_TO_POINTER (y));
  g_queue_push_tail (coord_stack, GINT_TO_POINTER (x - 1));
  g_queue_push_tail (coord_stack, GINT_TO_POINTER (x + 1));
473

474 475 476 477 478
  do
    {
      y     = GPOINTER_TO_INT (g_queue_pop_head (coord_stack));
      start = GPOINTER_TO_INT (g_queue_pop_head (coord_stack));
      end   = GPOINTER_TO_INT (g_queue_pop_head (coord_stack));
479

480 481
      for (x = start + 1; x < end; x++)
        {
482 483
          gfloat val;

484
          gegl_buffer_sample (mask_buffer, x, y, NULL, &val,
485
                              babl_format ("Y float"),
486
                              GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
487
          if (val != 0.0)
488 489
            continue;

490 491
          if (! find_contiguous_segment (col, src_buffer, mask_buffer,
                                         format,
492
                                         babl_format_get_n_components (format),
493 494
                                         babl_format_has_alpha (format),
                                         gegl_buffer_get_width (src_buffer),
495
                                         select_transparent, select_criterion,
496
                                         antialias, threshold, x, y,
497
                                         &new_start, &new_end))
498
            continue;
499

500
          if (y + 1 < gegl_buffer_get_height (src_buffer))
501 502 503 504 505
            {
              g_queue_push_tail (coord_stack, GINT_TO_POINTER (y + 1));
              g_queue_push_tail (coord_stack, GINT_TO_POINTER (new_start));
              g_queue_push_tail (coord_stack, GINT_TO_POINTER (new_end));
            }
506

507
          if (y - 1 >= 0)
508 509 510 511 512 513
            {
              g_queue_push_tail (coord_stack, GINT_TO_POINTER (y - 1));
              g_queue_push_tail (coord_stack, GINT_TO_POINTER (new_start));
              g_queue_push_tail (coord_stack, GINT_TO_POINTER (new_end));
            }
        }
514
    }
515
  while (! g_queue_is_empty (coord_stack));
516 517

  g_queue_free (coord_stack);
518
}