gimp/app/gimpchannel.c
GMT 2000 Andy Thomas 8f6bca3234 gimp/app/gimpdrawable.c gimp/app/channel.c gimp/app/layer.c
Tue Feb 15 23:27:42 GMT 2000 Andy Thomas <alt@gimp.org>

	 * gimp/app/gimpdrawable.c
	 * gimp/app/channel.c
	 * gimp/app/layer.c
	 * gimp/app/channel.h
	 * gimp/app/layer.h
	 * gimp/app/gimpimage.c
	 * gimp/app/gimpimage.h
	 * gimp/app/gimpdrawable.h
	 * gimp/tools/pdbgen/pdb/paths.pdb
	 * gimp/tools/pdbgen/pdb/layer.pdb
	 * gimp/tools/pdbgen/pdb/channel.pdb
	 * gimp/tools/pdbgen/pdb/gimage.pdb
	 * gimp/app/channel_cmds.c
	 * gimp/app/gimage_cmds.c
	 * gimp/app/internal_procs.c
	 * gimp/app/layer_cmds.c
	 * gimp/app/paths_cmds.c

	New gimp_*_set_tattoo procedures. This allows save/load plugins
        to save/restore tattoo states of layers, channels and paths. Note the
	internal tattoo state can also be set, however rigorous checks are
	performed to make sure that the internal tattoo states of layer,
	channels and paths are consistent and that the new state value is
	newval > MAX(MAX(layertattoo),MAX(channeltattoo),MAX(pathtattoo)).
2000-02-15 23:49:03 +00:00

1756 lines
41 KiB
C

/* 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 <string.h>
#include "appenv.h"
#include "channel.h"
#include "drawable.h"
#include "errors.h"
#include "gdisplay.h"
#include "gimage_mask.h"
#include "layer.h"
#include "paint_funcs.h"
#include "parasitelist.h"
#include "temp_buf.h"
#include "undo.h"
#include "gimpsignal.h"
#include "gimppreviewcache.h"
#include "libgimp/gimpintl.h"
#include "libgimp/gimpmath.h"
#include "channel_pvt.h"
#include "tile.h"
#include "gimplut.h"
#include "lut_funcs.h"
enum {
REMOVED,
LAST_SIGNAL
};
static void gimp_channel_class_init (GimpChannelClass *klass);
static void gimp_channel_init (GimpChannel *channel);
static void gimp_channel_destroy (GtkObject *object);
static guint channel_signals[LAST_SIGNAL] = { 0 };
static GimpDrawableClass *parent_class = NULL;
GtkType
gimp_channel_get_type ()
{
static GtkType channel_type = 0;
if (!channel_type)
{
GtkTypeInfo channel_info =
{
"GimpChannel",
sizeof (GimpChannel),
sizeof (GimpChannelClass),
(GtkClassInitFunc) gimp_channel_class_init,
(GtkObjectInitFunc) gimp_channel_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL,
};
channel_type = gtk_type_unique (gimp_drawable_get_type (), &channel_info);
}
return channel_type;
}
static void
gimp_channel_class_init (GimpChannelClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
parent_class = gtk_type_class (gimp_drawable_get_type ());
channel_signals[REMOVED] =
gimp_signal_new ("removed",
0, object_class->type, 0, gimp_sigtype_void);
gtk_object_class_add_signals (object_class, channel_signals, LAST_SIGNAL);
object_class->destroy = gimp_channel_destroy;
}
static void
gimp_channel_init (GimpChannel *channel)
{
}
/**************************/
/* Function definitions */
/**************************/
static void
channel_validate (TileManager *tm,
Tile *tile)
{
/* Set the contents of the tile to empty */
memset (tile_data_pointer (tile, 0, 0),
TRANSPARENT_OPACITY, tile_size (tile));
}
Channel *
channel_new (GimpImage *gimage,
gint width,
gint height,
gchar *name,
gint opacity,
guchar *col)
{
Channel * channel;
gint i;
channel = gtk_type_new (gimp_channel_get_type ());
gimp_drawable_configure (GIMP_DRAWABLE (channel),
gimage, width, height, GRAY_GIMAGE, name);
/* set the channel color and opacity */
for (i = 0; i < 3; i++)
channel->col[i] = col[i];
channel->opacity = opacity;
channel->show_masked = TRUE;
/* selection mask variables */
channel->empty = TRUE;
channel->segs_in = NULL;
channel->segs_out = NULL;
channel->num_segs_in = 0;
channel->num_segs_out = 0;
channel->bounds_known = TRUE;
channel->boundary_known = TRUE;
channel->x1 = 0;
channel->y1 = 0;
channel->x2 = width;
channel->y2 = height;
return channel;
}
Channel *
channel_ref (Channel *channel)
{
gtk_object_ref (GTK_OBJECT (channel));
gtk_object_sink (GTK_OBJECT (channel));
return channel;
}
void
channel_unref (Channel *channel)
{
gtk_object_unref (GTK_OBJECT (channel));
}
Channel *
channel_copy (Channel *channel)
{
gchar *channel_name;
Channel *new_channel;
PixelRegion srcPR, destPR;
gchar *ext;
gint number;
gchar *name;
gint len;
/* formulate the new channel name */
name = channel_get_name (channel);
ext = strrchr (name, '#');
len = strlen (_("copy"));
if ((strlen (name) >= len &&
strcmp (&name[strlen (name) - len], _("copy")) == 0) ||
(ext && (number = atoi (ext + 1)) > 0 &&
((int)(log10 (number) + 1)) == strlen (ext + 1)))
/* don't have redundant "copy"s */
channel_name = g_strdup (name);
else
channel_name = g_strdup_printf (_("%s copy"), name);
/* allocate a new channel object */
new_channel = channel_new (GIMP_DRAWABLE (channel)->gimage,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height,
channel_name, channel->opacity, channel->col);
GIMP_DRAWABLE (new_channel)->visible = GIMP_DRAWABLE (channel)->visible;
new_channel->show_masked = channel->show_masked;
/* copy the contents across channels */
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->tiles, 0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE (new_channel)->tiles,
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height, TRUE);
copy_region (&srcPR, &destPR);
/* copy the parasites */
GIMP_DRAWABLE (new_channel)->parasites
= parasite_list_copy (GIMP_DRAWABLE (channel)->parasites);
/* free up the channel_name memory */
g_free (channel_name);
return new_channel;
}
void
channel_set_name (Channel *channel,
gchar *name)
{
gimp_drawable_set_name (GIMP_DRAWABLE (channel), name);
}
gchar *
channel_get_name (Channel *channel)
{
return gimp_drawable_get_name (GIMP_DRAWABLE (channel));
}
void
channel_set_color (Channel *channel,
guchar *color)
{
gint i;
if (color)
{
for (i = 0; i < 3; i++)
channel->col[i] = color[i];
}
}
guchar *
channel_get_color (Channel *channel)
{
return (GIMP_CHANNEL (channel)->col);
}
int
channel_get_opacity (Channel *channel)
{
return channel->opacity;
}
void
channel_set_opacity (Channel *channel,
gint opacity)
{
if (opacity >=0 && opacity <= 100)
channel->opacity = (gint) (opacity * 255) / 100;
}
Channel *
channel_get_ID (gint ID)
{
GimpDrawable *drawable;
drawable = drawable_get_ID (ID);
if (drawable && GIMP_IS_CHANNEL (drawable))
return GIMP_CHANNEL (drawable);
else
return NULL;
}
void
channel_delete (Channel *channel)
{
gtk_object_unref (GTK_OBJECT (channel));
}
static void
gimp_channel_destroy (GtkObject *object)
{
GimpChannel *channel;
g_return_if_fail (object != NULL);
g_return_if_fail (GIMP_IS_CHANNEL (object));
channel = GIMP_CHANNEL (object);
/* free the segments? */
if (channel->segs_in)
g_free (channel->segs_in);
if (channel->segs_out)
g_free (channel->segs_out);
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
/* The removed signal is sent out when the channel is no longer
* associcated with an image. It's needed because channels aren't
* destroyed immediately, but kept around for undo purposes. Connect
* to the removed signal to update bits of UI that are tied to a
* particular layer. */
void
channel_removed (Channel *channel,
gpointer image)
{
g_return_if_fail (channel != NULL);
g_return_if_fail (GIMP_IS_CHANNEL (channel));
gtk_signal_emit (GTK_OBJECT (channel), channel_signals[REMOVED]);
}
void
channel_scale (Channel *channel,
gint new_width,
gint new_height)
{
PixelRegion srcPR, destPR;
TileManager *new_tiles;
if (new_width == 0 || new_height == 0)
return;
/* Update the old channel position */
drawable_update (GIMP_DRAWABLE (channel),
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height);
/* Configure the pixel regions */
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->tiles,
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height, FALSE);
/* Allocate the new channel, configure dest region */
new_tiles = tile_manager_new (new_width, new_height, 1);
pixel_region_init (&destPR, new_tiles, 0, 0, new_width, new_height, TRUE);
/* Add an alpha channel */
scale_region (&srcPR, &destPR);
/* Push the channel on the undo stack */
undo_push_channel_mod (GIMP_DRAWABLE (channel)->gimage, channel);
/* Configure the new channel */
GIMP_DRAWABLE (channel)->tiles = new_tiles;
GIMP_DRAWABLE (channel)->width = new_width;
GIMP_DRAWABLE (channel)->height = new_height;
/* bounds are now unknown */
channel->bounds_known = FALSE;
/* Update the new channel position */
drawable_update (GIMP_DRAWABLE (channel),
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height);
}
void
channel_resize (Channel *channel,
gint new_width,
gint new_height,
gint offx,
gint offy)
{
PixelRegion srcPR, destPR;
TileManager *new_tiles;
guchar bg = 0;
gint clear;
gint w, h;
gint x1, y1, x2, y2;
if (!new_width || !new_height)
return;
x1 = CLAMP (offx, 0, new_width);
y1 = CLAMP (offy, 0, new_height);
x2 = CLAMP ((offx + GIMP_DRAWABLE (channel)->width), 0, new_width);
y2 = CLAMP ((offy + GIMP_DRAWABLE (channel)->height), 0, new_height);
w = x2 - x1;
h = y2 - y1;
if (offx > 0)
{
x1 = 0;
x2 = offx;
}
else
{
x1 = -offx;
x2 = 0;
}
if (offy > 0)
{
y1 = 0;
y2 = offy;
}
else
{
y1 = -offy;
y2 = 0;
}
/* Update the old channel position */
drawable_update (GIMP_DRAWABLE (channel),
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height);
/* Configure the pixel regions */
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->tiles,
x1, y1, w, h, FALSE);
/* Determine whether the new channel needs to be initially cleared */
if ((new_width > GIMP_DRAWABLE (channel)->width) ||
(new_height > GIMP_DRAWABLE (channel)->height) ||
(x2 || y2))
clear = TRUE;
else
clear = FALSE;
/* Allocate the new channel, configure dest region */
new_tiles = tile_manager_new (new_width, new_height, 1);
/* Set to black (empty--for selections) */
if (clear)
{
pixel_region_init (&destPR, new_tiles, 0, 0, new_width, new_height, TRUE);
color_region (&destPR, &bg);
}
/* copy from the old to the new */
pixel_region_init (&destPR, new_tiles, x2, y2, w, h, TRUE);
if (w && h)
copy_region (&srcPR, &destPR);
/* Push the channel on the undo stack */
undo_push_channel_mod (GIMP_DRAWABLE (channel)->gimage, channel);
/* Configure the new channel */
GIMP_DRAWABLE (channel)->tiles = new_tiles;
GIMP_DRAWABLE (channel)->width = new_width;
GIMP_DRAWABLE (channel)->height = new_height;
/* bounds are now unknown */
channel->bounds_known = FALSE;
/* update the new channel area */
drawable_update (GIMP_DRAWABLE (channel),
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height);
}
void
channel_update (Channel *channel)
{
drawable_update (GIMP_DRAWABLE (channel),
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height);
gdisplays_flush ();
}
/**********************/
/* access functions */
/**********************/
gboolean
channel_toggle_visibility (Channel *channel)
{
GIMP_DRAWABLE (channel)->visible = !GIMP_DRAWABLE (channel)->visible;
return GIMP_DRAWABLE (channel)->visible;
}
static TempBuf *
channel_preview_private (Channel *channel,
gint width,
gint height)
{
MaskBuf * preview_buf;
PixelRegion srcPR, destPR;
gint subsample;
TempBuf *ret_buf;
/* The easy way */
if (GIMP_DRAWABLE (channel)->preview_valid &&
(ret_buf =
gimp_preview_cache_get (& (GIMP_DRAWABLE (channel)->preview_cache),
width, height)))
return ret_buf;
/* The hard way */
else
{
/* calculate 'acceptable' subsample */
subsample = 1;
if (width < 1) width = 1;
if (height < 1) height = 1;
while ((width * (subsample + 1) * 2 < GIMP_DRAWABLE (channel)->width) &&
(height * (subsample + 1) * 2 < GIMP_DRAWABLE (channel)->height))
subsample = subsample + 1;
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->tiles,
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height, FALSE);
preview_buf = mask_buf_new (width, height);
destPR.bytes = 1;
destPR.x = 0;
destPR.y = 0;
destPR.w = width;
destPR.h = height;
destPR.rowstride = width;
destPR.data = mask_buf_data (preview_buf);
subsample_region (&srcPR, &destPR, subsample);
if (!GIMP_DRAWABLE (channel)->preview_valid)
gimp_preview_cache_invalidate (&(GIMP_DRAWABLE(channel)->preview_cache));
GIMP_DRAWABLE (channel)->preview_valid = TRUE;
gimp_preview_cache_add (&(GIMP_DRAWABLE (channel)->preview_cache),
preview_buf);
return preview_buf;
}
}
TempBuf *
channel_preview (Channel *channel,
gint width,
gint height)
{
/* Ok prime the cache with a large preview if the cache is invalid */
if(!GIMP_DRAWABLE(channel)->preview_valid &&
width <= PREVIEW_CACHE_PRIME_WIDTH &&
height <= PREVIEW_CACHE_PRIME_HEIGHT)
{
TempBuf * tb = channel_preview_private(channel,
PREVIEW_CACHE_PRIME_WIDTH,
PREVIEW_CACHE_PRIME_HEIGHT);
/* Save the 2nd call */
if(width == PREVIEW_CACHE_PRIME_WIDTH &&
height == PREVIEW_CACHE_PRIME_HEIGHT)
return tb;
}
/* Second call - should NOT visit the tile cache...*/
return channel_preview_private(channel,width,height);
}
void
channel_invalidate_previews (GimpImage* gimage)
{
GSList * tmp;
Channel * channel;
tmp = gimage->channels;
while (tmp)
{
channel = (Channel *) tmp->data;
drawable_invalidate_preview (GIMP_DRAWABLE (channel));
tmp = g_slist_next (tmp);
}
}
Tattoo
channel_get_tattoo (const Channel *channel)
{
return (gimp_drawable_get_tattoo (GIMP_DRAWABLE (channel)));
}
void
channel_set_tattoo (const Channel *channel, Tattoo val)
{
gimp_drawable_set_tattoo(GIMP_DRAWABLE (channel),val);
}
/******************************/
/* selection mask functions */
/******************************/
Channel *
channel_new_mask (GimpImage* gimage,
gint width,
gint height)
{
guchar black[3] = {0, 0, 0};
Channel *new_channel;
/* Create the new channel */
new_channel = channel_new (gimage, width, height,
_("Selection Mask"), 127, black);
/* Set the validate procedure */
tile_manager_set_validate_proc (GIMP_DRAWABLE (new_channel)->tiles,
channel_validate);
return new_channel;
}
gboolean
channel_boundary (Channel *mask,
BoundSeg **segs_in,
BoundSeg **segs_out,
gint *num_segs_in,
gint *num_segs_out,
gint x1,
gint y1,
gint x2,
gint y2)
{
gint x3, y3, x4, y4;
PixelRegion bPR;
if (! mask->boundary_known)
{
/* free the out of date boundary segments */
if (mask->segs_in)
g_free (mask->segs_in);
if (mask->segs_out)
g_free (mask->segs_out);
if (channel_bounds (mask, &x3, &y3, &x4, &y4))
{
pixel_region_init (&bPR, GIMP_DRAWABLE (mask)->tiles,
x3, y3, (x4 - x3), (y4 - y3), FALSE);
mask->segs_out = find_mask_boundary (&bPR, &mask->num_segs_out,
IgnoreBounds,
x1, y1,
x2, y2);
x1 = MAX (x1, x3);
y1 = MAX (y1, y3);
x2 = MIN (x2, x4);
y2 = MIN (y2, y4);
if (x2 > x1 && y2 > y1)
{
pixel_region_init (&bPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, FALSE);
mask->segs_in = find_mask_boundary (&bPR, &mask->num_segs_in,
WithinBounds,
x1, y1,
x2, y2);
}
else
{
mask->segs_in = NULL;
mask->num_segs_in = 0;
}
}
else
{
mask->segs_in = NULL;
mask->segs_out = NULL;
mask->num_segs_in = 0;
mask->num_segs_out = 0;
}
mask->boundary_known = TRUE;
}
*segs_in = mask->segs_in;
*segs_out = mask->segs_out;
*num_segs_in = mask->num_segs_in;
*num_segs_out = mask->num_segs_out;
return TRUE;
}
gint
channel_value (Channel *mask,
gint x,
gint y)
{
Tile *tile;
gint val;
/* Some checks to cut back on unnecessary work */
if (mask->bounds_known)
{
if (mask->empty)
return 0;
else if (x < mask->x1 || x >= mask->x2 || y < mask->y1 || y >= mask->y2)
return 0;
}
else
{
if (x < 0 || x >= GIMP_DRAWABLE (mask)->width ||
y < 0 || y >= GIMP_DRAWABLE (mask)->height)
return 0;
}
tile = tile_manager_get_tile (GIMP_DRAWABLE (mask)->tiles, x, y, TRUE, FALSE);
val = *(guchar *) (tile_data_pointer (tile, x % TILE_WIDTH, y % TILE_HEIGHT));
tile_release (tile, FALSE);
return val;
}
gboolean
channel_bounds (Channel *mask,
gint *x1,
gint *y1,
gint *x2,
gint *y2)
{
PixelRegion maskPR;
guchar *data, *data1;
gint x, y;
gint ex, ey;
void *pr;
gint tx1, tx2, ty1, ty2;
gint minx, maxx;
/* if the mask's bounds have already been reliably calculated... */
if (mask->bounds_known)
{
*x1 = mask->x1;
*y1 = mask->y1;
*x2 = mask->x2;
*y2 = mask->y2;
return !mask->empty;
}
/* go through and calculate the bounds */
tx1 = GIMP_DRAWABLE (mask)->width;
ty1 = GIMP_DRAWABLE (mask)->height;
tx2 = 0;
ty2 = 0;
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, FALSE);
for (pr = pixel_regions_register (1, &maskPR);
pr != NULL;
pr = pixel_regions_process (pr))
{
data1 = data = maskPR.data;
ex = maskPR.x + maskPR.w;
ey = maskPR.y + maskPR.h;
/* only check the pixels if this tile is not fully within the currently
computed bounds */
if (maskPR.x < tx1 || ex > tx2 ||
maskPR.y < ty1 || ey > ty2)
{
/* Check upper left and lower right corners to see if we can
avoid checking the rest of the pixels in this tile */
if (data[0] && data[maskPR.rowstride*(maskPR.h - 1) + maskPR.w - 1])
{
if (maskPR.x < tx1)
tx1 = maskPR.x;
if (ex > tx2)
tx2 = ex;
if (maskPR.y < ty1)
ty1 = maskPR.y;
if (ey > ty2)
ty2 = ey;
}
else
for (y = maskPR.y; y < ey; y++, data1 += maskPR.rowstride)
{
for (x = maskPR.x, data = data1; x < ex; x++, data++)
if (*data)
{
minx = x;
maxx = x;
for (; x < ex; x++, data++)
if (*data)
maxx = x;
if (minx < tx1)
tx1 = minx;
if (maxx > tx2)
tx2 = maxx;
if (y < ty1)
ty1 = y;
if (y > ty2)
ty2 = y;
}
}
}
}
tx2 = CLAMP (tx2 + 1, 0, GIMP_DRAWABLE (mask)->width);
ty2 = CLAMP (ty2 + 1, 0, GIMP_DRAWABLE (mask)->height);
if (tx1 == GIMP_DRAWABLE (mask)->width && ty1 == GIMP_DRAWABLE (mask)->height)
{
mask->empty = TRUE;
mask->x1 = 0;
mask->y1 = 0;
mask->x2 = GIMP_DRAWABLE (mask)->width;
mask->y2 = GIMP_DRAWABLE (mask)->height;
}
else
{
mask->empty = FALSE;
mask->x1 = tx1;
mask->y1 = ty1;
mask->x2 = tx2;
mask->y2 = ty2;
}
mask->bounds_known = TRUE;
*x1 = tx1;
*x2 = tx2;
*y1 = ty1;
*y2 = ty2;
return !mask->empty;
}
gboolean
channel_is_empty (Channel *mask)
{
PixelRegion maskPR;
guchar * data;
gint x, y;
void * pr;
if (mask->bounds_known)
return mask->empty;
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, FALSE);
for (pr = pixel_regions_register (1, &maskPR);
pr != NULL;
pr = pixel_regions_process (pr))
{
/* check if any pixel in the mask is non-zero */
data = maskPR.data;
for (y = 0; y < maskPR.h; y++)
for (x = 0; x < maskPR.w; x++)
if (*data++)
{
pixel_regions_process_stop (pr);
return FALSE;
}
}
/* The mask is empty, meaning we can set the bounds as known */
if (mask->segs_in)
g_free (mask->segs_in);
if (mask->segs_out)
g_free (mask->segs_out);
mask->empty = TRUE;
mask->segs_in = NULL;
mask->segs_out = NULL;
mask->num_segs_in = 0;
mask->num_segs_out = 0;
mask->bounds_known = TRUE;
mask->boundary_known = TRUE;
mask->x1 = 0;
mask->y1 = 0;
mask->x2 = GIMP_DRAWABLE (mask)->width;
mask->y2 = GIMP_DRAWABLE (mask)->height;
return TRUE;
}
void
channel_add_segment (Channel *mask,
gint x,
gint y,
gint width,
gint value)
{
PixelRegion maskPR;
guchar *data;
gint val;
gint x2;
void * pr;
/* check horizontal extents... */
x2 = x + width;
if (x2 < 0) x2 = 0;
if (x2 > GIMP_DRAWABLE (mask)->width) x2 = GIMP_DRAWABLE (mask)->width;
if (x < 0) x = 0;
if (x > GIMP_DRAWABLE (mask)->width) x = GIMP_DRAWABLE (mask)->width;
width = x2 - x;
if (!width) return;
if (y < 0 || y > GIMP_DRAWABLE (mask)->height)
return;
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
x, y, width, 1, TRUE);
for (pr = pixel_regions_register (1, &maskPR);
pr != NULL;
pr = pixel_regions_process (pr))
{
data = maskPR.data;
width = maskPR.w;
while (width--)
{
val = *data + value;
if (val > 255)
val = 255;
*data++ = val;
}
}
}
void
channel_sub_segment (Channel *mask,
gint x,
gint y,
gint width,
gint value)
{
PixelRegion maskPR;
guchar *data;
gint val;
gint x2;
void * pr;
/* check horizontal extents... */
x2 = x + width;
if (x2 < 0) x2 = 0;
if (x2 > GIMP_DRAWABLE (mask)->width) x2 = GIMP_DRAWABLE (mask)->width;
if (x < 0) x = 0;
if (x > GIMP_DRAWABLE (mask)->width) x = GIMP_DRAWABLE (mask)->width;
width = x2 - x;
if (!width) return;
if (y < 0 || y > GIMP_DRAWABLE (mask)->height)
return;
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles, x, y, width, 1, TRUE);
for (pr = pixel_regions_register (1, &maskPR);
pr != NULL;
pr = pixel_regions_process (pr))
{
data = maskPR.data;
width = maskPR.w;
while (width--)
{
val = *data - value;
if (val < 0)
val = 0;
*data++ = val;
}
}
}
void
channel_combine_rect (Channel *mask,
ChannelOps op,
gint x,
gint y,
gint w,
gint h)
{
gint x2, y2;
PixelRegion maskPR;
guchar color;
y2 = y + h;
x2 = x + w;
x = CLAMP (x, 0, GIMP_DRAWABLE (mask)->width);
y = CLAMP (y, 0, GIMP_DRAWABLE (mask)->height);
x2 = CLAMP (x2, 0, GIMP_DRAWABLE (mask)->width);
y2 = CLAMP (y2, 0, GIMP_DRAWABLE (mask)->height);
if (x2 - x <= 0 || y2 - y <= 0)
return;
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
x, y, x2 - x, y2 - y, TRUE);
if (op == ADD || op == REPLACE)
color = 255;
else
color = 0;
color_region (&maskPR, &color);
/* Determine new boundary */
if (mask->bounds_known && (op == ADD) && !mask->empty)
{
if (x < mask->x1)
mask->x1 = x;
if (y < mask->y1)
mask->y1 = y;
if ((x + w) > mask->x2)
mask->x2 = (x + w);
if ((y + h) > mask->y2)
mask->y2 = (y + h);
}
else if (op == REPLACE || mask->empty)
{
mask->empty = FALSE;
mask->x1 = x;
mask->y1 = y;
mask->x2 = x + w;
mask->y2 = y + h;
}
else
mask->bounds_known = FALSE;
mask->x1 = CLAMP (mask->x1, 0, GIMP_DRAWABLE (mask)->width);
mask->y1 = CLAMP (mask->y1, 0, GIMP_DRAWABLE (mask)->height);
mask->x2 = CLAMP (mask->x2, 0, GIMP_DRAWABLE (mask)->width);
mask->y2 = CLAMP (mask->y2, 0, GIMP_DRAWABLE (mask)->height);
}
void
channel_combine_ellipse (Channel *mask,
ChannelOps op,
gint x,
gint y,
gint w,
gint h,
gboolean aa /* antialias selection? */)
{
gint i, j;
gint x0, x1, x2;
gint val, last;
gfloat a_sqr, b_sqr, aob_sqr;
gfloat w_sqr, h_sqr;
gfloat y_sqr;
gfloat t0, t1;
gfloat r;
gfloat cx, cy;
gfloat rad;
gfloat dist;
if (!w || !h)
return;
a_sqr = (w * w / 4.0);
b_sqr = (h * h / 4.0);
aob_sqr = a_sqr / b_sqr;
cx = x + w / 2.0;
cy = y + h / 2.0;
for (i = y; i < (y + h); i++)
{
if (i >= 0 && i < GIMP_DRAWABLE (mask)->height)
{
/* Non-antialiased code */
if (!aa)
{
y_sqr = (i + 0.5 - cy) * (i + 0.5 - cy);
rad = sqrt (a_sqr - a_sqr * y_sqr / (double) b_sqr);
x1 = ROUND (cx - rad);
x2 = ROUND (cx + rad);
switch (op)
{
case ADD: case REPLACE:
channel_add_segment (mask, x1, i, (x2 - x1), 255);
break;
case SUB :
channel_sub_segment (mask, x1, i, (x2 - x1), 255);
break;
default:
g_warning ("Only ADD, REPLACE and SUB are valid for channel_combine!");
break;
}
}
/* antialiasing */
else
{
x0 = x;
last = 0;
h_sqr = (i + 0.5 - cy) * (i + 0.5 - cy);
for (j = x; j < (x + w); j++)
{
w_sqr = (j + 0.5 - cx) * (j + 0.5 - cx);
if (h_sqr != 0)
{
t0 = w_sqr / h_sqr;
t1 = a_sqr / (t0 + aob_sqr);
r = sqrt (t1 + t0 * t1);
rad = sqrt (w_sqr + h_sqr);
dist = rad - r;
}
else
dist = -1.0;
if (dist < -0.5)
val = 255;
else if (dist < 0.5)
val = (int) (255 * (1 - (dist + 0.5)));
else
val = 0;
if (last != val && last)
{
switch (op)
{
case ADD: case REPLACE:
channel_add_segment (mask, x0, i, j - x0, last);
break;
case SUB:
channel_sub_segment (mask, x0, i, j - x0, last);
break;
default:
g_warning ("Only ADD, REPLACE and SUB are valid for channel_combine!");
break;
}
}
if (last != val)
{
x0 = j;
last = val;
/* because we are symetric accross the y axis we can
skip ahead a bit if we are inside the ellipse*/
if (val == 255 && j < cx)
j = cx + (cx - j) - 1;
}
}
if (last)
{
if (op == ADD || op == REPLACE)
channel_add_segment (mask, x0, i, j - x0, last);
else if (op == SUB)
channel_sub_segment (mask, x0, i, j - x0, last);
else
g_warning ("Only ADD, REPLACE and SUB are valid for channel_combine!");
}
}
}
}
/* Determine new boundary */
if (mask->bounds_known && (op == ADD) && !mask->empty)
{
if (x < mask->x1)
mask->x1 = x;
if (y < mask->y1)
mask->y1 = y;
if ((x + w) > mask->x2)
mask->x2 = (x + w);
if ((y + h) > mask->y2)
mask->y2 = (y + h);
}
else if (op == REPLACE || mask->empty)
{
mask->empty = FALSE;
mask->x1 = x;
mask->y1 = y;
mask->x2 = x + w;
mask->y2 = y + h;
}
else
mask->bounds_known = FALSE;
mask->x1 = CLAMP (mask->x1, 0, GIMP_DRAWABLE (mask)->width);
mask->y1 = CLAMP (mask->y1, 0, GIMP_DRAWABLE (mask)->height);
mask->x2 = CLAMP (mask->x2, 0, GIMP_DRAWABLE (mask)->width);
mask->y2 = CLAMP (mask->y2, 0, GIMP_DRAWABLE (mask)->height);
}
static void
channel_combine_sub_region_add (void *unused,
PixelRegion *srcPR,
PixelRegion *destPR)
{
guchar *src, *dest;
gint x, y, val;
src = srcPR->data;
dest = destPR->data;
for (y = 0; y < srcPR->h; y++)
{
for (x = 0; x < srcPR->w; x++)
{
val = dest[x] + src[x];
if (val > 255)
dest[x] = 255;
else
dest[x] = val;
}
src += srcPR->rowstride;
dest += destPR->rowstride;
}
}
static void
channel_combine_sub_region_sub (void *unused,
PixelRegion *srcPR,
PixelRegion *destPR)
{
guchar *src, *dest;
gint x, y;
src = srcPR->data;
dest = destPR->data;
for (y = 0; y < srcPR->h; y++)
{
for (x = 0; x < srcPR->w; x++)
{
if (src[x] > dest[x])
dest[x] = 0;
else
dest[x]-= src[x];
}
src += srcPR->rowstride;
dest += destPR->rowstride;
}
}
static void
channel_combine_sub_region_intersect (void *unused,
PixelRegion *srcPR,
PixelRegion *destPR)
{
guchar *src, *dest;
gint x, y;
src = srcPR->data;
dest = destPR->data;
for (y = 0; y < srcPR->h; y++)
{
for (x = 0; x < srcPR->w; x++)
{
dest[x] = MIN (dest[x], src[x]);
}
src += srcPR->rowstride;
dest += destPR->rowstride;
}
}
void
channel_combine_mask (Channel *mask,
Channel *add_on,
ChannelOps op,
gint off_x,
gint off_y)
{
PixelRegion srcPR, destPR;
gint x1, y1, x2, y2;
gint w, h;
x1 = CLAMP (off_x, 0, GIMP_DRAWABLE (mask)->width);
y1 = CLAMP (off_y, 0, GIMP_DRAWABLE (mask)->height);
x2 = CLAMP (off_x + GIMP_DRAWABLE (add_on)->width, 0,
GIMP_DRAWABLE (mask)->width);
y2 = CLAMP (off_y + GIMP_DRAWABLE (add_on)->height, 0,
GIMP_DRAWABLE (mask)->height);
w = (x2 - x1);
h = (y2 - y1);
pixel_region_init (&srcPR, GIMP_DRAWABLE (add_on)->tiles,
(x1 - off_x), (y1 - off_y), w, h, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE (mask)->tiles, x1, y1, w, h, TRUE);
switch (op)
{
case ADD: case REPLACE:
pixel_regions_process_parallel ((p_func) channel_combine_sub_region_add,
NULL, 2, &srcPR, &destPR);
break;
case SUB:
pixel_regions_process_parallel ((p_func) channel_combine_sub_region_sub,
NULL, 2, &srcPR, &destPR);
break;
case INTERSECT:
pixel_regions_process_parallel ((p_func)
channel_combine_sub_region_intersect,
NULL, 2, &srcPR, &destPR);
break;
default:
g_message ("Error: unknown opperation type in channel_combine_mask\n");
break;
}
mask->bounds_known = FALSE;
}
void
channel_feather (Channel *input,
Channel *output,
gdouble radius_x,
gdouble radius_y,
ChannelOps op,
gint off_x,
gint off_y)
{
gint x1, y1, x2, y2;
PixelRegion srcPR;
x1 = CLAMP (off_x, 0, GIMP_DRAWABLE (output)->width);
y1 = CLAMP (off_y, 0, GIMP_DRAWABLE (output)->height);
x2 = CLAMP (off_x + GIMP_DRAWABLE (input)->width, 0,
GIMP_DRAWABLE (output)->width);
y2 = CLAMP (off_y + GIMP_DRAWABLE (input)->height, 0,
GIMP_DRAWABLE (output)->height);
pixel_region_init (&srcPR, GIMP_DRAWABLE (input)->tiles,
(x1 - off_x), (y1 - off_y), (x2 - x1), (y2 - y1), FALSE);
gaussian_blur_region (&srcPR, radius_x, radius_y);
if (input != output)
channel_combine_mask (output, input, op, 0, 0);
output->bounds_known = FALSE;
}
void
channel_push_undo (Channel *mask)
{
gint x1, y1, x2, y2;
MaskUndo *mask_undo;
TileManager *undo_tiles;
PixelRegion srcPR, destPR;
GImage *gimage;
mask_undo = g_new (MaskUndo, 1);
if (channel_bounds (mask, &x1, &y1, &x2, &y2))
{
undo_tiles = tile_manager_new ((x2 - x1), (y2 - y1), 1);
pixel_region_init (&srcPR, GIMP_DRAWABLE (mask)->tiles,
x1, y1, (x2 - x1), (y2 - y1), FALSE);
pixel_region_init (&destPR, undo_tiles, 0, 0, (x2 - x1), (y2 - y1), TRUE);
copy_region (&srcPR, &destPR);
}
else
undo_tiles = NULL;
mask_undo->tiles = undo_tiles;
mask_undo->x = x1;
mask_undo->y = y1;
/* push the undo buffer onto the undo stack */
gimage = GIMP_DRAWABLE (mask)->gimage;
undo_push_mask (gimage, mask_undo);
gimage_mask_invalidate (gimage);
/* invalidate the preview */
GIMP_DRAWABLE (mask)->preview_valid = FALSE;
}
void
channel_clear (Channel *mask)
{
PixelRegion maskPR;
guchar bg = 0;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
if (mask->bounds_known && !mask->empty)
{
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
mask->x1, mask->y1,
(mask->x2 - mask->x1), (mask->y2 - mask->y1), TRUE);
color_region (&maskPR, &bg);
}
else
{
/* clear the mask */
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, TRUE);
color_region (&maskPR, &bg);
}
/* we know the bounds */
mask->bounds_known = TRUE;
mask->empty = TRUE;
mask->x1 = 0;
mask->y1 = 0;
mask->x2 = GIMP_DRAWABLE (mask)->width;
mask->y2 = GIMP_DRAWABLE (mask)->height;
}
void
channel_invert (Channel *mask)
{
PixelRegion maskPR;
GimpLut *lut;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, TRUE);
lut = invert_lut_new (1);
pixel_regions_process_parallel ((p_func) gimp_lut_process_inline,
lut, 1, &maskPR);
gimp_lut_free (lut);
mask->bounds_known = FALSE;
}
void
channel_sharpen (Channel *mask)
{
PixelRegion maskPR;
GimpLut *lut;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, TRUE);
lut = threshold_lut_new (0.5, 1);
pixel_regions_process_parallel ((p_func) gimp_lut_process_inline,
lut, 1, &maskPR);
gimp_lut_free (lut);
}
void
channel_all (Channel *mask)
{
PixelRegion maskPR;
guchar bg = 255;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
/* clear the mask */
pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, TRUE);
color_region (&maskPR, &bg);
/* we know the bounds */
mask->bounds_known = TRUE;
mask->empty = FALSE;
mask->x1 = 0;
mask->y1 = 0;
mask->x2 = GIMP_DRAWABLE (mask)->width;
mask->y2 = GIMP_DRAWABLE (mask)->height;
}
void
channel_border (Channel *mask,
gint radius_x,
gint radius_y)
{
PixelRegion bPR;
gint x1, y1, x2, y2;
if (radius_x < 0 || radius_y < 0)
return;
if (! channel_bounds (mask, &x1, &y1, &x2, &y2))
return;
if (channel_is_empty (mask))
return;
if (x1 - radius_x < 0)
x1 = 0;
else
x1 -= radius_x;
if (x2 + radius_x > GIMP_DRAWABLE (mask)->width)
x2 = GIMP_DRAWABLE (mask)->width;
else
x2 += radius_x;
if (y1 - radius_y < 0)
y1 = 0;
else
y1 -= radius_y;
if (y2 + radius_y > GIMP_DRAWABLE (mask)->height)
y2 = GIMP_DRAWABLE (mask)->height;
else
y2 += radius_y;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
pixel_region_init (&bPR, GIMP_DRAWABLE (mask)->tiles, x1, y1,
(x2-x1), (y2-y1), TRUE);
border_region (&bPR, radius_x, radius_y);
mask->bounds_known = FALSE;
}
void
channel_grow (Channel *mask,
gint radius_x,
gint radius_y)
{
PixelRegion bPR;
gint x1, y1, x2, y2;
if (radius_x == 0 && radius_y == 0)
return;
if (radius_x <= 0 && radius_y <= 0)
{
channel_shrink (mask, -radius_x, -radius_y, FALSE);
return;
}
if (radius_x < 0 || radius_y < 0)
return;
if (! channel_bounds (mask, &x1, &y1, &x2, &y2))
return;
if (channel_is_empty (mask))
return;
if (x1 - radius_x > 0)
x1 = x1 - radius_x;
else
x1 = 0;
if (y1 - radius_y > 0)
y1 = y1 - radius_y;
else
y1 = 0;
if (x2 + radius_x < GIMP_DRAWABLE (mask)->width)
x2 = x2 + radius_x;
else
x2 = GIMP_DRAWABLE (mask)->width;
if (y2 + radius_y < GIMP_DRAWABLE (mask)->height)
y2 = y2 + radius_y;
else
y2 = GIMP_DRAWABLE (mask)->height;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
/* need full extents for grow, not! */
pixel_region_init (&bPR, GIMP_DRAWABLE (mask)->tiles, x1, y1, (x2 - x1),
(y2 - y1), TRUE);
fatten_region (&bPR, radius_x, radius_y);
mask->bounds_known = FALSE;
}
void
channel_shrink (Channel *mask,
gint radius_x,
gint radius_y,
gboolean edge_lock)
{
PixelRegion bPR;
gint x1, y1, x2, y2;
if (radius_x == 0 && radius_y == 0)
return;
if (radius_x <= 0 && radius_y <= 0)
{
channel_grow (mask, -radius_x, -radius_y);
return;
}
if (radius_x < 0 || radius_y < 0)
return;
if (! channel_bounds (mask, &x1, &y1, &x2, &y2))
return;
if (channel_is_empty (mask))
return;
if (x1 > 0)
x1--;
if (y1 > 0)
y1--;
if (x2 < GIMP_DRAWABLE (mask)->width)
x2++;
if (y2 < GIMP_DRAWABLE (mask)->height)
y2++;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
pixel_region_init (&bPR, GIMP_DRAWABLE (mask)->tiles, x1, y1, (x2 - x1),
(y2 - y1), TRUE);
thin_region (&bPR, radius_x, radius_y, edge_lock);
mask->bounds_known = FALSE;
}
void
channel_translate (Channel *mask,
gint off_x,
gint off_y)
{
gint width, height;
Channel *tmp_mask;
PixelRegion srcPR, destPR;
guchar empty = 0;
gint x1, y1, x2, y2;
tmp_mask = NULL;
/* push the current channel onto the undo stack */
channel_push_undo (mask);
channel_bounds (mask, &x1, &y1, &x2, &y2);
x1 = CLAMP ((x1 + off_x), 0, GIMP_DRAWABLE (mask)->width);
y1 = CLAMP ((y1 + off_y), 0, GIMP_DRAWABLE (mask)->height);
x2 = CLAMP ((x2 + off_x), 0, GIMP_DRAWABLE (mask)->width);
y2 = CLAMP ((y2 + off_y), 0, GIMP_DRAWABLE (mask)->height);
width = x2 - x1;
height = y2 - y1;
/* make sure width and height are non-zero */
if (width != 0 && height != 0)
{
/* copy the portion of the mask we will keep to a
* temporary buffer
*/
tmp_mask = channel_new_mask (GIMP_DRAWABLE (mask)->gimage, width, height);
pixel_region_init (&srcPR, GIMP_DRAWABLE (mask)->tiles,
x1 - off_x, y1 - off_y, width, height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE (tmp_mask)->tiles,
0, 0, width, height, TRUE);
copy_region (&srcPR, &destPR);
}
/* clear the mask */
pixel_region_init (&srcPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, TRUE);
color_region (&srcPR, &empty);
if (width != 0 && height != 0)
{
/* copy the temp mask back to the mask */
pixel_region_init (&srcPR, GIMP_DRAWABLE (tmp_mask)->tiles,
0, 0, width, height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE (mask)->tiles,
x1, y1, width, height, TRUE);
copy_region (&srcPR, &destPR);
/* free the temporary mask */
channel_delete (tmp_mask);
}
/* calculate new bounds */
if (width == 0 || height == 0)
{
mask->empty = TRUE;
mask->x1 = 0; mask->y1 = 0;
mask->x2 = GIMP_DRAWABLE (mask)->width;
mask->y2 = GIMP_DRAWABLE (mask)->height;
}
else
{
mask->x1 = x1;
mask->y1 = y1;
mask->x2 = x2;
mask->y2 = y2;
}
}
void
channel_layer_alpha (Channel *mask,
Layer *layer)
{
PixelRegion srcPR, destPR;
guchar empty = 0;
gint x1, y1, x2, y2;
/* push the current mask onto the undo stack */
channel_push_undo (mask);
/* clear the mask */
pixel_region_init (&destPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (mask)->width,
GIMP_DRAWABLE (mask)->height, TRUE);
color_region (&destPR, &empty);
x1 = CLAMP (GIMP_DRAWABLE (layer)->offset_x, 0, GIMP_DRAWABLE (mask)->width);
y1 = CLAMP (GIMP_DRAWABLE (layer)->offset_y, 0, GIMP_DRAWABLE (mask)->height);
x2 = CLAMP (GIMP_DRAWABLE (layer)->offset_x + GIMP_DRAWABLE (layer)->width,
0, GIMP_DRAWABLE (mask)->width);
y2 = CLAMP (GIMP_DRAWABLE( layer)->offset_y + GIMP_DRAWABLE (layer)->height,
0, GIMP_DRAWABLE (mask)->height);
pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles,
(x1 - GIMP_DRAWABLE (layer)->offset_x),
(y1 - GIMP_DRAWABLE (layer)->offset_y),
(x2 - x1), (y2 - y1), FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE (mask)->tiles,
x1, y1, (x2 - x1), (y2 - y1), TRUE);
extract_alpha_region (&srcPR, NULL, &destPR);
mask->bounds_known = FALSE;
}
void
channel_load (Channel *mask,
Channel *channel)
{
PixelRegion srcPR, destPR;
/* push the current mask onto the undo stack */
channel_push_undo (mask);
/* copy the channel to the mask */
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->tiles,
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE (mask)->tiles,
0, 0,
GIMP_DRAWABLE (channel)->width,
GIMP_DRAWABLE (channel)->height, TRUE);
copy_region (&srcPR, &destPR);
mask->bounds_known = FALSE;
}
void
channel_invalidate_bounds (Channel *channel)
{
channel->bounds_known = FALSE;
}