mirror of https://github.com/GNOME/gimp.git
669 lines
22 KiB
C
669 lines
22 KiB
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 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"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <gegl.h>
|
|
|
|
#include "libgimpcolor/gimpcolor.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "base/pixel-processor.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"
|
|
#include "gimppickable.h"
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GimpImage *image;
|
|
GimpImageType type;
|
|
gboolean sample_merged;
|
|
gboolean antialias;
|
|
gint threshold;
|
|
gboolean select_transparent;
|
|
GimpSelectCriterion select_criterion;
|
|
gboolean has_alpha;
|
|
guchar color[MAX_CHANNELS];
|
|
} ContinuousRegionData;
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void contiguous_region_by_color (ContinuousRegionData *cont,
|
|
PixelRegion *imagePR,
|
|
PixelRegion *maskPR);
|
|
|
|
static gint pixel_difference (const guchar *col1,
|
|
const guchar *col2,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gint bytes,
|
|
gboolean has_alpha,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion);
|
|
static void ref_tiles (TileManager *src,
|
|
TileManager *mask,
|
|
Tile **s_tile,
|
|
Tile **m_tile,
|
|
gint x,
|
|
gint y,
|
|
guchar **s,
|
|
guchar **m);
|
|
static gboolean find_contiguous_segment (GimpImage *image,
|
|
const guchar *col,
|
|
PixelRegion *src,
|
|
PixelRegion *mask,
|
|
gint width,
|
|
gint bytes,
|
|
GimpImageType src_type,
|
|
gboolean has_alpha,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gint initial,
|
|
gint *start,
|
|
gint *end);
|
|
static void find_contiguous_region_helper (GimpImage *image,
|
|
PixelRegion *mask,
|
|
PixelRegion *src,
|
|
GimpImageType src_type,
|
|
gboolean has_alpha,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gint x,
|
|
gint y,
|
|
const guchar *col);
|
|
|
|
|
|
/* public functions */
|
|
|
|
GimpChannel *
|
|
gimp_image_contiguous_region_by_seed (GimpImage *image,
|
|
GimpDrawable *drawable,
|
|
gboolean sample_merged,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
PixelRegion srcPR, maskPR;
|
|
GimpPickable *pickable;
|
|
TileManager *tiles;
|
|
GimpChannel *mask;
|
|
GimpImageType src_type;
|
|
gboolean has_alpha;
|
|
gint bytes;
|
|
Tile *tile;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
|
|
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
|
|
|
|
if (sample_merged)
|
|
pickable = GIMP_PICKABLE (image->projection);
|
|
else
|
|
pickable = GIMP_PICKABLE (drawable);
|
|
|
|
gimp_pickable_flush (pickable);
|
|
|
|
src_type = gimp_pickable_get_image_type (pickable);
|
|
has_alpha = GIMP_IMAGE_TYPE_HAS_ALPHA (src_type);
|
|
bytes = GIMP_IMAGE_TYPE_BYTES (src_type);
|
|
|
|
tiles = gimp_pickable_get_tiles (pickable);
|
|
pixel_region_init (&srcPR, tiles,
|
|
0, 0,
|
|
tile_manager_width (tiles),
|
|
tile_manager_height (tiles),
|
|
FALSE);
|
|
|
|
mask = gimp_channel_new_mask (image, srcPR.w, srcPR.h);
|
|
pixel_region_init (&maskPR, gimp_drawable_get_tiles (GIMP_DRAWABLE (mask)),
|
|
0, 0,
|
|
gimp_item_get_width (GIMP_ITEM (mask)),
|
|
gimp_item_get_height (GIMP_ITEM (mask)),
|
|
TRUE);
|
|
|
|
tile = tile_manager_get_tile (srcPR.tiles, x, y, TRUE, FALSE);
|
|
if (tile)
|
|
{
|
|
const guchar *start;
|
|
guchar start_col[MAX_CHANNELS];
|
|
|
|
start = tile_data_pointer (tile, x, y);
|
|
|
|
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;
|
|
}
|
|
|
|
if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
|
|
{
|
|
gimp_image_get_color (image, src_type, start, start_col);
|
|
}
|
|
else
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < bytes; i++)
|
|
start_col[i] = start[i];
|
|
}
|
|
|
|
find_contiguous_region_helper (image, &maskPR, &srcPR,
|
|
src_type, has_alpha,
|
|
select_transparent, select_criterion,
|
|
antialias, threshold,
|
|
x, y, start_col);
|
|
|
|
tile_release (tile, FALSE);
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
GimpChannel *
|
|
gimp_image_contiguous_region_by_color (GimpImage *image,
|
|
GimpDrawable *drawable,
|
|
gboolean sample_merged,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion,
|
|
const GimpRGB *color)
|
|
{
|
|
/* Scan over the image'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 image's mask to reflect the
|
|
* additional selection
|
|
*/
|
|
GimpPickable *pickable;
|
|
TileManager *tiles;
|
|
GimpChannel *mask;
|
|
PixelRegion imagePR, maskPR;
|
|
gint width, height;
|
|
|
|
ContinuousRegionData cont;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
|
|
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
|
|
gimp_rgba_get_uchar (color,
|
|
cont.color + 0,
|
|
cont.color + 1,
|
|
cont.color + 2,
|
|
cont.color + 3);
|
|
|
|
if (sample_merged)
|
|
pickable = GIMP_PICKABLE (image->projection);
|
|
else
|
|
pickable = GIMP_PICKABLE (drawable);
|
|
|
|
gimp_pickable_flush (pickable);
|
|
|
|
cont.type = gimp_pickable_get_image_type (pickable);
|
|
cont.has_alpha = GIMP_IMAGE_TYPE_HAS_ALPHA (cont.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);
|
|
|
|
if (cont.has_alpha)
|
|
{
|
|
if (select_transparent)
|
|
{
|
|
/* don't select transparancy if "color" isn't fully transparent
|
|
*/
|
|
if (cont.color[3] > 0)
|
|
select_transparent = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
select_transparent = FALSE;
|
|
}
|
|
|
|
cont.image = image;
|
|
cont.antialias = antialias;
|
|
cont.threshold = threshold;
|
|
cont.select_transparent = select_transparent;
|
|
cont.select_criterion = select_criterion;
|
|
|
|
mask = gimp_channel_new_mask (image, width, height);
|
|
|
|
pixel_region_init (&maskPR, gimp_drawable_get_tiles (GIMP_DRAWABLE (mask)),
|
|
0, 0, width, height,
|
|
TRUE);
|
|
|
|
pixel_regions_process_parallel ((PixelProcessorFunc)
|
|
contiguous_region_by_color, &cont,
|
|
2, &imagePR, &maskPR);
|
|
|
|
return mask;
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
contiguous_region_by_color (ContinuousRegionData *cont,
|
|
PixelRegion *imagePR,
|
|
PixelRegion *maskPR)
|
|
{
|
|
const guchar *image = imagePR->data;
|
|
guchar *mask = maskPR->data;
|
|
gint x, y;
|
|
|
|
for (y = 0; y < imagePR->h; y++)
|
|
{
|
|
const guchar *i = image;
|
|
guchar *m = mask;
|
|
|
|
for (x = 0; x < imagePR->w; x++)
|
|
{
|
|
guchar rgb[MAX_CHANNELS];
|
|
|
|
/* Get the rgb values for the color */
|
|
gimp_image_get_color (cont->image, cont->type, i, rgb);
|
|
|
|
/* Find how closely the colors match */
|
|
*m++ = pixel_difference (cont->color, rgb,
|
|
cont->antialias,
|
|
cont->threshold,
|
|
cont->has_alpha ? 4 : 3,
|
|
cont->has_alpha,
|
|
cont->select_transparent,
|
|
cont->select_criterion);
|
|
|
|
i += imagePR->bytes;
|
|
}
|
|
|
|
image += imagePR->rowstride;
|
|
mask += maskPR->rowstride;
|
|
}
|
|
}
|
|
|
|
static gint
|
|
pixel_difference (const guchar *col1,
|
|
const guchar *col2,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gint bytes,
|
|
gboolean has_alpha,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion)
|
|
{
|
|
gint max = 0;
|
|
|
|
/* if there is an alpha channel, never select transparent regions */
|
|
if (! select_transparent && has_alpha && col2[bytes - 1] == 0)
|
|
return 0;
|
|
|
|
if (select_transparent && has_alpha)
|
|
{
|
|
max = abs (col1[bytes - 1] - col2[bytes - 1]);
|
|
}
|
|
else
|
|
{
|
|
gint diff;
|
|
gint b;
|
|
gint av0, av1, av2;
|
|
gint bv0, bv1, bv2;
|
|
|
|
if (has_alpha)
|
|
bytes--;
|
|
|
|
switch (select_criterion)
|
|
{
|
|
case GIMP_SELECT_CRITERION_COMPOSITE:
|
|
for (b = 0; b < bytes; b++)
|
|
{
|
|
diff = abs (col1[b] - col2[b]);
|
|
if (diff > max)
|
|
max = diff;
|
|
}
|
|
break;
|
|
|
|
case GIMP_SELECT_CRITERION_R:
|
|
max = abs (col1[0] - col2[0]);
|
|
break;
|
|
|
|
case GIMP_SELECT_CRITERION_G:
|
|
max = abs (col1[1] - col2[1]);
|
|
break;
|
|
|
|
case GIMP_SELECT_CRITERION_B:
|
|
max = abs (col1[2] - col2[2]);
|
|
break;
|
|
|
|
case GIMP_SELECT_CRITERION_H:
|
|
av0 = (gint) col1[0];
|
|
av1 = (gint) col1[1];
|
|
av2 = (gint) col1[2];
|
|
bv0 = (gint) col2[0];
|
|
bv1 = (gint) col2[1];
|
|
bv2 = (gint) col2[2];
|
|
gimp_rgb_to_hsv_int (&av0, &av1, &av2);
|
|
gimp_rgb_to_hsv_int (&bv0, &bv1, &bv2);
|
|
/* 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;
|
|
}
|
|
break;
|
|
|
|
case GIMP_SELECT_CRITERION_S:
|
|
av0 = (gint) col1[0];
|
|
av1 = (gint) col1[1];
|
|
av2 = (gint) col1[2];
|
|
bv0 = (gint) col2[0];
|
|
bv1 = (gint) col2[1];
|
|
bv2 = (gint) col2[2];
|
|
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:
|
|
av0 = (gint) col1[0];
|
|
av1 = (gint) col1[1];
|
|
av2 = (gint) col1[2];
|
|
bv0 = (gint) col2[0];
|
|
bv1 = (gint) col2[1];
|
|
bv2 = (gint) col2[2];
|
|
gimp_rgb_to_hsv_int (&av0, &av1, &av2);
|
|
gimp_rgb_to_hsv_int (&bv0, &bv1, &bv2);
|
|
max = abs (av2 - bv2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (antialias && threshold > 0)
|
|
{
|
|
gfloat aa = 1.5 - ((gfloat) max / threshold);
|
|
|
|
if (aa <= 0.0)
|
|
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
|
|
ref_tiles (TileManager *src,
|
|
TileManager *mask,
|
|
Tile **s_tile,
|
|
Tile **m_tile,
|
|
gint x,
|
|
gint y,
|
|
guchar **s,
|
|
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, y);
|
|
*m = tile_data_pointer (*m_tile, x, y);
|
|
}
|
|
|
|
static gboolean
|
|
find_contiguous_segment (GimpImage *image,
|
|
const guchar *col,
|
|
PixelRegion *src,
|
|
PixelRegion *mask,
|
|
gint width,
|
|
gint bytes,
|
|
GimpImageType src_type,
|
|
gboolean has_alpha,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gint initial,
|
|
gint *start,
|
|
gint *end)
|
|
{
|
|
guchar *s;
|
|
guchar *m;
|
|
guchar s_color[MAX_CHANNELS];
|
|
guchar diff;
|
|
gint col_bytes = bytes;
|
|
Tile *s_tile = NULL;
|
|
Tile *m_tile = NULL;
|
|
|
|
ref_tiles (src->tiles, mask->tiles,
|
|
&s_tile, &m_tile, src->x, src->y, &s, &m);
|
|
|
|
if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
|
|
{
|
|
col_bytes = has_alpha ? 4 : 3;
|
|
|
|
gimp_image_get_color (image, src_type, s, s_color);
|
|
|
|
diff = pixel_difference (col, s_color, antialias, threshold,
|
|
col_bytes, has_alpha, select_transparent,
|
|
select_criterion);
|
|
}
|
|
else
|
|
{
|
|
diff = pixel_difference (col, s, antialias, threshold,
|
|
col_bytes, has_alpha, select_transparent,
|
|
select_criterion);
|
|
}
|
|
|
|
/* check the starting pixel */
|
|
if (! diff)
|
|
{
|
|
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))
|
|
ref_tiles (src->tiles, mask->tiles,
|
|
&s_tile, &m_tile, *start, src->y, &s, &m);
|
|
|
|
if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
|
|
{
|
|
gimp_image_get_color (image, src_type, s, s_color);
|
|
|
|
diff = pixel_difference (col, s_color, antialias, threshold,
|
|
col_bytes, has_alpha, select_transparent,
|
|
select_criterion);
|
|
}
|
|
else
|
|
{
|
|
diff = pixel_difference (col, s, antialias, threshold,
|
|
col_bytes, has_alpha, select_transparent,
|
|
select_criterion);
|
|
}
|
|
|
|
if ((*m-- = diff))
|
|
{
|
|
s -= bytes;
|
|
(*start)--;
|
|
}
|
|
}
|
|
|
|
diff = 1;
|
|
*end = initial + 1;
|
|
|
|
if (*end % TILE_WIDTH && *end < width)
|
|
ref_tiles (src->tiles, mask->tiles,
|
|
&s_tile, &m_tile, *end, src->y, &s, &m);
|
|
|
|
while (*end < width && diff)
|
|
{
|
|
if (! (*end % TILE_WIDTH))
|
|
ref_tiles (src->tiles, mask->tiles,
|
|
&s_tile, &m_tile, *end, src->y, &s, &m);
|
|
|
|
if (GIMP_IMAGE_TYPE_IS_INDEXED (src_type))
|
|
{
|
|
gimp_image_get_color (image, src_type, s, s_color);
|
|
|
|
diff = pixel_difference (col, s_color, antialias, threshold,
|
|
col_bytes, has_alpha, select_transparent,
|
|
select_criterion);
|
|
}
|
|
else
|
|
{
|
|
diff = pixel_difference (col, s, antialias, threshold,
|
|
col_bytes, has_alpha, select_transparent,
|
|
select_criterion);
|
|
}
|
|
|
|
if ((*m++ = diff))
|
|
{
|
|
s += bytes;
|
|
(*end)++;
|
|
}
|
|
}
|
|
|
|
tile_release (s_tile, FALSE);
|
|
tile_release (m_tile, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
find_contiguous_region_helper (GimpImage *image,
|
|
PixelRegion *mask,
|
|
PixelRegion *src,
|
|
GimpImageType src_type,
|
|
gboolean has_alpha,
|
|
gboolean select_transparent,
|
|
GimpSelectCriterion select_criterion,
|
|
gboolean antialias,
|
|
gint threshold,
|
|
gint x,
|
|
gint y,
|
|
const guchar *col)
|
|
{
|
|
gint start, end;
|
|
gint new_start, new_end;
|
|
gint val;
|
|
Tile *tile;
|
|
GQueue *coord_stack;
|
|
|
|
coord_stack = g_queue_new ();
|
|
|
|
/* 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));
|
|
|
|
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));
|
|
|
|
for (x = start + 1; x < end; x++)
|
|
{
|
|
tile = tile_manager_get_tile (mask->tiles, x, y, TRUE, FALSE);
|
|
val = *(const guchar *) tile_data_pointer (tile, x, y);
|
|
tile_release (tile, FALSE);
|
|
if (val != 0)
|
|
continue;
|
|
|
|
src->x = x;
|
|
src->y = y;
|
|
|
|
if (! find_contiguous_segment (image, col, src, mask, src->w,
|
|
src->bytes, src_type, has_alpha,
|
|
select_transparent, select_criterion,
|
|
antialias, threshold, x,
|
|
&new_start, &new_end))
|
|
continue;
|
|
|
|
if (y + 1 < src->h)
|
|
{
|
|
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));
|
|
}
|
|
|
|
if (y - 1 >= 0)
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
while (! g_queue_is_empty (coord_stack));
|
|
|
|
g_queue_free (coord_stack);
|
|
}
|