gimpimage-contiguous-region.c 16.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* The GIMP -- an 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

21 22
#include <stdlib.h>

23 24 25 26 27 28 29 30 31 32 33 34 35
#include <glib-object.h>

#include "libgimpcolor/gimpcolor.h"

#include "core-types.h"

#include "base/pixel-region.h"
#include "base/tile.h"
#include "base/tile-manager.h"

#include "gimpchannel.h"
#include "gimpimage.h"
#include "gimpimage-contiguous-region.h"
36
#include "gimppickable.h"
37 38 39 40


/*  local function prototypes  */

41 42
static gint pixel_difference              (guchar       *col1,
                                           guchar       *col2,
43 44
                                           gboolean      antialias,
                                           gint          threshold,
45 46 47
                                           gint          bytes,
                                           gboolean      has_alpha,
                                           gboolean      select_transparent);
48 49 50
static void ref_tiles                     (TileManager  *src,
                                           TileManager  *mask,
                                           Tile        **s_tile,
51
                                           Tile        **m_tile,
52 53 54
                                           gint          x,
                                           gint          y,
                                           guchar      **s,
55
                                           guchar      **m);
56 57
static gint find_contiguous_segment       (GimpImage    *gimage,
                                           guchar       *col,
58
                                           PixelRegion  *src,
59 60
                                           PixelRegion  *mask,
                                           gint          width,
61
                                           gint          bytes,
62
                                           GimpImageType src_type,
63 64 65 66
                                           gboolean      has_alpha,
                                           gboolean      select_transparent,
                                           gboolean      antialias,
                                           gint          threshold,
67 68
                                           gint          initial,
                                           gint         *start,
69
                                           gint         *end);
70 71
static void find_contiguous_region_helper (GimpImage    *gimage,
                                           PixelRegion  *mask,
72
                                           PixelRegion  *src,
73
                                           GimpImageType src_type,
74
                                           gboolean      has_alpha,
75
                                           gboolean      select_transparent,
76
                                           gboolean      antialias,
77
                                           gint          threshold,
78 79
                                           gint          x,
                                           gint          y,
80
                                           guchar       *col);
81 82 83 84 85


/*  public functions  */

GimpChannel *
86
gimp_image_contiguous_region_by_seed (GimpImage    *gimage,
87
                                      GimpDrawable *drawable,
88 89 90
                                      gboolean      sample_merged,
                                      gboolean      antialias,
                                      gint          threshold,
91
                                      gboolean      select_transparent,
92 93 94
                                      gint          x,
                                      gint          y)
{
95
  PixelRegion    srcPR, maskPR;
96 97
  GimpPickable  *pickable;
  TileManager   *tiles;
98 99 100 101 102
  GimpChannel   *mask;
  GimpImageType  src_type;
  gboolean       has_alpha;
  gint           bytes;
  Tile          *tile;
103 104 105 106 107

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
  g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);

  if (sample_merged)
108 109 110
    pickable = GIMP_PICKABLE (gimage->projection);
  else
    pickable = GIMP_PICKABLE (drawable);
111

112 113 114
  src_type  = gimp_pickable_get_image_type (pickable);
  has_alpha = GIMP_IMAGE_TYPE_HAS_ALPHA (src_type);
  bytes     = GIMP_IMAGE_TYPE_BYTES (src_type);
115

116 117 118 119 120 121
  tiles = gimp_pickable_get_tiles (pickable);
  pixel_region_init (&srcPR, tiles,
                     0, 0,
                     tile_manager_width (tiles),
                     tile_manager_height (tiles),
                     FALSE);
122

123
  mask = gimp_channel_new_mask (gimage, srcPR.w, srcPR.h);
124
  pixel_region_init (&maskPR, gimp_drawable_data (GIMP_DRAWABLE (mask)),
125 126 127
		     0, 0,
		     gimp_item_width  (GIMP_ITEM (mask)),
		     gimp_item_height (GIMP_ITEM (mask)),
128 129 130 131 132
		     TRUE);

  tile = tile_manager_get_tile (srcPR.tiles, x, y, TRUE, FALSE);
  if (tile)
    {
133 134 135
      guchar *start;
      guchar  start_col[MAX_CHANNELS];

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
      start = tile_data_pointer (tile, x % TILE_WIDTH, y % TILE_HEIGHT);

      if (has_alpha)
        {
          if (select_transparent)
            {
              /*  don't select transparent regions if the start pixel isn't
               *  fully transparent
               */
              if (start[bytes - 1] > 0)
                select_transparent = FALSE;
            }
        }
      else
        {
          select_transparent = FALSE;
        }
153

154 155 156 157 158 159 160 161 162 163 164 165 166 167
      if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
        {
          gimp_image_get_color (gimage, src_type, start, start_col);
        }
      else
        {
          gint i;

          for (i = 0; i < bytes; i++)
            start_col[i] = start[i];
        }

      find_contiguous_region_helper (gimage, &maskPR, &srcPR,
                                     src_type, has_alpha,
168
                                     select_transparent, antialias, threshold,
169
                                     x, y, start_col);
170 171 172 173 174 175 176 177 178 179 180 181 182

      tile_release (tile, FALSE);
    }

  return mask;
}

GimpChannel *
gimp_image_contiguous_region_by_color (GimpImage     *gimage,
                                       GimpDrawable  *drawable,
                                       gboolean       sample_merged,
                                       gboolean       antialias,
                                       gint           threshold,
183
                                       gboolean       select_transparent,
184 185 186 187 188 189 190
                                       const GimpRGB *color)
{
  /*  Scan over the gimage'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 gimage's
   *  mask to reflect the additional selection
   */
191 192
  GimpPickable  *pickable;
  TileManager   *tiles;
193 194 195 196 197 198 199 200 201 202 203 204 205
  GimpChannel   *mask;
  PixelRegion    imagePR, maskPR;
  guchar        *image_data;
  guchar        *mask_data;
  guchar        *idata, *mdata;
  guchar         rgb[MAX_CHANNELS];
  gint           has_alpha, indexed;
  gint           width, height;
  gint           bytes, color_bytes, alpha;
  gint           i, j;
  gpointer       pr;
  GimpImageType  d_type;
  guchar         col[MAX_CHANNELS];
206 207 208 209 210 211 212 213

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
  g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
  g_return_val_if_fail (color != NULL, NULL);

  gimp_rgba_get_uchar (color, &col[0], &col[1], &col[2], &col[3]);

  if (sample_merged)
214
    pickable = GIMP_PICKABLE (gimage->projection);
215
  else
216 217 218 219 220 221 222 223 224 225 226 227 228
    pickable = GIMP_PICKABLE (drawable);

  d_type    = gimp_pickable_get_image_type (pickable);
  bytes     = GIMP_IMAGE_TYPE_BYTES (d_type);
  has_alpha = GIMP_IMAGE_TYPE_HAS_ALPHA (d_type);
  indexed   = GIMP_IMAGE_TYPE_IS_INDEXED (d_type);

  tiles  = gimp_pickable_get_tiles (pickable);
  width  = tile_manager_width (tiles);
  height = tile_manager_height (tiles);

  pixel_region_init (&imagePR, tiles,
                     0, 0, width, height, FALSE);
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
  if (has_alpha)
    {
      if (select_transparent)
        {
          /*  don't select transparancy if "color" isn't fully transparent
           */
          if (col[3] > 0)
            select_transparent = FALSE;
        }
    }
  else
    {
      select_transparent = FALSE;
    }

245 246 247 248 249 250 251 252 253 254 255 256 257
  if (indexed)
    {
      /* indexed colors are always RGB or RGBA */
      color_bytes = has_alpha ? 4 : 3;
    }
  else
    {
      /* RGB, RGBA, GRAY and GRAYA colors are shaped just like the image */
      color_bytes = bytes;
    }

  alpha = bytes - 1;
  mask = gimp_channel_new_mask (gimage, width, height);
258

259
  pixel_region_init (&maskPR, gimp_drawable_data (GIMP_DRAWABLE (mask)),
260 261 262
		     0, 0,
                     width, height,
                     TRUE);
263 264 265 266 267 268 269 270 271 272 273 274 275

  /*  iterate over the entire image  */
  for (pr = pixel_regions_register (2, &imagePR, &maskPR);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      image_data = imagePR.data;
      mask_data = maskPR.data;

      for (i = 0; i < imagePR.h; i++)
	{
	  idata = image_data;
	  mdata = mask_data;
276

277 278 279
	  for (j = 0; j < imagePR.w; j++)
	    {
	      /*  Get the rgb values for the color  */
280
	      gimp_image_get_color (gimage, d_type, idata, rgb);
281 282

	      /*  Find how closely the colors match  */
283
	      *mdata++ = pixel_difference (col, rgb,
284
                                           antialias, threshold,
285
                                           color_bytes,
286
                                           has_alpha, select_transparent);
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302

	      idata += bytes;
	    }

	  image_data += imagePR.rowstride;
	  mask_data += maskPR.rowstride;
	}
    }

  return mask;
}


/*  private functions  */

static gint
303 304 305 306 307 308 309
pixel_difference (guchar   *col1,
                  guchar   *col2,
                  gboolean  antialias,
                  gint      threshold,
                  gint      bytes,
                  gboolean  has_alpha,
                  gboolean  select_transparent)
310
{
311
  gint max = 0;
312 313

  /*  if there is an alpha channel, never select transparent regions  */
314
  if (! select_transparent && has_alpha && col2[bytes - 1] == 0)
315 316
    return 0;

317
  if (select_transparent && has_alpha)
318
    {
319
      max = abs (col1[bytes - 1] - col2[bytes - 1]);
320 321 322
    }
  else
    {
323 324 325 326 327 328 329
      gint diff;
      gint b;

      if (has_alpha)
        bytes--;

      for (b = 0; b < bytes; b++)
330
        {
331
          diff = abs (col1[b] - col2[b]);
332 333 334
          if (diff > max)
            max = diff;
        }
335 336 337 338
    }

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

341
      if (aa <= 0.0)
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	return 0;
      else if (aa < 0.5)
	return (guchar) (aa * 512);
      else
	return 255;
    }
  else
    {
      if (max > threshold)
	return 0;
      else
	return 255;
    }
}

static void
358 359 360
ref_tiles (TileManager  *src,
	   TileManager  *mask,
	   Tile        **s_tile,
361
	   Tile        **m_tile,
362 363 364
	   gint          x,
	   gint          y,
	   guchar      **s,
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
	   guchar      **m)
{
  if (*s_tile != NULL)
    tile_release (*s_tile, FALSE);
  if (*m_tile != NULL)
    tile_release (*m_tile, TRUE);

  *s_tile = tile_manager_get_tile (src, x, y, TRUE, FALSE);
  *m_tile = tile_manager_get_tile (mask, x, y, TRUE, TRUE);

  *s = tile_data_pointer (*s_tile, x % TILE_WIDTH, y % TILE_HEIGHT);
  *m = tile_data_pointer (*m_tile, x % TILE_WIDTH, y % TILE_HEIGHT);
}

static int
380 381 382 383 384 385 386 387 388 389 390 391 392 393
find_contiguous_segment (GimpImage     *gimage,
                         guchar        *col,
			 PixelRegion   *src,
			 PixelRegion   *mask,
			 gint           width,
			 gint           bytes,
                         GimpImageType  src_type,
			 gboolean       has_alpha,
                         gboolean       select_transparent,
			 gboolean       antialias,
			 gint           threshold,
			 gint           initial,
			 gint          *start,
			 gint          *end)
394 395 396
{
  guchar *s;
  guchar *m;
397
  guchar  s_color[MAX_CHANNELS];
398
  guchar  diff;
399
  gint    col_bytes = bytes;
400 401
  Tile   *s_tile    = NULL;
  Tile   *m_tile    = NULL;
402

403
  ref_tiles (src->tiles, mask->tiles,
404
             &s_tile, &m_tile, src->x, src->y, &s, &m);
405

406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
  if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
    {
      col_bytes = has_alpha ? 4 : 3;

      gimp_image_get_color (gimage, src_type, s, s_color);

      diff = pixel_difference (col, s_color, antialias, threshold,
                               col_bytes, has_alpha, select_transparent);
     }
  else
    {
      diff = pixel_difference (col, s, antialias, threshold,
                               col_bytes, has_alpha, select_transparent);
    }

421
  /* check the starting pixel */
422
  if (! diff)
423 424 425 426 427 428 429 430 431 432 433 434 435
    {
      tile_release (s_tile, FALSE);
      tile_release (m_tile, TRUE);
      return FALSE;
    }

  *m-- = diff;
  s -= bytes;
  *start = initial - 1;

  while (*start >= 0 && diff)
    {
      if (! ((*start + 1) % TILE_WIDTH))
436
	ref_tiles (src->tiles, mask->tiles,
437 438
                   &s_tile, &m_tile, *start, src->y, &s, &m);

439 440 441 442 443 444 445 446 447 448 449 450
      if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
        {
          gimp_image_get_color (gimage, src_type, s, s_color);

          diff = pixel_difference (col, s_color, antialias, threshold,
                                   col_bytes, has_alpha, select_transparent);
        }
      else
        {
          diff = pixel_difference (col, s, antialias, threshold,
                                   col_bytes, has_alpha, select_transparent);
        }
451 452 453 454 455 456 457 458 459 460

      if ((*m-- = diff))
	{
	  s -= bytes;
	  (*start)--;
	}
    }

  diff = 1;
  *end = initial + 1;
461

462
  if (*end % TILE_WIDTH && *end < width)
463
    ref_tiles (src->tiles, mask->tiles,
464
               &s_tile, &m_tile, *end, src->y, &s, &m);
465 466 467 468

  while (*end < width && diff)
    {
      if (! (*end % TILE_WIDTH))
469
	ref_tiles (src->tiles, mask->tiles,
470 471
                   &s_tile, &m_tile, *end, src->y, &s, &m);

472 473 474 475 476 477 478 479 480 481 482 483
      if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
        {
          gimp_image_get_color (gimage, src_type, s, s_color);

          diff = pixel_difference (col, s_color, antialias, threshold,
                                   col_bytes, has_alpha, select_transparent);
        }
      else
        {
          diff = pixel_difference (col, s, antialias, threshold,
                                   col_bytes, has_alpha, select_transparent);
        }
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498

      if ((*m++ = diff))
	{
	  s += bytes;
	  (*end)++;
	}
    }

  tile_release (s_tile, FALSE);
  tile_release (m_tile, TRUE);

  return TRUE;
}

static void
499 500 501 502 503 504 505 506 507 508 509
find_contiguous_region_helper (GimpImage     *gimage,
                               PixelRegion   *mask,
			       PixelRegion   *src,
                               GimpImageType  src_type,
			       gboolean       has_alpha,
                               gboolean       select_transparent,
			       gboolean       antialias,
			       gint           threshold,
			       gint           x,
			       gint           y,
			       guchar        *col)
510 511 512 513 514 515 516 517 518 519
{
  gint start, end, i;
  gint val;

  Tile *tile;

  if (x < 0 || x >= src->w) return;
  if (y < 0 || y >= src->h) return;

  tile = tile_manager_get_tile (mask->tiles, x, y, TRUE, FALSE);
520
  val = *(guchar *)(tile_data_pointer (tile,
521 522 523 524 525 526 527 528
				       x % TILE_WIDTH, y % TILE_HEIGHT));
  tile_release (tile, FALSE);
  if (val != 0)
    return;

  src->x = x;
  src->y = y;

529 530
  if (! find_contiguous_segment (gimage, col, src, mask, src->w, src->bytes,
                                 src_type, has_alpha, select_transparent,
531
                                 antialias, threshold,
532
                                 x, &start, &end))
533 534 535 536
    return;

  for (i = start + 1; i < end; i++)
    {
537 538
      find_contiguous_region_helper (gimage, mask, src, src_type, has_alpha,
                                     select_transparent, antialias, threshold,
539
                                     i, y - 1, col);
540 541
      find_contiguous_region_helper (gimage, mask, src, src_type, has_alpha,
                                     select_transparent, antialias, threshold,
542
                                     i, y + 1, col);
543 544
    }
}