mirror of
https://gitlab.gnome.org/GNOME/gimp
synced 2024-10-22 04:22:29 +00:00
7a5f914866
2003-09-06 Michael Natterer <mitch@gimp.org> To optimize duplicate and/or wrong image updates away, introduced new policy that a child object must never explicitly update or invalidate its parent object (just like the GUI is not updated explicitly by the core): * app/core/gimpdrawable.[ch]: added new signal GimpDrawable::update(). Never update or invalidate the image when the drawable is updated or invalidated. (gimp_drawable_set_visible): don't gimp_drawable_update() the drawable since its pixels have not changed. * app/core/gimpimage.[ch]: connect to the "add" and "remove" signals of the layers and channels containers. Also connect to the "update" and "visibility_changed" signals of all drawables in these containers (optimizes away updates issued by drawables which are not yet added to the image and updates of the selection mask). Also, don't propagate updates to the image if the emitting drawable is invisible (optimizes away updates issued by invisible drawables). (gimp_image_add_layer,channel) (gimp_image_remove_layer,channel): don't update the image since that's done by our "add" and "remove" handlers now. (gimp_image_position_layer,channel): update just the image, not the drawable since its pixels have not changed. (gimp_image_real_colormap_changed) (gimp_image_set_component_visible): always call gimp_image_update() *and* gimp_viewable_invalidate_preview() to get everything updated, since update and invalidate of images are not connected. * app/core/gimpimage-undo-push.c (undo_pop_layer,channel): don't update the drawable since (a) its pixels don't change and (b) the image updates itself upon adding/removing now. (undo_pop_layer_mod): replaced gimp_image_update() by gimp_drawable_update() (just for consistency with other similar functions). * app/core/gimplayer.c: connect to "update" of the layer mask and issue updates on the layer if the mask update has any effect on the projection. (gimp_layer_create_mask): don't set the mask's offsets here since they may be different when we later add the mask to the layer. * app/core/gimplayermask.c (gimp_layer_mask_set_layer): set the mask offsets here instead. * app/core/gimpchannel.c (gimp_channel_translate): update the channel even if push_undo == FALSE. * app/paint/gimppaintcore.c (gimp_paint_core_finish) * app/tools/gimpinktool.c (ink_finish): invalidate both the drawable and the image preview since invalidating the drawable doesn't invalidate the image any more. * app/text/gimptextlayer.c (gimp_text_layer_render_now): also update the new extents of the text layer, not only the old one. (gimp_text_layer_render_layout): don't update the drawable since gimp_drawable_fill() already updated it.
1649 lines
49 KiB
C
1649 lines
49 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 <glib-object.h>
|
|
|
|
#include "libgimpcolor/gimpcolor.h"
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "base/boundary.h"
|
|
#include "base/gimplut.h"
|
|
#include "base/lut-funcs.h"
|
|
#include "base/pixel-processor.h"
|
|
#include "base/pixel-region.h"
|
|
#include "base/tile.h"
|
|
#include "base/tile-manager.h"
|
|
|
|
#include "paint-funcs/paint-funcs.h"
|
|
|
|
#include "paint/gimppaintcore-stroke.h"
|
|
|
|
#include "gimp-utils.h"
|
|
#include "gimpimage.h"
|
|
#include "gimpimage-projection.h"
|
|
#include "gimpimage-undo.h"
|
|
#include "gimpimage-undo-push.h"
|
|
#include "gimpchannel.h"
|
|
#include "gimplayer.h"
|
|
#include "gimppaintinfo.h"
|
|
#include "gimpparasitelist.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
static void gimp_channel_class_init (GimpChannelClass *klass);
|
|
static void gimp_channel_init (GimpChannel *channel);
|
|
|
|
static void gimp_channel_finalize (GObject *object);
|
|
|
|
static gsize gimp_channel_get_memsize (GimpObject *object,
|
|
gsize *gui_size);
|
|
|
|
static GimpItem * gimp_channel_duplicate (GimpItem *item,
|
|
GType new_type,
|
|
gboolean add_alpha);
|
|
static void gimp_channel_translate (GimpItem *item,
|
|
gint off_x,
|
|
gint off_y,
|
|
gboolean push_undo);
|
|
static void gimp_channel_scale (GimpItem *item,
|
|
gint new_width,
|
|
gint new_height,
|
|
gint new_offset_x,
|
|
gint new_offset_y,
|
|
GimpInterpolationType interp_type);
|
|
static void gimp_channel_resize (GimpItem *item,
|
|
gint new_width,
|
|
gint new_height,
|
|
gint offx,
|
|
gint offy);
|
|
static void gimp_channel_flip (GimpItem *item,
|
|
GimpOrientationType flip_type,
|
|
gdouble axis,
|
|
gboolean flip_result);
|
|
static void gimp_channel_rotate (GimpItem *item,
|
|
GimpRotationType flip_type,
|
|
gdouble center_x,
|
|
gdouble center_y,
|
|
gboolean flip_result);
|
|
static void gimp_channel_transform (GimpItem *item,
|
|
const GimpMatrix3 *matrix,
|
|
GimpTransformDirection direction,
|
|
GimpInterpolationType interpolation_type,
|
|
gboolean clip_result,
|
|
GimpProgressFunc progress_callback,
|
|
gpointer progress_data);
|
|
static gboolean gimp_channel_stroke (GimpItem *item,
|
|
GimpDrawable *drawable,
|
|
GimpPaintInfo *paint_info);
|
|
|
|
static void gimp_channel_invalidate_boundary (GimpDrawable *drawable);
|
|
|
|
static gboolean gimp_channel_real_boundary (GimpChannel *channel,
|
|
const BoundSeg **segs_in,
|
|
const BoundSeg **segs_out,
|
|
gint *num_segs_in,
|
|
gint *num_segs_out,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2);
|
|
static gboolean gimp_channel_real_bounds (GimpChannel *channel,
|
|
gint *x1,
|
|
gint *y1,
|
|
gint *x2,
|
|
gint *y2);
|
|
static gboolean gimp_channel_real_is_empty (GimpChannel *channel);
|
|
static gint gimp_channel_real_value (GimpChannel *channel,
|
|
gint x,
|
|
gint y);
|
|
static void gimp_channel_real_feather (GimpChannel *channel,
|
|
gdouble radius_x,
|
|
gdouble radius_y,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_sharpen (GimpChannel *channel,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_clear (GimpChannel *channel,
|
|
const gchar *undo_desc,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_all (GimpChannel *channel,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_invert (GimpChannel *channel,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_border (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_grow (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean push_undo);
|
|
static void gimp_channel_real_shrink (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean edge_lock,
|
|
gboolean push_undo);
|
|
|
|
static void gimp_channel_validate (TileManager *tm,
|
|
Tile *tile);
|
|
|
|
|
|
static GimpDrawableClass * parent_class = NULL;
|
|
|
|
|
|
GType
|
|
gimp_channel_get_type (void)
|
|
{
|
|
static GType channel_type = 0;
|
|
|
|
if (! channel_type)
|
|
{
|
|
static const GTypeInfo channel_info =
|
|
{
|
|
sizeof (GimpChannelClass),
|
|
(GBaseInitFunc) NULL,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) gimp_channel_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GimpChannel),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) gimp_channel_init,
|
|
};
|
|
|
|
channel_type = g_type_register_static (GIMP_TYPE_DRAWABLE,
|
|
"GimpChannel",
|
|
&channel_info, 0);
|
|
}
|
|
|
|
return channel_type;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_class_init (GimpChannelClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
GimpObjectClass *gimp_object_class;
|
|
GimpViewableClass *viewable_class;
|
|
GimpItemClass *item_class;
|
|
GimpDrawableClass *drawable_class;
|
|
|
|
object_class = G_OBJECT_CLASS (klass);
|
|
gimp_object_class = GIMP_OBJECT_CLASS (klass);
|
|
viewable_class = GIMP_VIEWABLE_CLASS (klass);
|
|
item_class = GIMP_ITEM_CLASS (klass);
|
|
drawable_class = GIMP_DRAWABLE_CLASS (klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
object_class->finalize = gimp_channel_finalize;
|
|
|
|
gimp_object_class->get_memsize = gimp_channel_get_memsize;
|
|
|
|
viewable_class->default_stock_id = "gimp-channel";
|
|
|
|
item_class->duplicate = gimp_channel_duplicate;
|
|
item_class->translate = gimp_channel_translate;
|
|
item_class->scale = gimp_channel_scale;
|
|
item_class->resize = gimp_channel_resize;
|
|
item_class->flip = gimp_channel_flip;
|
|
item_class->rotate = gimp_channel_rotate;
|
|
item_class->transform = gimp_channel_transform;
|
|
item_class->stroke = gimp_channel_stroke;
|
|
item_class->default_name = _("Channel");
|
|
item_class->rename_desc = _("Rename Channel");
|
|
|
|
drawable_class->invalidate_boundary = gimp_channel_invalidate_boundary;
|
|
|
|
klass->boundary = gimp_channel_real_boundary;
|
|
klass->bounds = gimp_channel_real_bounds;
|
|
klass->is_empty = gimp_channel_real_is_empty;
|
|
klass->value = gimp_channel_real_value;
|
|
klass->feather = gimp_channel_real_feather;
|
|
klass->sharpen = gimp_channel_real_sharpen;
|
|
klass->clear = gimp_channel_real_clear;
|
|
klass->all = gimp_channel_real_all;
|
|
klass->invert = gimp_channel_real_invert;
|
|
klass->border = gimp_channel_real_border;
|
|
klass->grow = gimp_channel_real_grow;
|
|
klass->shrink = gimp_channel_real_shrink;
|
|
|
|
klass->translate_desc = _("Move Channel");
|
|
klass->feather_desc = _("Feather Channel");
|
|
klass->sharpen_desc = _("Sharpen Channel");
|
|
klass->clear_desc = _("Clear Channel");
|
|
klass->all_desc = _("Fill Channel");
|
|
klass->invert_desc = _("Invert Channel");
|
|
klass->border_desc = _("Border Channel");
|
|
klass->grow_desc = _("Grow Channel");
|
|
klass->shrink_desc = _("Shrink Channel");
|
|
}
|
|
|
|
static void
|
|
gimp_channel_init (GimpChannel *channel)
|
|
{
|
|
gimp_rgba_set (&channel->color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE);
|
|
|
|
channel->show_masked = FALSE;
|
|
|
|
/* Selection mask variables */
|
|
channel->boundary_known = FALSE;
|
|
channel->segs_in = NULL;
|
|
channel->segs_out = NULL;
|
|
channel->num_segs_in = 0;
|
|
channel->num_segs_out = 0;
|
|
channel->empty = FALSE;
|
|
channel->bounds_known = FALSE;
|
|
channel->x1 = 0;
|
|
channel->y1 = 0;
|
|
channel->x2 = 0;
|
|
channel->y2 = 0;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_finalize (GObject *object)
|
|
{
|
|
GimpChannel *channel;
|
|
|
|
channel = GIMP_CHANNEL (object);
|
|
|
|
if (channel->segs_in)
|
|
{
|
|
g_free (channel->segs_in);
|
|
channel->segs_in = NULL;
|
|
}
|
|
|
|
if (channel->segs_out)
|
|
{
|
|
g_free (channel->segs_out);
|
|
channel->segs_out = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gsize
|
|
gimp_channel_get_memsize (GimpObject *object,
|
|
gsize *gui_size)
|
|
{
|
|
GimpChannel *channel;
|
|
|
|
channel = GIMP_CHANNEL (object);
|
|
|
|
*gui_size += channel->num_segs_in * sizeof (BoundSeg);
|
|
*gui_size += channel->num_segs_out * sizeof (BoundSeg);
|
|
|
|
return GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size);
|
|
}
|
|
|
|
static GimpItem *
|
|
gimp_channel_duplicate (GimpItem *item,
|
|
GType new_type,
|
|
gboolean add_alpha)
|
|
{
|
|
GimpChannel *channel;
|
|
GimpItem *new_item;
|
|
GimpChannel *new_channel;
|
|
|
|
g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
|
|
|
|
if (g_type_is_a (new_type, GIMP_TYPE_CHANNEL))
|
|
add_alpha = FALSE;
|
|
|
|
new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type,
|
|
add_alpha);
|
|
|
|
if (! GIMP_IS_CHANNEL (new_item))
|
|
return new_item;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
new_channel = GIMP_CHANNEL (new_item);
|
|
|
|
/* set the channel color and opacity */
|
|
new_channel->color = channel->color;
|
|
|
|
new_channel->show_masked = channel->show_masked;
|
|
|
|
/* selection mask variables */
|
|
new_channel->bounds_known = channel->bounds_known;
|
|
new_channel->empty = channel->empty;
|
|
new_channel->x1 = channel->x1;
|
|
new_channel->y1 = channel->y1;
|
|
new_channel->x2 = channel->x2;
|
|
new_channel->y2 = channel->y2;
|
|
|
|
return new_item;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_translate (GimpItem *item,
|
|
gint off_x,
|
|
gint off_y,
|
|
gboolean push_undo)
|
|
{
|
|
GimpChannel *channel;
|
|
GimpChannel *tmp_mask = NULL;
|
|
gint width, height;
|
|
PixelRegion srcPR, destPR;
|
|
guchar empty = TRANSPARENT_OPACITY;
|
|
gint x1, y1, x2, y2;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
|
|
gimp_channel_bounds (channel, &x1, &y1, &x2, &y2);
|
|
x1 = CLAMP ((x1 + off_x), 0, GIMP_ITEM (channel)->width);
|
|
y1 = CLAMP ((y1 + off_y), 0, GIMP_ITEM (channel)->height);
|
|
x2 = CLAMP ((x2 + off_x), 0, GIMP_ITEM (channel)->width);
|
|
y2 = CLAMP ((y2 + off_y), 0, GIMP_ITEM (channel)->height);
|
|
|
|
width = x2 - x1;
|
|
height = y2 - y1;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->translate_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
/* update the old area */
|
|
gimp_drawable_update (GIMP_DRAWABLE (item),
|
|
x1, y1,
|
|
x2 - x1, 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 = gimp_channel_new_mask (gimp_item_get_image (item),
|
|
width, height);
|
|
|
|
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->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 (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->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 (channel)->tiles,
|
|
x1, y1, width, height, TRUE);
|
|
copy_region (&srcPR, &destPR);
|
|
|
|
/* free the temporary mask */
|
|
g_object_unref (tmp_mask);
|
|
}
|
|
|
|
/* calculate new bounds */
|
|
if (width == 0 || height == 0)
|
|
{
|
|
channel->empty = TRUE;
|
|
channel->x1 = 0;
|
|
channel->y1 = 0;
|
|
channel->x2 = GIMP_ITEM (channel)->width;
|
|
channel->y2 = GIMP_ITEM (channel)->height;
|
|
}
|
|
else
|
|
{
|
|
channel->x1 = x1;
|
|
channel->y1 = y1;
|
|
channel->x2 = x2;
|
|
channel->y2 = y2;
|
|
}
|
|
|
|
/* update the new area */
|
|
gimp_drawable_update (GIMP_DRAWABLE (item),
|
|
channel->x1, channel->y1,
|
|
channel->x2 - channel->x1,
|
|
channel->y2 - channel->y1);
|
|
}
|
|
|
|
static void
|
|
gimp_channel_scale (GimpItem *item,
|
|
gint new_width,
|
|
gint new_height,
|
|
gint new_offset_x,
|
|
gint new_offset_y,
|
|
GimpInterpolationType interpolation_type)
|
|
{
|
|
GimpChannel *channel;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
|
|
gimp_image_undo_push_channel_mod (gimp_item_get_image (item),
|
|
_("Scale Channel"),
|
|
channel);
|
|
|
|
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
|
|
{
|
|
new_offset_x = 0;
|
|
new_offset_y = 0;
|
|
}
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->scale (item, new_width, new_height,
|
|
new_offset_x, new_offset_y,
|
|
interpolation_type);
|
|
|
|
/* bounds are now unknown */
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_resize (GimpItem *item,
|
|
gint new_width,
|
|
gint new_height,
|
|
gint offset_x,
|
|
gint offset_y)
|
|
{
|
|
GimpChannel *channel;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
|
|
gimp_image_undo_push_channel_mod (gimp_item_get_image (item),
|
|
_("Resize Channel"),
|
|
channel);
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->resize (item, new_width, new_height,
|
|
offset_x, offset_y);
|
|
|
|
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
|
|
{
|
|
item->offset_x = 0;
|
|
item->offset_y = 0;
|
|
}
|
|
|
|
/* bounds are now unknown */
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_flip (GimpItem *item,
|
|
GimpOrientationType flip_type,
|
|
gdouble axis,
|
|
gboolean clip_result)
|
|
{
|
|
GimpChannel *channel;
|
|
GimpImage *gimage;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
gimage = gimp_item_get_image (item);
|
|
|
|
gimp_image_undo_group_start (gimage, GIMP_UNDO_GROUP_TRANSFORM,
|
|
_("Flip Channel"));
|
|
|
|
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
|
|
clip_result = TRUE;
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->flip (item, flip_type, axis, clip_result);
|
|
|
|
gimp_image_undo_group_end (gimage);
|
|
|
|
/* bounds are now unknown */
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_rotate (GimpItem *item,
|
|
GimpRotationType rotate_type,
|
|
gdouble center_x,
|
|
gdouble center_y,
|
|
gboolean clip_result)
|
|
{
|
|
GimpChannel *channel;
|
|
GimpImage *gimage;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
gimage = gimp_item_get_image (item);
|
|
|
|
gimp_image_undo_group_start (gimage, GIMP_UNDO_GROUP_TRANSFORM,
|
|
_("Rotate Channel"));
|
|
|
|
/* don't default to clip_result == TRUE here */
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->rotate (item,
|
|
rotate_type, center_x, center_y,
|
|
clip_result);
|
|
|
|
gimp_image_undo_group_end (gimage);
|
|
|
|
/* bounds are now unknown */
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_transform (GimpItem *item,
|
|
const GimpMatrix3 *matrix,
|
|
GimpTransformDirection direction,
|
|
GimpInterpolationType interpolation_type,
|
|
gboolean clip_result,
|
|
GimpProgressFunc progress_callback,
|
|
gpointer progress_data)
|
|
{
|
|
GimpChannel *channel;
|
|
GimpImage *gimage;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
gimage = gimp_item_get_image (item);
|
|
|
|
gimp_image_undo_group_start (gimage, GIMP_UNDO_GROUP_TRANSFORM,
|
|
_("Transform Channel"));
|
|
|
|
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
|
|
clip_result = TRUE;
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->transform (item, matrix, direction,
|
|
interpolation_type, clip_result,
|
|
progress_callback, progress_data);
|
|
|
|
gimp_image_undo_group_end (gimage);
|
|
|
|
/* bounds are now unknown */
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_channel_stroke (GimpItem *item,
|
|
GimpDrawable *drawable,
|
|
GimpPaintInfo *paint_info)
|
|
{
|
|
GimpChannel *channel;
|
|
GimpImage *gimage;
|
|
const BoundSeg *bs_in;
|
|
const BoundSeg *bs_out;
|
|
gint num_segs_in;
|
|
gint num_segs_out;
|
|
GimpPaintCore *core;
|
|
gboolean retval;
|
|
|
|
channel = GIMP_CHANNEL (item);
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (channel));
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
|
|
|
|
if (! gimp_channel_boundary (channel, &bs_in, &bs_out,
|
|
&num_segs_in, &num_segs_out,
|
|
0, 0, 0, 0))
|
|
{
|
|
g_message (_("Cannot stroke empty channel."));
|
|
return FALSE;
|
|
}
|
|
|
|
gimp_image_undo_group_start (gimage, GIMP_UNDO_GROUP_PAINT,
|
|
_("Stroke Channel"));
|
|
|
|
core = g_object_new (paint_info->paint_type, NULL);
|
|
|
|
retval = gimp_paint_core_stroke_boundary (core, drawable,
|
|
paint_info->paint_options,
|
|
bs_in, num_segs_in,
|
|
0, 0);
|
|
|
|
g_object_unref (core);
|
|
|
|
gimp_image_undo_group_end (gimage);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_invalidate_boundary (GimpDrawable *drawable)
|
|
{
|
|
GimpChannel *channel = GIMP_CHANNEL (drawable);
|
|
|
|
channel->boundary_known = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_channel_real_boundary (GimpChannel *channel,
|
|
const BoundSeg **segs_in,
|
|
const 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 (! channel->boundary_known)
|
|
{
|
|
/* free the out of date boundary segments */
|
|
if (channel->segs_in)
|
|
g_free (channel->segs_in);
|
|
if (channel->segs_out)
|
|
g_free (channel->segs_out);
|
|
|
|
if (gimp_channel_bounds (channel, &x3, &y3, &x4, &y4))
|
|
{
|
|
pixel_region_init (&bPR, GIMP_DRAWABLE (channel)->tiles,
|
|
x3, y3, (x4 - x3), (y4 - y3), FALSE);
|
|
channel->segs_out = find_mask_boundary (&bPR, &channel->num_segs_out,
|
|
IgnoreBounds,
|
|
x1, y1,
|
|
x2, y2,
|
|
HALF_WAY);
|
|
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 (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height, FALSE);
|
|
channel->segs_in = find_mask_boundary (&bPR, &channel->num_segs_in,
|
|
WithinBounds,
|
|
x1, y1,
|
|
x2, y2,
|
|
HALF_WAY);
|
|
}
|
|
else
|
|
{
|
|
channel->segs_in = NULL;
|
|
channel->num_segs_in = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
channel->segs_in = NULL;
|
|
channel->segs_out = NULL;
|
|
channel->num_segs_in = 0;
|
|
channel->num_segs_out = 0;
|
|
}
|
|
|
|
channel->boundary_known = TRUE;
|
|
}
|
|
|
|
*segs_in = channel->segs_in;
|
|
*segs_out = channel->segs_out;
|
|
*num_segs_in = channel->num_segs_in;
|
|
*num_segs_out = channel->num_segs_out;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_channel_real_bounds (GimpChannel *channel,
|
|
gint *x1,
|
|
gint *y1,
|
|
gint *x2,
|
|
gint *y2)
|
|
{
|
|
PixelRegion maskPR;
|
|
guchar *data, *data1;
|
|
gint x, y;
|
|
gint ex, ey;
|
|
gint tx1, tx2, ty1, ty2;
|
|
gint minx, maxx;
|
|
gpointer pr;
|
|
|
|
/* if the channel's bounds have already been reliably calculated... */
|
|
if (channel->bounds_known)
|
|
{
|
|
*x1 = channel->x1;
|
|
*y1 = channel->y1;
|
|
*x2 = channel->x2;
|
|
*y2 = channel->y2;
|
|
|
|
return ! channel->empty;
|
|
}
|
|
|
|
/* go through and calculate the bounds */
|
|
tx1 = GIMP_ITEM (channel)->width;
|
|
ty1 = GIMP_ITEM (channel)->height;
|
|
tx2 = 0;
|
|
ty2 = 0;
|
|
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->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_ITEM (channel)->width);
|
|
ty2 = CLAMP (ty2 + 1, 0, GIMP_ITEM (channel)->height);
|
|
|
|
if (tx1 == GIMP_ITEM (channel)->width && ty1 == GIMP_ITEM (channel)->height)
|
|
{
|
|
channel->empty = TRUE;
|
|
channel->x1 = 0;
|
|
channel->y1 = 0;
|
|
channel->x2 = GIMP_ITEM (channel)->width;
|
|
channel->y2 = GIMP_ITEM (channel)->height;
|
|
}
|
|
else
|
|
{
|
|
channel->empty = FALSE;
|
|
channel->x1 = tx1;
|
|
channel->y1 = ty1;
|
|
channel->x2 = tx2;
|
|
channel->y2 = ty2;
|
|
}
|
|
|
|
channel->bounds_known = TRUE;
|
|
|
|
*x1 = channel->x1;
|
|
*x2 = channel->x2;
|
|
*y1 = channel->y1;
|
|
*y2 = channel->y2;
|
|
|
|
return ! channel->empty;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_channel_real_is_empty (GimpChannel *channel)
|
|
{
|
|
PixelRegion maskPR;
|
|
guchar *data;
|
|
gint x, y;
|
|
gpointer pr;
|
|
|
|
if (channel->bounds_known)
|
|
return channel->empty;
|
|
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height, FALSE);
|
|
|
|
for (pr = pixel_regions_register (1, &maskPR);
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr))
|
|
{
|
|
/* check if any pixel in the channel 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 (channel->segs_in)
|
|
g_free (channel->segs_in);
|
|
if (channel->segs_out)
|
|
g_free (channel->segs_out);
|
|
|
|
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 = GIMP_ITEM (channel)->width;
|
|
channel->y2 = GIMP_ITEM (channel)->height;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
gimp_channel_real_value (GimpChannel *channel,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
Tile *tile;
|
|
gint val;
|
|
|
|
/* Some checks to cut back on unnecessary work */
|
|
if (channel->bounds_known)
|
|
{
|
|
if (channel->empty)
|
|
return 0;
|
|
else if (x < channel->x1 || x >= channel->x2 ||
|
|
y < channel->y1 || y >= channel->y2)
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (x < 0 || x >= GIMP_ITEM (channel)->width ||
|
|
y < 0 || y >= GIMP_ITEM (channel)->height)
|
|
return 0;
|
|
}
|
|
|
|
tile = tile_manager_get_tile (GIMP_DRAWABLE (channel)->tiles, x, y,
|
|
TRUE, FALSE);
|
|
val = *(guchar *) (tile_data_pointer (tile, x % TILE_WIDTH, y % TILE_HEIGHT));
|
|
tile_release (tile, FALSE);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_feather (GimpChannel *channel,
|
|
gdouble radius_x,
|
|
gdouble radius_y,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion srcPR;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->feather_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
pixel_region_init (&srcPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
gimp_item_width (GIMP_ITEM (channel)),
|
|
gimp_item_height (GIMP_ITEM (channel)),
|
|
FALSE);
|
|
gaussian_blur_region (&srcPR, radius_x, radius_y);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_sharpen (GimpChannel *channel,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion maskPR;
|
|
GimpLut *lut;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->sharpen_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
gimp_item_width (GIMP_ITEM (channel)),
|
|
gimp_item_height (GIMP_ITEM (channel)),
|
|
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);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_clear (GimpChannel *channel,
|
|
const gchar *undo_desc,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion maskPR;
|
|
guchar bg = TRANSPARENT_OPACITY;
|
|
|
|
if (push_undo)
|
|
{
|
|
if (! undo_desc)
|
|
undo_desc = GIMP_CHANNEL_GET_CLASS (channel)->clear_desc;
|
|
|
|
gimp_channel_push_undo (channel, undo_desc);
|
|
}
|
|
else
|
|
{
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
}
|
|
|
|
if (channel->bounds_known && !channel->empty)
|
|
{
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
channel->x1, channel->y1,
|
|
(channel->x2 - channel->x1), (channel->y2 - channel->y1), TRUE);
|
|
color_region (&maskPR, &bg);
|
|
}
|
|
else
|
|
{
|
|
/* clear the mask */
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height, TRUE);
|
|
color_region (&maskPR, &bg);
|
|
}
|
|
|
|
/* we know the bounds */
|
|
channel->bounds_known = TRUE;
|
|
channel->empty = TRUE;
|
|
channel->x1 = 0;
|
|
channel->y1 = 0;
|
|
channel->x2 = GIMP_ITEM (channel)->width;
|
|
channel->y2 = GIMP_ITEM (channel)->height;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_all (GimpChannel *channel,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion maskPR;
|
|
guchar bg = OPAQUE_OPACITY;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->all_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
/* clear the channel */
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height, TRUE);
|
|
color_region (&maskPR, &bg);
|
|
|
|
/* we know the bounds */
|
|
channel->bounds_known = TRUE;
|
|
channel->empty = FALSE;
|
|
channel->x1 = 0;
|
|
channel->y1 = 0;
|
|
channel->x2 = GIMP_ITEM (channel)->width;
|
|
channel->y2 = GIMP_ITEM (channel)->height;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_invert (GimpChannel *channel,
|
|
gboolean push_undo)
|
|
{
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->invert_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
if (channel->bounds_known && channel->empty)
|
|
{
|
|
gimp_channel_all (channel, FALSE);
|
|
}
|
|
else
|
|
{
|
|
PixelRegion maskPR;
|
|
GimpLut *lut;
|
|
|
|
pixel_region_init (&maskPR, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height, TRUE);
|
|
|
|
lut = invert_lut_new (1);
|
|
|
|
pixel_regions_process_parallel ((p_func) gimp_lut_process_inline, lut,
|
|
1, &maskPR);
|
|
|
|
gimp_lut_free (lut);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_border (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion bPR;
|
|
gint x1, y1, x2, y2;
|
|
|
|
if (radius_x < 0 || radius_y < 0)
|
|
return;
|
|
|
|
if (! gimp_channel_bounds (channel, &x1, &y1, &x2, &y2))
|
|
return;
|
|
|
|
if (gimp_channel_is_empty (channel))
|
|
return;
|
|
|
|
if (x1 - radius_x < 0)
|
|
x1 = 0;
|
|
else
|
|
x1 -= radius_x;
|
|
if (x2 + radius_x > GIMP_ITEM (channel)->width)
|
|
x2 = GIMP_ITEM (channel)->width;
|
|
else
|
|
x2 += radius_x;
|
|
|
|
if (y1 - radius_y < 0)
|
|
y1 = 0;
|
|
else
|
|
y1 -= radius_y;
|
|
if (y2 + radius_y > GIMP_ITEM (channel)->height)
|
|
y2 = GIMP_ITEM (channel)->height;
|
|
else
|
|
y2 += radius_y;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->border_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
pixel_region_init (&bPR, GIMP_DRAWABLE (channel)->tiles, x1, y1,
|
|
(x2-x1), (y2-y1), TRUE);
|
|
|
|
border_region (&bPR, radius_x, radius_y);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_grow (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion bPR;
|
|
gint x1, y1, x2, y2;
|
|
|
|
if (radius_x == 0 && radius_y == 0)
|
|
return;
|
|
|
|
if (radius_x <= 0 && radius_y <= 0)
|
|
{
|
|
gimp_channel_shrink (channel, -radius_x, -radius_y, FALSE, push_undo);
|
|
return;
|
|
}
|
|
|
|
if (radius_x < 0 || radius_y < 0)
|
|
return;
|
|
|
|
if (! gimp_channel_bounds (channel, &x1, &y1, &x2, &y2))
|
|
return;
|
|
|
|
if (gimp_channel_is_empty (channel))
|
|
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_ITEM (channel)->width)
|
|
x2 = x2 + radius_x;
|
|
else
|
|
x2 = GIMP_ITEM (channel)->width;
|
|
if (y2 + radius_y < GIMP_ITEM (channel)->height)
|
|
y2 = y2 + radius_y;
|
|
else
|
|
y2 = GIMP_ITEM (channel)->height;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->grow_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
/* need full extents for grow, not! */
|
|
pixel_region_init (&bPR, GIMP_DRAWABLE (channel)->tiles, x1, y1, (x2 - x1),
|
|
(y2 - y1), TRUE);
|
|
|
|
fatten_region (&bPR, radius_x, radius_y);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_channel_real_shrink (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean edge_lock,
|
|
gboolean push_undo)
|
|
{
|
|
PixelRegion bPR;
|
|
gint x1, y1, x2, y2;
|
|
|
|
if (radius_x == 0 && radius_y == 0)
|
|
return;
|
|
|
|
if (radius_x <= 0 && radius_y <= 0)
|
|
{
|
|
gimp_channel_grow (channel, -radius_x, -radius_y, push_undo);
|
|
return;
|
|
}
|
|
|
|
if (radius_x < 0 || radius_y < 0)
|
|
return;
|
|
|
|
if (! gimp_channel_bounds (channel, &x1, &y1, &x2, &y2))
|
|
return;
|
|
|
|
if (gimp_channel_is_empty (channel))
|
|
return;
|
|
|
|
if (x1 > 0)
|
|
x1--;
|
|
if (y1 > 0)
|
|
y1--;
|
|
if (x2 < GIMP_ITEM (channel)->width)
|
|
x2++;
|
|
if (y2 < GIMP_ITEM (channel)->height)
|
|
y2++;
|
|
|
|
if (push_undo)
|
|
gimp_channel_push_undo (channel,
|
|
GIMP_CHANNEL_GET_CLASS (channel)->shrink_desc);
|
|
else
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
|
|
pixel_region_init (&bPR, GIMP_DRAWABLE (channel)->tiles, x1, y1, (x2 - x1),
|
|
(y2 - y1), TRUE);
|
|
|
|
thin_region (&bPR, radius_x, radius_y, edge_lock);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
static void
|
|
gimp_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));
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
GimpChannel *
|
|
gimp_channel_new (GimpImage *gimage,
|
|
gint width,
|
|
gint height,
|
|
const gchar *name,
|
|
const GimpRGB *color)
|
|
{
|
|
GimpChannel *channel;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
|
|
channel = g_object_new (GIMP_TYPE_CHANNEL, NULL);
|
|
|
|
gimp_drawable_configure (GIMP_DRAWABLE (channel),
|
|
gimage,
|
|
0, 0, width, height,
|
|
GIMP_GRAY_IMAGE, name);
|
|
|
|
/* set the channel color and opacity */
|
|
channel->color = *color;
|
|
|
|
channel->show_masked = TRUE;
|
|
|
|
/* selection mask variables */
|
|
channel->x2 = width;
|
|
channel->y2 = height;
|
|
|
|
return channel;
|
|
}
|
|
|
|
GimpChannel *
|
|
gimp_channel_new_from_alpha (GimpImage *gimage,
|
|
GimpLayer *layer,
|
|
const gchar *name,
|
|
const GimpRGB *color)
|
|
{
|
|
GimpChannel *channel;
|
|
gint x, y;
|
|
gint width;
|
|
gint height;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
|
|
g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
|
|
g_return_val_if_fail (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)), NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
|
|
width = gimp_image_get_width (gimage);
|
|
height = gimp_image_get_height (gimage);
|
|
|
|
channel = gimp_channel_new (gimage, width, height, name, color);
|
|
|
|
gimp_channel_clear (channel, NULL, FALSE);
|
|
|
|
if (gimp_rectangle_intersect (0, 0, width, height,
|
|
GIMP_ITEM (layer)->offset_x,
|
|
GIMP_ITEM (layer)->offset_y,
|
|
GIMP_ITEM (layer)->width,
|
|
GIMP_ITEM (layer)->height,
|
|
&x, &y, &width, &height))
|
|
{
|
|
PixelRegion srcPR, destPR;
|
|
|
|
pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles,
|
|
x - GIMP_ITEM (layer)->offset_x,
|
|
y - GIMP_ITEM (layer)->offset_y,
|
|
width, height, FALSE);
|
|
pixel_region_init (&destPR, GIMP_DRAWABLE (channel)->tiles,
|
|
x, y, width, height, TRUE);
|
|
|
|
extract_alpha_region (&srcPR, NULL, &destPR);
|
|
|
|
channel->bounds_known = FALSE;
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
GimpChannel *
|
|
gimp_channel_new_from_component (GimpImage *gimage,
|
|
GimpChannelType type,
|
|
const gchar *name,
|
|
const GimpRGB *color)
|
|
{
|
|
GimpChannel *channel;
|
|
TileManager *projection;
|
|
PixelRegion src;
|
|
PixelRegion dest;
|
|
gint width;
|
|
gint height;
|
|
gint pixel;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
|
|
pixel = gimp_image_get_component_index (gimage, type);
|
|
|
|
g_return_val_if_fail (pixel != -1, NULL);
|
|
|
|
projection = gimp_image_projection (gimage);
|
|
width = tile_manager_width (projection);
|
|
height = tile_manager_height (projection);
|
|
|
|
channel = gimp_channel_new (gimage, width, height, name, color);
|
|
|
|
pixel_region_init (&src, projection,
|
|
0, 0, width, height, FALSE);
|
|
pixel_region_init (&dest, GIMP_DRAWABLE (channel)->tiles,
|
|
0, 0, width, height, TRUE);
|
|
|
|
copy_component (&src, &dest, pixel);
|
|
|
|
return channel;
|
|
}
|
|
|
|
void
|
|
gimp_channel_set_color (GimpChannel *channel,
|
|
const GimpRGB *color,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
if (gimp_rgba_distance (&channel->color, color) > 0.0001)
|
|
{
|
|
if (push_undo)
|
|
{
|
|
GimpImage *gimage;
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (channel));
|
|
|
|
if (gimage)
|
|
gimp_image_undo_push_channel_color (gimage, _("Set Channel Color"),
|
|
channel);
|
|
}
|
|
|
|
channel->color = *color;
|
|
|
|
gimp_drawable_update (GIMP_DRAWABLE (channel),
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height);
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_channel_get_color (const GimpChannel *channel,
|
|
GimpRGB *color)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
*color = channel->color;
|
|
}
|
|
|
|
gdouble
|
|
gimp_channel_get_opacity (const GimpChannel *channel)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), GIMP_OPACITY_TRANSPARENT);
|
|
|
|
return channel->color.a;
|
|
}
|
|
|
|
void
|
|
gimp_channel_set_opacity (GimpChannel *channel,
|
|
gdouble opacity,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
opacity = CLAMP (opacity, GIMP_OPACITY_TRANSPARENT, GIMP_OPACITY_OPAQUE);
|
|
|
|
if (channel->color.a != opacity)
|
|
{
|
|
if (push_undo)
|
|
{
|
|
GimpImage *gimage;
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (channel));
|
|
|
|
if (gimage)
|
|
gimp_image_undo_push_channel_color (gimage, _("Set Channel Opacity"),
|
|
channel);
|
|
}
|
|
|
|
channel->color.a = opacity;
|
|
|
|
gimp_drawable_update (GIMP_DRAWABLE (channel),
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gimp_channel_get_show_masked (GimpChannel *channel)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
|
|
|
|
return channel->show_masked;
|
|
}
|
|
|
|
void
|
|
gimp_channel_set_show_masked (GimpChannel *channel,
|
|
gboolean show_masked)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
if (show_masked != channel->show_masked)
|
|
{
|
|
channel->show_masked = show_masked ? TRUE : FALSE;
|
|
|
|
gimp_drawable_update (GIMP_DRAWABLE (channel),
|
|
0, 0,
|
|
GIMP_ITEM (channel)->width,
|
|
GIMP_ITEM (channel)->height);
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_channel_push_undo (GimpChannel *channel,
|
|
const gchar *undo_desc)
|
|
{
|
|
GimpImage *gimage;
|
|
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (channel));
|
|
|
|
g_return_if_fail (GIMP_IS_IMAGE (gimage));
|
|
|
|
gimp_image_undo_push_mask (gimage, undo_desc, channel);
|
|
|
|
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
|
|
}
|
|
|
|
|
|
/******************************/
|
|
/* selection mask functions */
|
|
/******************************/
|
|
|
|
GimpChannel *
|
|
gimp_channel_new_mask (GimpImage *gimage,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
GimpRGB black = { 0.0, 0.0, 0.0, 0.5 };
|
|
GimpChannel *new_channel;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
|
|
|
|
/* Create the new channel */
|
|
new_channel = gimp_channel_new (gimage, width, height,
|
|
_("Selection Mask"), &black);
|
|
|
|
/* Set the validate procedure */
|
|
tile_manager_set_validate_proc (GIMP_DRAWABLE (new_channel)->tiles,
|
|
gimp_channel_validate);
|
|
|
|
return new_channel;
|
|
}
|
|
|
|
gboolean
|
|
gimp_channel_boundary (GimpChannel *channel,
|
|
const BoundSeg **segs_in,
|
|
const BoundSeg **segs_out,
|
|
gint *num_segs_in,
|
|
gint *num_segs_out,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
|
|
g_return_val_if_fail (segs_in != NULL, FALSE);
|
|
g_return_val_if_fail (segs_out != NULL, FALSE);
|
|
g_return_val_if_fail (num_segs_in != NULL, FALSE);
|
|
g_return_val_if_fail (num_segs_out != NULL, FALSE);
|
|
|
|
return GIMP_CHANNEL_GET_CLASS (channel)->boundary (channel,
|
|
segs_in, segs_out,
|
|
num_segs_in, num_segs_out,
|
|
x1, y1,
|
|
x2, y2);
|
|
}
|
|
|
|
gboolean
|
|
gimp_channel_bounds (GimpChannel *channel,
|
|
gint *x1,
|
|
gint *y1,
|
|
gint *x2,
|
|
gint *y2)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
|
|
g_return_val_if_fail (x1 != NULL, FALSE);
|
|
g_return_val_if_fail (y1 != NULL, FALSE);
|
|
g_return_val_if_fail (x2 != NULL, FALSE);
|
|
g_return_val_if_fail (y2 != NULL, FALSE);
|
|
|
|
return GIMP_CHANNEL_GET_CLASS (channel)->bounds (channel, x1, y1, x2, y2);
|
|
}
|
|
|
|
gboolean
|
|
gimp_channel_is_empty (GimpChannel *channel)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
|
|
|
|
return GIMP_CHANNEL_GET_CLASS (channel)->is_empty (channel);
|
|
}
|
|
|
|
gint
|
|
gimp_channel_value (GimpChannel *channel,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), 0);
|
|
|
|
return GIMP_CHANNEL_GET_CLASS (channel)->value (channel, x, y);
|
|
}
|
|
|
|
void
|
|
gimp_channel_feather (GimpChannel *channel,
|
|
gdouble radius_x,
|
|
gdouble radius_y,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->feather (channel, radius_x, radius_y,
|
|
push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_sharpen (GimpChannel *channel,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->sharpen (channel, push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_clear (GimpChannel *channel,
|
|
const gchar *undo_desc,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->clear (channel, undo_desc, push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_all (GimpChannel *channel,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->all (channel, push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_invert (GimpChannel *channel,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->invert (channel, push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_border (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->border (channel, radius_x, radius_y,
|
|
push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_grow (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->grow (channel, radius_x, radius_y,
|
|
push_undo);
|
|
}
|
|
|
|
void
|
|
gimp_channel_shrink (GimpChannel *channel,
|
|
gint radius_x,
|
|
gint radius_y,
|
|
gboolean edge_lock,
|
|
gboolean push_undo)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CHANNEL (channel));
|
|
|
|
GIMP_CHANNEL_GET_CLASS (channel)->shrink (channel, radius_x, radius_y,
|
|
edge_lock, push_undo);
|
|
}
|