gimp/app/gimplayer.c

1567 lines
40 KiB
C

/* TODO: make sure has_alpha gets set */
/* 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 <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include "drawable.h"
#include "errors.h"
#include "floating_sel.h"
#include "gdisplay.h"
#include "gimage.h"
#include "gimage_mask.h"
#include "layer.h"
#include "paint_funcs.h"
#include "temp_buf.h"
#include "parasitelist.h"
#include "undo.h"
#include "gimpsignal.h"
#include "libgimp/gimpintl.h"
#include "layer_pvt.h"
#include "tile_manager_pvt.h"
#include "tile.h" /* ick. */
enum {
REMOVED,
LAST_SIGNAL
};
static void gimp_layer_class_init (GimpLayerClass *klass);
static void gimp_layer_init (GimpLayer *layer);
static void gimp_layer_destroy (GtkObject *object);
static void layer_invalidate_preview (GtkObject *);
static void gimp_layer_mask_class_init (GimpLayerMaskClass *klass);
static void gimp_layer_mask_init (GimpLayerMask *layermask);
static void gimp_layer_mask_destroy (GtkObject *object);
static guint layer_signals[LAST_SIGNAL] = { 0 };
/*
static gint layer_mask_signals[LAST_SIGNAL] = { 0 };
*/
static GimpDrawableClass *layer_parent_class = NULL;
static GimpChannelClass *layer_mask_parent_class = NULL;
GtkType
gimp_layer_get_type ()
{
static GtkType layer_type = 0;
if (!layer_type)
{
GtkTypeInfo layer_info =
{
"GimpLayer",
sizeof (GimpLayer),
sizeof (GimpLayerClass),
(GtkClassInitFunc) gimp_layer_class_init,
(GtkObjectInitFunc) gimp_layer_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL,
};
layer_type = gtk_type_unique (gimp_drawable_get_type (), &layer_info);
}
return layer_type;
}
static void
gimp_layer_class_init (GimpLayerClass *class)
{
GtkObjectClass *object_class;
GimpDrawableClass *drawable_class;
object_class = (GtkObjectClass*) class;
drawable_class = (GimpDrawableClass*) class;
layer_parent_class = gtk_type_class (gimp_drawable_get_type ());
layer_signals[REMOVED] =
gimp_signal_new ("removed",
0, object_class->type, 0, gimp_sigtype_void);
gtk_object_class_add_signals (object_class, layer_signals, LAST_SIGNAL);
object_class->destroy = gimp_layer_destroy;
drawable_class->invalidate_preview = layer_invalidate_preview;
}
static void
gimp_layer_init (GimpLayer *layer)
{
}
GtkType
gimp_layer_mask_get_type ()
{
static GtkType layer_mask_type = 0;
if (!layer_mask_type)
{
GtkTypeInfo layer_mask_info =
{
"GimpLayerMask",
sizeof (GimpLayerMask),
sizeof (GimpLayerMaskClass),
(GtkClassInitFunc) gimp_layer_mask_class_init,
(GtkObjectInitFunc) gimp_layer_mask_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL,
};
layer_mask_type = gtk_type_unique (gimp_channel_get_type (), &layer_mask_info);
}
return layer_mask_type;
}
static void
gimp_layer_mask_class_init (GimpLayerMaskClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
layer_mask_parent_class = gtk_type_class (gimp_channel_get_type ());
/*
gtk_object_class_add_signals (object_class, layer_mask_signals, LAST_SIGNAL);
*/
object_class->destroy = gimp_layer_mask_destroy;
}
static void
gimp_layer_mask_init (GimpLayerMask *layermask)
{
}
/* static functions */
static void transform_color (GImage *, PixelRegion *,
PixelRegion *, GimpDrawable *, int);
static void layer_preview_scale (int, unsigned char *, PixelRegion *,
PixelRegion *, int);
/*
* Static variables
*/
extern int global_drawable_ID;
int layer_get_count = 0;
/********************************/
/* Local function definitions */
static void
layer_invalidate_preview (GtkObject *object)
{
GimpLayer *layer;
g_return_if_fail (object != NULL);
g_return_if_fail (GIMP_IS_LAYER (object));
layer = GIMP_LAYER (object);
if (layer_is_floating_sel (layer))
floating_sel_invalidate (layer);
}
static void
transform_color (gimage, layerPR, bufPR, drawable, type)
GImage * gimage;
PixelRegion * layerPR;
PixelRegion * bufPR;
GimpDrawable *drawable;
int type;
{
int i, h;
unsigned char * s, * d;
void * pr;
for (pr = pixel_regions_register (2, layerPR, bufPR); pr != NULL; pr = pixel_regions_process (pr))
{
h = layerPR->h;
s = bufPR->data;
d = layerPR->data;
while (h--)
{
for (i = 0; i < layerPR->w; i++)
{
gimage_transform_color (gimage, drawable,
s + (i * bufPR->bytes),
d + (i * layerPR->bytes), type);
/* copy alpha channel */
d[(i + 1) * layerPR->bytes - 1] = s[(i + 1) * bufPR->bytes - 1];
}
s += bufPR->rowstride;
d += layerPR->rowstride;
}
}
}
/**************************/
/* Function definitions */
Layer *
layer_new (gimage, width, height, type, name, opacity, mode)
GimpImage* gimage;
int width, height;
int type;
char * name;
int opacity;
int mode;
{
Layer * layer;
if (width == 0 || height == 0) {
g_message (_("Zero width or height layers not allowed."));
return NULL;
}
layer = gtk_type_new (gimp_layer_get_type ());
gimp_drawable_configure (GIMP_DRAWABLE(layer),
gimage, width, height, type, name);
/* allocate the memory for this layer */
layer->linked = 0;
layer->preserve_trans = 0;
/* no layer mask is present at start */
layer->mask = NULL;
layer->apply_mask = 0;
layer->edit_mask = 0;
layer->show_mask = 0;
/* mode and opacity */
layer->mode = mode;
layer->opacity = opacity;
/* floating selection variables */
layer->fs.backing_store = NULL;
layer->fs.drawable = NULL;
layer->fs.initial = TRUE;
layer->fs.boundary_known = FALSE;
layer->fs.segs = NULL;
layer->fs.num_segs = 0;
return layer;
}
Layer *
layer_ref (Layer *layer)
{
gtk_object_ref (GTK_OBJECT (layer));
gtk_object_sink (GTK_OBJECT (layer));
return layer;
}
void
layer_unref (Layer *layer)
{
gtk_object_unref (GTK_OBJECT (layer));
}
Layer *
layer_copy (layer, add_alpha)
Layer * layer;
int add_alpha;
{
char * layer_name;
Layer * new_layer;
int new_type;
char *ext;
int number;
char *name;
int len;
PixelRegion srcPR, destPR;
/* formulate the new layer name */
name = layer_get_name(layer);
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 */
layer_name = g_strdup (name);
else
layer_name = g_strdup_printf (_("%s copy"), name);
/* when copying a layer, the copy ALWAYS has an alpha channel */
if (add_alpha)
{
switch (GIMP_DRAWABLE(layer)->type)
{
case RGB_GIMAGE:
new_type = RGBA_GIMAGE;
break;
case GRAY_GIMAGE:
new_type = GRAYA_GIMAGE;
break;
case INDEXED_GIMAGE:
new_type = INDEXEDA_GIMAGE;
break;
default:
new_type = GIMP_DRAWABLE(layer)->type;
break;
}
}
else
new_type = GIMP_DRAWABLE(layer)->type;
/* allocate a new layer object */
new_layer = layer_new (GIMP_DRAWABLE(layer)->gimage,
GIMP_DRAWABLE(layer)->width,
GIMP_DRAWABLE(layer)->height,
new_type, layer_name, layer->opacity, layer->mode);
if (!new_layer) {
g_message (_("layer_copy: could not allocate new layer"));
goto cleanup;
}
GIMP_DRAWABLE(new_layer)->offset_x = GIMP_DRAWABLE(layer)->offset_x;
GIMP_DRAWABLE(new_layer)->offset_y = GIMP_DRAWABLE(layer)->offset_y;
GIMP_DRAWABLE(new_layer)->visible = GIMP_DRAWABLE(layer)->visible;
new_layer->linked = layer->linked;
new_layer->preserve_trans = layer->preserve_trans;
/* copy the contents across layers */
if (new_type == GIMP_DRAWABLE(layer)->type)
{
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE(new_layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
copy_region (&srcPR, &destPR);
}
else
{
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE(new_layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
add_alpha_region (&srcPR, &destPR);
}
/* duplicate the layer mask if necessary */
if (layer->mask)
{
new_layer->mask = layer_mask_ref (layer_mask_copy (layer->mask));
new_layer->apply_mask = layer->apply_mask;
new_layer->edit_mask = layer->edit_mask;
new_layer->show_mask = layer->show_mask;
}
/* copy the parasites */
GIMP_DRAWABLE(new_layer)->parasites
= parasite_list_copy(GIMP_DRAWABLE(layer)->parasites);
cleanup:
/* free up the layer_name memory */
g_free (layer_name);
return new_layer;
}
Layer *
layer_from_tiles (gimage_ptr, drawable, tiles, name, opacity, mode)
void *gimage_ptr;
GimpDrawable *drawable;
TileManager *tiles;
char *name;
int opacity;
int mode;
{
GImage * gimage;
Layer * new_layer;
int layer_type;
PixelRegion layerPR, bufPR;
/* Function copies buffer to a layer
* taking into consideration the possibility of transforming
* the contents to meet the requirements of the target image type
*/
/* If no tile manager, return NULL */
if (!tiles)
return NULL;
gimage = (GImage *) gimage_ptr;
layer_type = drawable_type_with_alpha ( (drawable));
/* Create the new layer */
new_layer = layer_new (0, tiles->width, tiles->height,
layer_type, name, opacity, mode);
if (!new_layer) {
g_message (_("layer_from_tiles: could not allocate new layer"));
return NULL;
}
/* Configure the pixel regions */
pixel_region_init (&layerPR, GIMP_DRAWABLE(new_layer)->tiles, 0, 0, GIMP_DRAWABLE(new_layer)->width, GIMP_DRAWABLE(new_layer)->height, TRUE);
pixel_region_init (&bufPR, tiles, 0, 0, GIMP_DRAWABLE(new_layer)->width, GIMP_DRAWABLE(new_layer)->height, FALSE);
if ((tiles->bpp == 4 && GIMP_DRAWABLE(new_layer)->type == RGBA_GIMAGE) ||
(tiles->bpp == 2 && GIMP_DRAWABLE(new_layer)->type == GRAYA_GIMAGE))
/* If we want a layer the same type as the buffer */
copy_region (&bufPR, &layerPR);
else
/* Transform the contents of the buf to the new_layer */
transform_color (gimage, &layerPR, &bufPR, GIMP_DRAWABLE(new_layer),
(tiles->bpp == 4) ? RGB : GRAY);
return new_layer;
}
LayerMask *
layer_add_mask (layer, mask)
Layer * layer;
LayerMask * mask;
{
if (layer->mask)
return NULL;
layer->mask = layer_mask_ref (mask);
mask->layer = layer;
/* Set the application mode in the layer to "apply" */
layer->apply_mask = 1;
layer->edit_mask = 1;
layer->show_mask = 0;
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
return layer->mask;
}
LayerMask *
layer_create_mask (layer, add_mask_type)
Layer * layer;
AddMaskType add_mask_type;
{
PixelRegion maskPR, layerPR;
LayerMask *mask;
char * mask_name;
unsigned char black[3] = {0, 0, 0};
unsigned char white_mask = OPAQUE_OPACITY;
unsigned char black_mask = TRANSPARENT_OPACITY;
mask_name = g_strdup_printf (_("%s mask"), GIMP_DRAWABLE(layer)->name);
/* Create the layer mask */
mask = layer_mask_new (GIMP_DRAWABLE(layer)->gimage, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height,
mask_name, OPAQUE_OPACITY, black);
GIMP_DRAWABLE(mask)->offset_x = GIMP_DRAWABLE(layer)->offset_x;
GIMP_DRAWABLE(mask)->offset_y = GIMP_DRAWABLE(layer)->offset_y;
pixel_region_init (&maskPR, GIMP_DRAWABLE(mask)->tiles, 0, 0, GIMP_DRAWABLE(mask)->width, GIMP_DRAWABLE(mask)->height, TRUE);
switch (add_mask_type)
{
case WhiteMask:
color_region (&maskPR, &white_mask);
break;
case BlackMask:
color_region (&maskPR, &black_mask);
break;
case AlphaMask:
/* Extract the layer's alpha channel */
if (layer_has_alpha (layer))
{
pixel_region_init (&layerPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
extract_alpha_region (&layerPR, NULL, &maskPR);
}
break;
}
g_free (mask_name);
return mask;
}
Layer *
layer_get_ID (ID)
int ID;
{
GimpDrawable *drawable;
drawable = drawable_get_ID (ID);
if (drawable && GIMP_IS_LAYER (drawable))
return GIMP_LAYER (drawable);
else
return NULL;
}
void
layer_delete (Layer * layer)
{
gtk_object_unref (GTK_OBJECT (layer));
}
static void
gimp_layer_destroy (GtkObject *object)
{
GimpLayer *layer;
g_return_if_fail (object != NULL);
g_return_if_fail (GIMP_IS_LAYER (object));
layer = GIMP_LAYER (object);
/* if a layer mask exists, free it */
if (layer->mask)
layer_mask_delete (layer->mask);
/* free the layer boundary if it exists */
if (layer->fs.segs)
g_free (layer->fs.segs);
/* free the floating selection if it exists */
if (layer_is_floating_sel (layer))
{
tile_manager_destroy (layer->fs.backing_store);
}
if (GTK_OBJECT_CLASS (layer_parent_class)->destroy)
(*GTK_OBJECT_CLASS (layer_parent_class)->destroy) (object);
}
/* The removed signal is sent out when the layer is no longer
* associcated with an image. It's needed because layers 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
layer_removed (Layer *layer, gpointer image)
{
g_return_if_fail (layer != NULL);
g_return_if_fail (GIMP_IS_LAYER (layer));
gtk_signal_emit (GTK_OBJECT (layer), layer_signals[REMOVED]);
}
void
layer_apply_mask (layer, mode)
Layer * layer;
int mode;
{
PixelRegion srcPR, maskPR;
if (!layer->mask)
return;
/* this operation can only be done to layers with an alpha channel */
if (! layer_has_alpha (layer))
return;
/* Need to save the mask here for undo */
if (mode == APPLY)
{
/* Put this apply mask operation on the undo stack
*/
drawable_apply_image (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, NULL, FALSE);
/* Combine the current layer's alpha channel and the mask */
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
pixel_region_init (&maskPR, GIMP_DRAWABLE(layer->mask)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
apply_mask_to_region (&srcPR, &maskPR, OPAQUE_OPACITY);
GIMP_DRAWABLE(layer)->preview_valid = FALSE;
layer->mask = NULL;
layer->apply_mask = 0;
layer->edit_mask = 0;
layer->show_mask = 0;
}
else if (mode == DISCARD)
{
layer->mask = NULL;
layer->apply_mask = 0;
layer->edit_mask = 0;
layer->show_mask = 0;
}
}
static void
layer_translate_lowlevel (layer, off_x, off_y, temporary)
Layer * layer;
int off_x, off_y;
gboolean temporary;
{
if (!temporary)
{
/* the undo call goes here */
/*g_warning ("setting undo for layer translation");*/
undo_push_layer_displace (GIMP_DRAWABLE(layer)->gimage, layer);
}
/* update the affected region */
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
if (!temporary)
{
/* invalidate the selection boundary because of a layer modification */
layer_invalidate_boundary (layer);
}
/* update the layer offsets */
GIMP_DRAWABLE(layer)->offset_x += off_x;
GIMP_DRAWABLE(layer)->offset_y += off_y;
/* update the affected region */
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
if (layer->mask)
{
GIMP_DRAWABLE(layer->mask)->offset_x += off_x;
GIMP_DRAWABLE(layer->mask)->offset_y += off_y;
if (!temporary)
{
/* invalidate the mask preview */
drawable_invalidate_preview (GIMP_DRAWABLE(layer->mask));
}
}
}
void
layer_temporarily_translate (layer, off_x, off_y)
Layer * layer;
int off_x, off_y;
{
layer_translate_lowlevel (layer, off_x, off_y, TRUE);
}
void
layer_translate (layer, off_x, off_y)
Layer * layer;
int off_x, off_y;
{
layer_translate_lowlevel (layer, off_x, off_y, FALSE);
}
void
layer_add_alpha (layer)
Layer *layer;
{
PixelRegion srcPR, destPR;
TileManager *new_tiles;
int type;
/* Don't bother if the layer already has alpha */
switch (GIMP_DRAWABLE(layer)->type)
{
case RGB_GIMAGE:
type = RGBA_GIMAGE;
break;
case GRAY_GIMAGE:
type = GRAYA_GIMAGE;
break;
case INDEXED_GIMAGE:
type = INDEXEDA_GIMAGE;
break;
case RGBA_GIMAGE:
case GRAYA_GIMAGE:
case INDEXEDA_GIMAGE:
default:
return;
break;
}
/* Configure the pixel regions */
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
/* Allocate the new layer, configure dest region */
new_tiles = tile_manager_new (GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, (GIMP_DRAWABLE(layer)->bytes + 1));
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
/* Add an alpha channel */
add_alpha_region (&srcPR, &destPR);
/* Push the layer on the undo stack */
undo_push_layer_mod (GIMP_DRAWABLE(layer)->gimage, layer);
/* Configure the new layer */
GIMP_DRAWABLE(layer)->tiles = new_tiles;
GIMP_DRAWABLE(layer)->type = type;
GIMP_DRAWABLE(layer)->bytes = GIMP_DRAWABLE(layer)->bytes + 1;
GIMP_DRAWABLE(layer)->has_alpha = TYPE_HAS_ALPHA (type);
gtk_signal_emit_by_name(GTK_OBJECT(gimp_drawable_gimage(GIMP_DRAWABLE(layer))),
"restructure");
}
void
layer_scale (layer, new_width, new_height, local_origin)
Layer *layer;
int new_width, new_height;
int local_origin;
{
PixelRegion srcPR, destPR;
TileManager *new_tiles;
if (new_width == 0 || new_height == 0)
return;
/* Update the old layer position */
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
/* Configure the pixel regions */
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
/* Allocate the new layer, configure dest region */
new_tiles = tile_manager_new (new_width, new_height, GIMP_DRAWABLE(layer)->bytes);
pixel_region_init (&destPR, new_tiles, 0, 0, new_width, new_height, TRUE);
/* Scale the layer -
* If the layer is of type INDEXED, then we don't use pixel-value
* resampling because that doesn't necessarily make sense for INDEXED
* images.
*/
if ((GIMP_DRAWABLE(layer)->type == INDEXED_GIMAGE) || (GIMP_DRAWABLE(layer)->type == INDEXEDA_GIMAGE))
scale_region_no_resample (&srcPR, &destPR);
else
scale_region (&srcPR, &destPR);
/* Push the layer on the undo stack */
undo_push_layer_mod (GIMP_DRAWABLE(layer)->gimage, layer);
/* Configure the new layer */
if (local_origin)
{
int cx, cy;
cx = GIMP_DRAWABLE(layer)->offset_x + GIMP_DRAWABLE(layer)->width / 2;
cy = GIMP_DRAWABLE(layer)->offset_y + GIMP_DRAWABLE(layer)->height / 2;
GIMP_DRAWABLE(layer)->offset_x = cx - (new_width / 2);
GIMP_DRAWABLE(layer)->offset_y = cy - (new_height / 2);
}
else
{
double xrat, yrat;
xrat = (double) new_width / (double) GIMP_DRAWABLE(layer)->width;
yrat = (double) new_height / (double) GIMP_DRAWABLE(layer)->height;
GIMP_DRAWABLE(layer)->offset_x = (int) (xrat * GIMP_DRAWABLE(layer)->offset_x);
GIMP_DRAWABLE(layer)->offset_y = (int) (yrat * GIMP_DRAWABLE(layer)->offset_y);
}
GIMP_DRAWABLE(layer)->tiles = new_tiles;
GIMP_DRAWABLE(layer)->width = new_width;
GIMP_DRAWABLE(layer)->height = new_height;
/* If there is a layer mask, make sure it gets scaled also */
if (layer->mask)
{
GIMP_DRAWABLE(layer->mask)->offset_x = GIMP_DRAWABLE(layer)->offset_x;
GIMP_DRAWABLE(layer->mask)->offset_y = GIMP_DRAWABLE(layer)->offset_y;
channel_scale (GIMP_CHANNEL(layer->mask), new_width, new_height);
}
/* Update the new layer position */
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
}
void
layer_resize (layer, new_width, new_height, offx, offy)
Layer *layer;
int new_width, new_height;
int offx, offy;
{
PixelRegion srcPR, destPR;
TileManager *new_tiles;
int w, h;
int 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(layer)->width), 0, new_width);
y2 = CLAMP ((offy + GIMP_DRAWABLE(layer)->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 layer position */
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
/* Configure the pixel regions */
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, x1, y1, w, h, FALSE);
/* Allocate the new layer, configure dest region */
new_tiles = tile_manager_new (new_width, new_height, GIMP_DRAWABLE(layer)->bytes);
pixel_region_init (&destPR, new_tiles, 0, 0, new_width, new_height, TRUE);
/* fill with the fill color */
if (layer_has_alpha (layer))
{
/* Set to transparent and black */
unsigned char bg[4] = {0, 0, 0, 0};
color_region (&destPR, bg);
}
else
{
unsigned char bg[3];
gimage_get_background (GIMP_DRAWABLE(layer)->gimage, GIMP_DRAWABLE(layer), bg);
color_region (&destPR, bg);
}
pixel_region_init (&destPR, new_tiles, x2, y2, w, h, TRUE);
/* copy from the old to the new */
if (w && h)
copy_region (&srcPR, &destPR);
/* Push the layer on the undo stack */
undo_push_layer_mod (GIMP_DRAWABLE(layer)->gimage, layer);
/* Configure the new layer */
GIMP_DRAWABLE(layer)->tiles = new_tiles;
GIMP_DRAWABLE(layer)->offset_x = x1 + GIMP_DRAWABLE(layer)->offset_x - x2;
GIMP_DRAWABLE(layer)->offset_y = y1 + GIMP_DRAWABLE(layer)->offset_y - y2;
GIMP_DRAWABLE(layer)->width = new_width;
GIMP_DRAWABLE(layer)->height = new_height;
/* If there is a layer mask, make sure it gets resized also */
if (layer->mask) {
GIMP_DRAWABLE(layer->mask)->offset_x = GIMP_DRAWABLE(layer)->offset_x;
GIMP_DRAWABLE(layer->mask)->offset_y = GIMP_DRAWABLE(layer)->offset_y;
channel_resize (GIMP_CHANNEL(layer->mask), new_width, new_height, offx, offy);
}
/* update the new layer area */
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
}
BoundSeg *
layer_boundary (layer, num_segs)
Layer *layer;
int *num_segs;
{
BoundSeg *new_segs;
/* Create the four boundary segments that encompass this
* layer's boundary.
*/
new_segs = (BoundSeg *) g_malloc (sizeof (BoundSeg) * 4);
*num_segs = 4;
/* if the layer is a floating selection */
if (layer_is_floating_sel (layer))
{
/* if the owner drawable is a channel, just return nothing */
if (drawable_channel (layer->fs.drawable))
{
*num_segs = 0;
return NULL;
}
/* otherwise, set the layer to the owner drawable */
else
layer = GIMP_LAYER(layer->fs.drawable);
}
new_segs[0].x1 = GIMP_DRAWABLE(layer)->offset_x;
new_segs[0].y1 = GIMP_DRAWABLE(layer)->offset_y;
new_segs[0].x2 = GIMP_DRAWABLE(layer)->offset_x;
new_segs[0].y2 = GIMP_DRAWABLE(layer)->offset_y + GIMP_DRAWABLE(layer)->height;
new_segs[0].open = 1;
new_segs[1].x1 = GIMP_DRAWABLE(layer)->offset_x;
new_segs[1].y1 = GIMP_DRAWABLE(layer)->offset_y;
new_segs[1].x2 = GIMP_DRAWABLE(layer)->offset_x + GIMP_DRAWABLE(layer)->width;
new_segs[1].y2 = GIMP_DRAWABLE(layer)->offset_y;
new_segs[1].open = 1;
new_segs[2].x1 = GIMP_DRAWABLE(layer)->offset_x + GIMP_DRAWABLE(layer)->width;
new_segs[2].y1 = GIMP_DRAWABLE(layer)->offset_y;
new_segs[2].x2 = GIMP_DRAWABLE(layer)->offset_x + GIMP_DRAWABLE(layer)->width;
new_segs[2].y2 = GIMP_DRAWABLE(layer)->offset_y + GIMP_DRAWABLE(layer)->height;
new_segs[2].open = 0;
new_segs[3].x1 = GIMP_DRAWABLE(layer)->offset_x;
new_segs[3].y1 = GIMP_DRAWABLE(layer)->offset_y + GIMP_DRAWABLE(layer)->height;
new_segs[3].x2 = GIMP_DRAWABLE(layer)->offset_x + GIMP_DRAWABLE(layer)->width;
new_segs[3].y2 = GIMP_DRAWABLE(layer)->offset_y + GIMP_DRAWABLE(layer)->height;
new_segs[3].open = 0;
return new_segs;
}
void
layer_invalidate_boundary (layer)
Layer *layer;
{
GImage *gimage;
Channel *mask;
/* first get the selection mask channel */
if (! (gimage = drawable_gimage(GIMP_DRAWABLE(layer))))
return;
/* Turn the current selection off */
gdisplays_selection_visibility (gimage, SelectionOff);
/* clear the affected region surrounding the layer */
gdisplays_selection_visibility (gimage, SelectionLayerOff);
mask = gimage_get_mask (gimage);
/* Only bother with the bounds if there is a selection */
if (! channel_is_empty (mask))
{
mask->bounds_known = FALSE;
mask->boundary_known = FALSE;
}
if (layer_is_floating_sel(layer))
floating_sel_invalidate(layer);
}
int
layer_pick_correlate (layer, x, y)
Layer *layer;
int x, y;
{
Tile *tile;
Tile *mask_tile;
int val;
/* Is the point inside the layer?
* First transform the point to layer coordinates...
*/
x -= GIMP_DRAWABLE(layer)->offset_x;
y -= GIMP_DRAWABLE(layer)->offset_y;
if (x >= 0 && x < GIMP_DRAWABLE(layer)->width &&
y >= 0 && y < GIMP_DRAWABLE(layer)->height &&
GIMP_DRAWABLE(layer)->visible)
{
/* If the point is inside, and the layer has no
* alpha channel, success!
*/
if (! layer_has_alpha (layer))
return TRUE;
/* Otherwise, determine if the alpha value at
* the given point is non-zero
*/
tile = tile_manager_get_tile (GIMP_DRAWABLE(layer)->tiles, x, y, TRUE, FALSE);
val = * ((unsigned char*) tile_data_pointer (tile,
x % TILE_WIDTH,
y % TILE_HEIGHT) +
tile_bpp (tile) - 1);
if (layer->mask)
{
unsigned char *ptr;
mask_tile = tile_manager_get_tile (GIMP_DRAWABLE(layer->mask)->tiles, x, y, TRUE, FALSE);
ptr = tile_data_pointer (mask_tile, x % TILE_WIDTH, y % TILE_HEIGHT);
val = val * (*ptr) / 255;
tile_release (mask_tile, FALSE);
}
tile_release (tile, FALSE);
if (val > 63)
return TRUE;
}
return FALSE;
}
/********************/
/* access functions */
void
layer_set_name (Layer *layer, char *name)
{
gimp_drawable_set_name(GIMP_DRAWABLE(layer), name);
}
char *
layer_get_name (Layer *layer)
{
return gimp_drawable_get_name(GIMP_DRAWABLE(layer));
}
unsigned char *
layer_data (layer)
Layer * layer;
{
return NULL;
}
LayerMask *
layer_mask (layer)
Layer * layer;
{
return layer->mask;
}
int
layer_has_alpha (layer)
Layer * layer;
{
if (GIMP_DRAWABLE(layer)->type == RGBA_GIMAGE ||
GIMP_DRAWABLE(layer)->type == GRAYA_GIMAGE ||
GIMP_DRAWABLE(layer)->type == INDEXEDA_GIMAGE)
return 1;
else
return 0;
}
int
layer_is_floating_sel (layer)
Layer *layer;
{
if (layer->fs.drawable != NULL)
return 1;
else
return 0;
}
int
layer_linked (Layer *layer)
{
return layer->linked;
}
TempBuf *
layer_preview (layer, w, h)
Layer *layer;
int w, h;
{
GImage *gimage;
TempBuf *preview_buf;
PixelRegion srcPR, destPR;
int type;
int bytes;
int subsample;
type = 0;
bytes = 0;
/* The easy way */
if (GIMP_DRAWABLE(layer)->preview_valid &&
GIMP_DRAWABLE(layer)->preview->width == w &&
GIMP_DRAWABLE(layer)->preview->height == h)
return GIMP_DRAWABLE(layer)->preview;
/* The hard way */
else
{
gimage = GIMP_DRAWABLE(layer)->gimage;
switch (GIMP_DRAWABLE(layer)->type)
{
case RGB_GIMAGE: case RGBA_GIMAGE:
type = 0;
bytes = GIMP_DRAWABLE(layer)->bytes;
break;
case GRAY_GIMAGE: case GRAYA_GIMAGE:
type = 1;
bytes = GIMP_DRAWABLE(layer)->bytes;
break;
case INDEXED_GIMAGE: case INDEXEDA_GIMAGE:
type = 2;
bytes = (GIMP_DRAWABLE(layer)->type == INDEXED_GIMAGE) ? 3 : 4;
break;
}
/* calculate 'acceptable' subsample */
subsample = 1;
/* handle some truncation errors */
if (w < 1) w = 1;
if (h < 1) h = 1;
while ((w * (subsample + 1) * 2 < GIMP_DRAWABLE(layer)->width) &&
(h * (subsample + 1) * 2 < GIMP_DRAWABLE(layer)->height))
subsample = subsample + 1;
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
preview_buf = temp_buf_new (w, h, bytes, 0, 0, NULL);
destPR.bytes = preview_buf->bytes;
destPR.w = w;
destPR.h = h;
destPR.rowstride = w * destPR.bytes;
destPR.data = temp_buf_data (preview_buf);
layer_preview_scale (type, gimage->cmap, &srcPR, &destPR, subsample);
if (GIMP_DRAWABLE(layer)->preview)
temp_buf_free (GIMP_DRAWABLE(layer)->preview);
GIMP_DRAWABLE(layer)->preview = preview_buf;
GIMP_DRAWABLE(layer)->preview_valid = TRUE;
return GIMP_DRAWABLE(layer)->preview;
}
}
TempBuf *
layer_mask_preview (layer, w, h)
Layer *layer;
int w, h;
{
TempBuf *preview_buf;
LayerMask *mask;
PixelRegion srcPR, destPR;
int subsample;
mask = layer->mask;
if (!mask)
return NULL;
/* The easy way */
if (GIMP_DRAWABLE(mask)->preview_valid &&
GIMP_DRAWABLE(mask)->preview->width == w &&
GIMP_DRAWABLE(mask)->preview->height == h)
return GIMP_DRAWABLE(mask)->preview;
/* The hard way */
else
{
/* calculate 'acceptable' subsample */
subsample = 1;
if (w < 1) w = 1;
if (h < 1) h = 1;
while ((w * (subsample + 1) * 2 < GIMP_DRAWABLE(layer)->width) &&
(h * (subsample + 1) * 2 < GIMP_DRAWABLE(layer)->height))
subsample = subsample + 1;
pixel_region_init (&srcPR, GIMP_DRAWABLE(mask)->tiles, 0, 0, GIMP_DRAWABLE(mask)->width, GIMP_DRAWABLE(mask)->height, FALSE);
preview_buf = temp_buf_new (w, h, 1, 0, 0, NULL);
destPR.bytes = preview_buf->bytes;
destPR.w = w;
destPR.h = h;
destPR.rowstride = w * destPR.bytes;
destPR.data = temp_buf_data (preview_buf);
layer_preview_scale (1 /* GRAY */, NULL, &srcPR, &destPR, subsample);
if (GIMP_DRAWABLE(mask)->preview)
temp_buf_free (GIMP_DRAWABLE(mask)->preview);
GIMP_DRAWABLE(mask)->preview = preview_buf;
GIMP_DRAWABLE(mask)->preview_valid = TRUE;
return GIMP_DRAWABLE(mask)->preview;
}
}
Tattoo
layer_get_tattoo(const Layer *layer)
{
return (gimp_drawable_get_tattoo(GIMP_DRAWABLE(layer)));
}
void
layer_invalidate_previews (GimpImage* gimage)
{
GSList * tmp;
Layer * layer;
g_return_if_fail (gimage != NULL);
tmp = gimage->layers;
while (tmp)
{
layer = (Layer *) tmp->data;
drawable_invalidate_preview (GIMP_DRAWABLE(layer));
tmp = g_slist_next (tmp);
}
}
static void
layer_preview_scale (type, cmap, srcPR, destPR, subsample)
int type;
unsigned char *cmap;
PixelRegion *srcPR;
PixelRegion *destPR;
int subsample;
{
#define EPSILON 0.000001
unsigned char * src, * s;
unsigned char * dest, * d;
double * row, * r;
int destwidth;
int src_row, src_col;
int bytes, b;
int width, height;
int orig_width, orig_height;
double x_rat, y_rat;
double x_cum, y_cum;
double x_last, y_last;
double * x_frac, y_frac, tot_frac;
int i, j;
int frac;
int advance_dest;
unsigned char rgb[MAX_CHANNELS];
orig_width = srcPR->w / subsample;
orig_height = srcPR->h / subsample;
width = destPR->w;
height = destPR->h;
/* Some calculations... */
bytes = destPR->bytes;
destwidth = destPR->rowstride;
/* the data pointers... */
src = (unsigned char *) g_malloc (orig_width * bytes);
dest = destPR->data;
/* find the ratios of old x to new x and old y to new y */
x_rat = (double) orig_width / (double) width;
y_rat = (double) orig_height / (double) height;
/* allocate an array to help with the calculations */
row = (double *) g_malloc (sizeof (double) * width * bytes);
x_frac = (double *) g_malloc (sizeof (double) * (width + orig_width));
/* initialize the pre-calculated pixel fraction array */
src_col = 0;
x_cum = (double) src_col;
x_last = x_cum;
for (i = 0; i < width + orig_width; i++)
{
if (x_cum + x_rat <= (src_col + 1 + EPSILON))
{
x_cum += x_rat;
x_frac[i] = x_cum - x_last;
}
else
{
src_col ++;
x_frac[i] = src_col - x_last;
}
x_last += x_frac[i];
}
/* clear the "row" array */
memset (row, 0, sizeof (double) * width * bytes);
/* counters... */
src_row = 0;
y_cum = (double) src_row;
y_last = y_cum;
pixel_region_get_row (srcPR, 0, src_row * subsample, orig_width * subsample, src, subsample);
/* Scale the selected region */
for (i = 0; i < height; )
{
src_col = 0;
x_cum = (double) src_col;
/* determine the fraction of the src pixel we are using for y */
if (y_cum + y_rat <= (src_row + 1 + EPSILON))
{
y_cum += y_rat;
y_frac = y_cum - y_last;
advance_dest = TRUE;
}
else
{
src_row ++;
y_frac = src_row - y_last;
advance_dest = FALSE;
}
y_last += y_frac;
s = src;
r = row;
frac = 0;
j = width;
while (j)
{
tot_frac = x_frac[frac++] * y_frac;
/* If indexed, transform the color to RGB */
if (type == 2)
{
map_to_color (2, cmap, s, rgb);
r[RED_PIX] += rgb[RED_PIX] * tot_frac;
r[GREEN_PIX] += rgb[GREEN_PIX] * tot_frac;
r[BLUE_PIX] += rgb[BLUE_PIX] * tot_frac;
if (bytes == 4)
r[ALPHA_PIX] += s[ALPHA_I_PIX] * tot_frac;
}
else
for (b = 0; b < bytes; b++)
r[b] += s[b] * tot_frac;
/* increment the destination */
if (x_cum + x_rat <= (src_col + 1 + EPSILON))
{
r += bytes;
x_cum += x_rat;
j--;
}
/* increment the source */
else
{
s += srcPR->bytes;
src_col++;
}
}
if (advance_dest)
{
tot_frac = 1.0 / (x_rat * y_rat);
/* copy "row" to "dest" */
d = dest;
r = row;
j = width;
while (j--)
{
b = bytes;
while (b--)
*d++ = (unsigned char) (*r++ * tot_frac);
}
dest += destwidth;
/* clear the "row" array */
memset (row, 0, sizeof (double) * destwidth);
i++;
}
else
pixel_region_get_row (srcPR, 0, src_row * subsample, orig_width * subsample, src, subsample);
}
/* free up temporary arrays */
g_free (row);
g_free (x_frac);
g_free (src);
}
static void
gimp_layer_mask_destroy (GtkObject *object)
{
GimpLayerMask *layermask;
g_return_if_fail (object != NULL);
g_return_if_fail (GIMP_IS_LAYER_MASK (object));
layermask = GIMP_LAYER_MASK (object);
if (GTK_OBJECT_CLASS (layer_mask_parent_class)->destroy)
(*GTK_OBJECT_CLASS (layer_mask_parent_class)->destroy) (object);
}
LayerMask *
layer_mask_new (GimpImage* gimage, int width, int height, char *name, int opacity,
unsigned char *col)
{
LayerMask * layer_mask;
int i;
layer_mask = gtk_type_new (gimp_layer_mask_get_type ());
gimp_drawable_configure (GIMP_DRAWABLE(layer_mask),
gimage, width, height, GRAY_GIMAGE, name);
/* set the layer_mask color and opacity */
for (i = 0; i < 3; i++)
GIMP_CHANNEL(layer_mask)->col[i] = col[i];
GIMP_CHANNEL(layer_mask)->opacity = opacity;
GIMP_CHANNEL(layer_mask)->show_masked = 1;
/* selection mask variables */
GIMP_CHANNEL(layer_mask)->empty = TRUE;
GIMP_CHANNEL(layer_mask)->segs_in = NULL;
GIMP_CHANNEL(layer_mask)->segs_out = NULL;
GIMP_CHANNEL(layer_mask)->num_segs_in = 0;
GIMP_CHANNEL(layer_mask)->num_segs_out = 0;
GIMP_CHANNEL(layer_mask)->bounds_known = TRUE;
GIMP_CHANNEL(layer_mask)->boundary_known = TRUE;
GIMP_CHANNEL(layer_mask)->x1 = GIMP_CHANNEL(layer_mask)->y1 = 0;
GIMP_CHANNEL(layer_mask)->x2 = width;
GIMP_CHANNEL(layer_mask)->y2 = height;
return layer_mask;
}
LayerMask *
layer_mask_copy (LayerMask *layer_mask)
{
char * layer_mask_name;
LayerMask * new_layer_mask;
PixelRegion srcPR, destPR;
/* formulate the new layer_mask name */
layer_mask_name = g_strdup_printf (_("%s copy"), GIMP_DRAWABLE(layer_mask)->name);
/* allocate a new layer_mask object */
new_layer_mask = layer_mask_new (GIMP_DRAWABLE(layer_mask)->gimage,
GIMP_DRAWABLE(layer_mask)->width,
GIMP_DRAWABLE(layer_mask)->height,
layer_mask_name,
GIMP_CHANNEL(layer_mask)->opacity,
GIMP_CHANNEL(layer_mask)->col);
GIMP_DRAWABLE(new_layer_mask)->visible = GIMP_DRAWABLE(layer_mask)->visible;
GIMP_DRAWABLE(new_layer_mask)->offset_x = GIMP_DRAWABLE(layer_mask)->offset_x;
GIMP_DRAWABLE(new_layer_mask)->offset_y = GIMP_DRAWABLE(layer_mask)->offset_y;
GIMP_CHANNEL(new_layer_mask)->show_masked = GIMP_CHANNEL(layer_mask)->show_masked;
/* copy the contents across layer masks */
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer_mask)->tiles, 0, 0,
GIMP_DRAWABLE(layer_mask)->width,
GIMP_DRAWABLE(layer_mask)->height, FALSE);
pixel_region_init (&destPR, GIMP_DRAWABLE(new_layer_mask)->tiles, 0, 0, GIMP_DRAWABLE(layer_mask)->width, GIMP_DRAWABLE(layer_mask)->height, TRUE);
copy_region (&srcPR, &destPR);
/* free up the layer_mask_name memory */
g_free (layer_mask_name);
return new_layer_mask;
}
LayerMask *
layer_mask_get_ID (int ID)
{
GimpDrawable *drawable;
drawable = drawable_get_ID (ID);
if (drawable && GIMP_IS_LAYER_MASK (drawable))
return GIMP_LAYER_MASK (drawable);
else
return NULL;
}
void
layer_mask_delete (LayerMask * layermask)
{
gtk_object_unref (GTK_OBJECT (layermask));
}
LayerMask *
layer_mask_ref (LayerMask *mask)
{
gtk_object_ref (GTK_OBJECT (mask));
gtk_object_sink (GTK_OBJECT (mask));
return mask;
}
void
layer_mask_unref (LayerMask *mask)
{
gtk_object_unref (GTK_OBJECT (mask));
}
void
channel_layer_mask (Channel *mask, Layer * layer)
{
PixelRegion srcPR, destPR;
unsigned char empty = 0;
int 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->mask)->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);
copy_region (&srcPR, &destPR);
mask->bounds_known = FALSE;
}