home *** CD-ROM | disk | FTP | other *** search
- /* 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"
-
- #include <stdlib.h>
-
- #include <gtk/gtk.h>
-
- #include "apptypes.h"
-
- #include "appenv.h"
- #include "boundary.h"
- #include "cursorutil.h"
- #include "draw_core.h"
- #include "drawable.h"
- #include "edit_selection.h"
- #include "fuzzy_select.h"
- #include "gimage_mask.h"
- #include "gimprc.h"
- #include "gimpui.h"
- #include "gdisplay.h"
- #include "rect_select.h"
- #include "selection_options.h"
-
- #include "tile.h" /* ick. */
-
- #include "libgimp/gimpmath.h"
- #include "libgimp/gimpintl.h"
-
- /* the fuzzy selection structures */
-
- typedef struct _FuzzySelect FuzzySelect;
- struct _FuzzySelect
- {
- DrawCore *core; /* Core select object */
-
- gint op; /* selection operation (ADD, SUB, etc) */
-
- gint current_x; /* these values are updated on every motion event */
- gint current_y; /* (enables immediate cursor updating on modifier
- * key events). */
-
- gint x, y; /* Point from which to execute seed fill */
- gint first_x; /* */
- gint first_y; /* variables to keep track of sensitivity */
- gdouble first_threshold; /* initial value of threshold slider */
- };
-
-
- /* the fuzzy selection tool options */
- static SelectionOptions *fuzzy_options = NULL;
-
- /* XSegments which make up the fuzzy selection boundary */
- static GdkSegment *segs = NULL;
- static gint num_segs = 0;
-
- Channel * fuzzy_mask = NULL;
-
-
- /* fuzzy select action functions */
- static void fuzzy_select_button_press (Tool *, GdkEventButton *, gpointer);
- static void fuzzy_select_button_release (Tool *, GdkEventButton *, gpointer);
- static void fuzzy_select_motion (Tool *, GdkEventMotion *, gpointer);
- static void fuzzy_select_control (Tool *, ToolAction, gpointer);
-
- static void fuzzy_select_draw (Tool *);
-
- /* fuzzy select action functions */
- static GdkSegment * fuzzy_select_calculate (Tool *, void *, int *);
-
-
- /*************************************/
- /* Fuzzy selection apparatus */
-
- static gint
- is_pixel_sufficiently_different (guchar *col1,
- guchar *col2,
- gboolean antialias,
- gint threshold,
- gint bytes,
- gboolean has_alpha)
- {
- gint diff;
- gint max;
- gint b;
- gint alpha;
-
- max = 0;
- alpha = (has_alpha) ? bytes - 1 : bytes;
-
- /* if there is an alpha channel, never select transparent regions */
- if (has_alpha && col2[alpha] == 0)
- return 0;
-
- for (b = 0; b < bytes; b++)
- {
- diff = col1[b] - col2[b];
- diff = abs (diff);
- if (diff > max)
- max = diff;
- }
-
- if (antialias)
- {
- float aa;
-
- aa = 1.5 - ((float) max / threshold);
- if (aa <= 0)
- return 0;
- else if (aa < 0.5)
- return (unsigned char) (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 % TILE_WIDTH, y % TILE_HEIGHT);
- *m = tile_data_pointer (*m_tile, x % TILE_WIDTH, y % TILE_HEIGHT);
- }
-
- static int
- find_contiguous_segment (guchar *col,
- PixelRegion *src,
- PixelRegion *mask,
- gint width,
- gint bytes,
- gboolean has_alpha,
- gboolean antialias,
- gint threshold,
- gint initial,
- gint *start,
- gint *end)
- {
- guchar *s;
- guchar *m;
- guchar diff;
- Tile *s_tile = NULL;
- Tile *m_tile = NULL;
-
- ref_tiles (src->tiles, mask->tiles, &s_tile, &m_tile, src->x, src->y, &s, &m);
-
- /* check the starting pixel */
- if (! (diff = is_pixel_sufficiently_different (col, s, antialias,
- threshold, bytes, has_alpha)))
- {
- 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);
-
- diff = is_pixel_sufficiently_different (col, s, antialias,
- threshold, bytes, has_alpha);
- 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);
-
- diff = is_pixel_sufficiently_different (col, s, antialias,
- threshold, bytes, has_alpha);
- if ((*m++ = diff))
- {
- s += bytes;
- (*end)++;
- }
- }
-
- tile_release (s_tile, FALSE);
- tile_release (m_tile, TRUE);
- return TRUE;
- }
-
- static void
- find_contiguous_region_helper (PixelRegion *mask,
- PixelRegion *src,
- gboolean has_alpha,
- gboolean antialias,
- gint threshold,
- gboolean indexed,
- gint x,
- gint y,
- guchar *col)
- {
- gint start, end, i;
- gint val;
- gint bytes;
-
- Tile *tile;
-
- if (threshold == 0) threshold = 1;
- 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);
- val = *(guchar *)(tile_data_pointer (tile,
- x % TILE_WIDTH, y % TILE_HEIGHT));
- tile_release (tile, FALSE);
- if (val != 0)
- return;
-
- src->x = x;
- src->y = y;
-
- bytes = src->bytes;
- if(indexed)
- {
- bytes = has_alpha ? 4 : 3;
- }
-
- if (! find_contiguous_segment (col, src, mask, src->w,
- src->bytes, has_alpha,
- antialias, threshold, x, &start, &end))
- return;
-
- for (i = start + 1; i < end; i++)
- {
- find_contiguous_region_helper (mask, src, has_alpha, antialias,
- threshold, indexed, i, y - 1, col);
- find_contiguous_region_helper (mask, src, has_alpha, antialias,
- threshold, indexed, i, y + 1, col);
- }
- }
-
- Channel *
- find_contiguous_region (GImage *gimage,
- GimpDrawable *drawable,
- gboolean antialias,
- gint threshold,
- gint x,
- gint y,
- gboolean sample_merged)
- {
- PixelRegion srcPR, maskPR;
- Channel *mask;
- guchar *start;
- gboolean has_alpha;
- gboolean indexed;
- gint type;
- gint bytes;
- Tile *tile;
-
- if (sample_merged)
- {
- pixel_region_init (&srcPR, gimage_composite (gimage), 0, 0,
- gimage->width, gimage->height, FALSE);
- type = gimage_composite_type (gimage);
- has_alpha = (type == RGBA_GIMAGE ||
- type == GRAYA_GIMAGE ||
- type == INDEXEDA_GIMAGE);
- }
- else
- {
- pixel_region_init (&srcPR, drawable_data (drawable), 0, 0,
- drawable_width (drawable), drawable_height (drawable),
- FALSE);
- has_alpha = drawable_has_alpha (drawable);
- }
- indexed = drawable_indexed (drawable);
- bytes = drawable_bytes (drawable);
-
- if (indexed)
- {
- bytes = has_alpha ? 4 : 3;
- }
- mask = channel_new_mask (gimage, srcPR.w, srcPR.h);
- pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), 0, 0,
- drawable_width (GIMP_DRAWABLE(mask)),
- drawable_height (GIMP_DRAWABLE(mask)),
- TRUE);
-
- tile = tile_manager_get_tile (srcPR.tiles, x, y, TRUE, FALSE);
- if (tile)
- {
- start = tile_data_pointer (tile, x%TILE_WIDTH, y%TILE_HEIGHT);
-
- find_contiguous_region_helper (&maskPR, &srcPR, has_alpha, antialias,
- threshold, bytes, x, y, start);
-
- tile_release (tile, FALSE);
- }
-
- return mask;
- }
-
- void
- fuzzy_select (GImage *gimage,
- GimpDrawable *drawable,
- gint op,
- gboolean feather,
- gdouble feather_radius)
- {
- gint off_x, off_y;
-
- /* if applicable, replace the current selection */
- if (op == REPLACE)
- gimage_mask_clear (gimage);
- else
- gimage_mask_undo (gimage);
-
- if (drawable) /* NULL if sample_merged is active */
- drawable_offsets (drawable, &off_x, &off_y);
- else
- off_x = off_y = 0;
-
- if (feather)
- channel_feather (fuzzy_mask, gimage_get_mask (gimage),
- feather_radius,
- feather_radius,
- op, off_x, off_y);
- else
- channel_combine_mask (gimage_get_mask (gimage),
- fuzzy_mask, op, off_x, off_y);
-
- /* free the fuzzy region struct */
- channel_delete (fuzzy_mask);
- fuzzy_mask = NULL;
- }
-
- /* fuzzy select action functions */
-
- static void
- fuzzy_select_button_press (Tool *tool,
- GdkEventButton *bevent,
- gpointer gdisp_ptr)
- {
- GDisplay *gdisp;
- FuzzySelect *fuzzy_sel;
-
- gdisp = (GDisplay *) gdisp_ptr;
- fuzzy_sel = (FuzzySelect *) tool->private;
-
- fuzzy_sel->x = bevent->x;
- fuzzy_sel->y = bevent->y;
- fuzzy_sel->first_x = fuzzy_sel->x;
- fuzzy_sel->first_y = fuzzy_sel->y;
- fuzzy_sel->first_threshold = fuzzy_options->threshold;
-
- gdk_pointer_grab (gdisp->canvas->window, FALSE,
- GDK_POINTER_MOTION_HINT_MASK |
- GDK_BUTTON1_MOTION_MASK |
- GDK_BUTTON_RELEASE_MASK,
- NULL, NULL, bevent->time);
-
- tool->state = ACTIVE;
- tool->gdisp_ptr = gdisp;
-
- if (fuzzy_sel->op == SELECTION_MOVE_MASK)
- {
- init_edit_selection (tool, gdisp_ptr, bevent, EDIT_MASK_TRANSLATE);
- return;
- }
- else if (fuzzy_sel->op == SELECTION_MOVE)
- {
- init_edit_selection (tool, gdisp_ptr, bevent, EDIT_MASK_TO_LAYER_TRANSLATE);
- return;
- }
-
- /* calculate the region boundary */
- segs = fuzzy_select_calculate (tool, gdisp_ptr, &num_segs);
-
- draw_core_start (fuzzy_sel->core,
- gdisp->canvas->window,
- tool);
- }
-
- static void
- fuzzy_select_button_release (Tool *tool,
- GdkEventButton *bevent,
- gpointer gdisp_ptr)
- {
- FuzzySelect *fuzzy_sel;
- GDisplay *gdisp;
- GimpDrawable *drawable;
-
- gdisp = (GDisplay *) gdisp_ptr;
- fuzzy_sel = (FuzzySelect *) tool->private;
-
- gdk_pointer_ungrab (bevent->time);
- gdk_flush ();
-
- draw_core_stop (fuzzy_sel->core, tool);
- tool->state = INACTIVE;
-
- /* First take care of the case where the user "cancels" the action */
- if (! (bevent->state & GDK_BUTTON3_MASK))
- {
- drawable = (fuzzy_options->sample_merged ?
- NULL : gimage_active_drawable (gdisp->gimage));
-
- fuzzy_select (gdisp->gimage, drawable, fuzzy_sel->op,
- fuzzy_options->feather,
- fuzzy_options->feather_radius);
- gdisplays_flush ();
- }
-
- /* If the segment array is allocated, free it */
- if (segs)
- g_free (segs);
- segs = NULL;
- }
-
- static void
- fuzzy_select_motion (Tool *tool,
- GdkEventMotion *mevent,
- gpointer gdisp_ptr)
- {
- FuzzySelect *fuzzy_sel;
- GdkSegment *new_segs;
- gint num_new_segs;
- gint diff_x, diff_y;
- gdouble diff;
-
- static guint last_time = 0;
-
- fuzzy_sel = (FuzzySelect *) tool->private;
-
- /* needed for immediate cursor update on modifier event */
- fuzzy_sel->current_x = mevent->x;
- fuzzy_sel->current_y = mevent->y;
-
- if (tool->state != ACTIVE)
- return;
-
- /* don't let the events come in too fast, ignore below a delay of 100 ms */
- if (ABS (mevent->time - last_time) < 100)
- return;
-
- last_time = mevent->time;
-
- diff_x = mevent->x - fuzzy_sel->first_x;
- diff_y = mevent->y - fuzzy_sel->first_y;
-
- diff = ((ABS (diff_x) > ABS (diff_y)) ? diff_x : diff_y) / 2.0;
-
- gtk_adjustment_set_value (GTK_ADJUSTMENT (fuzzy_options->threshold_w),
- fuzzy_sel->first_threshold + diff);
-
- /* calculate the new fuzzy boundary */
- new_segs = fuzzy_select_calculate (tool, gdisp_ptr, &num_new_segs);
-
- /* stop the current boundary */
- draw_core_pause (fuzzy_sel->core, tool);
-
- /* make sure the XSegment array is freed before we assign the new one */
- if (segs)
- g_free (segs);
- segs = new_segs;
- num_segs = num_new_segs;
-
- /* start the new boundary */
- draw_core_resume (fuzzy_sel->core, tool);
- }
-
-
- static GdkSegment *
- fuzzy_select_calculate (Tool *tool,
- void *gdisp_ptr,
- gint *nsegs)
- {
- PixelRegion maskPR;
- FuzzySelect *fuzzy_sel;
- GDisplay *gdisp;
- Channel *new;
- GdkSegment *segs;
- BoundSeg *bsegs;
- GimpDrawable *drawable;
- gint i;
- gint x, y;
- gboolean use_offsets;
-
- fuzzy_sel = (FuzzySelect *) tool->private;
- gdisp = (GDisplay *) gdisp_ptr;
- drawable = gimage_active_drawable (gdisp->gimage);
-
- gimp_add_busy_cursors ();
-
- use_offsets = fuzzy_options->sample_merged ? FALSE : TRUE;
-
- gdisplay_untransform_coords (gdisp, fuzzy_sel->x,
- fuzzy_sel->y, &x, &y, FALSE, use_offsets);
-
- new = find_contiguous_region (gdisp->gimage, drawable,
- fuzzy_options->antialias,
- fuzzy_options->threshold, x, y,
- fuzzy_options->sample_merged);
-
- if (fuzzy_mask)
- channel_delete (fuzzy_mask);
- fuzzy_mask = channel_ref (new);
-
- /* calculate and allocate a new XSegment array which represents the boundary
- * of the color-contiguous region
- */
- pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE (fuzzy_mask)), 0, 0,
- drawable_width (GIMP_DRAWABLE (fuzzy_mask)),
- drawable_height (GIMP_DRAWABLE (fuzzy_mask)),
- FALSE);
- bsegs = find_mask_boundary (&maskPR, nsegs, WithinBounds,
- 0, 0,
- drawable_width (GIMP_DRAWABLE (fuzzy_mask)),
- drawable_height (GIMP_DRAWABLE (fuzzy_mask)));
-
- segs = g_new (GdkSegment, *nsegs);
-
- for (i = 0; i < *nsegs; i++)
- {
- gdisplay_transform_coords (gdisp, bsegs[i].x1, bsegs[i].y1, &x, &y, use_offsets);
- segs[i].x1 = x; segs[i].y1 = y;
- gdisplay_transform_coords (gdisp, bsegs[i].x2, bsegs[i].y2, &x, &y, use_offsets);
- segs[i].x2 = x; segs[i].y2 = y;
- }
-
- /* free boundary segments */
- g_free (bsegs);
-
- gimp_remove_busy_cursors (NULL);
-
- return segs;
- }
-
- static void
- fuzzy_select_draw (Tool *tool)
- {
- FuzzySelect *fuzzy_sel;
-
- fuzzy_sel = (FuzzySelect *) tool->private;
-
- if (segs)
- gdk_draw_segments (fuzzy_sel->core->win, fuzzy_sel->core->gc, segs, num_segs);
- }
-
- static void
- fuzzy_select_control (Tool *tool,
- ToolAction action,
- gpointer gdisp_ptr)
- {
- FuzzySelect *fuzzy_sel;
-
- fuzzy_sel = (FuzzySelect *) tool->private;
-
- switch (action)
- {
- case PAUSE :
- draw_core_pause (fuzzy_sel->core, tool);
- break;
-
- case RESUME :
- draw_core_resume (fuzzy_sel->core, tool);
- break;
-
- case HALT :
- draw_core_stop (fuzzy_sel->core, tool);
- break;
-
- default:
- break;
- }
- }
-
- static void
- fuzzy_select_options_reset (void)
- {
- selection_options_reset (fuzzy_options);
- }
-
- Tool *
- tools_new_fuzzy_select (void)
- {
- Tool *tool;
- FuzzySelect *private;
-
- /* The tool options */
- if (! fuzzy_options)
- {
- fuzzy_options = selection_options_new (FUZZY_SELECT,
- fuzzy_select_options_reset);
- tools_register (FUZZY_SELECT, (ToolOptions *) fuzzy_options);
- }
-
- tool = tools_new_tool (FUZZY_SELECT);
- private = g_new0 (FuzzySelect, 1);
-
- private->core = draw_core_new (fuzzy_select_draw);
-
- tool->scroll_lock = TRUE; /* Disallow scrolling */
-
- tool->private = (void *) private;
-
- tool->button_press_func = fuzzy_select_button_press;
- tool->button_release_func = fuzzy_select_button_release;
- tool->motion_func = fuzzy_select_motion;
- tool->modifier_key_func = rect_select_modifier_update;
- tool->cursor_update_func = rect_select_cursor_update;
- tool->oper_update_func = rect_select_oper_update;
- tool->control_func = fuzzy_select_control;
-
- return tool;
- }
-
- void
- tools_free_fuzzy_select (Tool *tool)
- {
- FuzzySelect *fuzzy_sel;
-
- fuzzy_sel = (FuzzySelect *) tool->private;
- draw_core_free (fuzzy_sel->core);
- g_free (fuzzy_sel);
- }
-