mirror of https://github.com/GNOME/gimp.git
1244 lines
29 KiB
C
1244 lines
29 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include "appenv.h"
|
|
#include "drawable.h"
|
|
#include "errors.h"
|
|
#include "floating_sel.h"
|
|
#include "gdisplay.h"
|
|
#include "gimage.h"
|
|
#include "gimage_mask.h"
|
|
#include "interface.h"
|
|
#include "layer.h"
|
|
#include "layers_dialog.h"
|
|
#include "linked.h"
|
|
#include "paint_funcs.h"
|
|
#include "temp_buf.h"
|
|
#include "undo.h"
|
|
|
|
/* static functions */
|
|
|
|
static void transform_color (GImage *, PixelRegion *,
|
|
PixelRegion *, int, int);
|
|
static void layer_preview_scale (int, unsigned char *, PixelRegion *,
|
|
PixelRegion *, int);
|
|
|
|
/*
|
|
* Static variables
|
|
*/
|
|
extern int global_drawable_ID;
|
|
static link_ptr layer_list = NULL;
|
|
|
|
|
|
/********************************/
|
|
/* Local function definitions */
|
|
|
|
static void
|
|
transform_color (gimage, layerPR, bufPR, drawable_id, type)
|
|
GImage * gimage;
|
|
PixelRegion * layerPR;
|
|
PixelRegion * bufPR;
|
|
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_id,
|
|
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 */
|
|
|
|
void
|
|
layer_allocate (layer, width, height, bpp)
|
|
Layer * layer;
|
|
int width;
|
|
int height;
|
|
int bpp;
|
|
{
|
|
layer->tiles = tile_manager_new (width, height, bpp);
|
|
}
|
|
|
|
|
|
void
|
|
layer_deallocate (layer)
|
|
Layer * layer;
|
|
{
|
|
if (layer->tiles)
|
|
tile_manager_destroy (layer->tiles);
|
|
}
|
|
|
|
|
|
Layer *
|
|
layer_new (gimage_ID, width, height, type, name, opacity, mode)
|
|
int gimage_ID;
|
|
int width, height;
|
|
int type;
|
|
char * name;
|
|
int opacity;
|
|
int mode;
|
|
{
|
|
Layer * layer;
|
|
|
|
if (width == 0 || height == 0) {
|
|
warning ("Zero width or height layers not allowed.");
|
|
return NULL;
|
|
}
|
|
|
|
layer = (Layer *) g_malloc (sizeof (Layer));
|
|
|
|
if (!name)
|
|
name = "unnamed";
|
|
|
|
layer->name = (char *) g_malloc (strlen (name) + 1);
|
|
strcpy (layer->name, name);
|
|
|
|
/* set size information */
|
|
layer->offset_x = 0;
|
|
layer->offset_y = 0;
|
|
layer->width = width;
|
|
layer->height = height;
|
|
layer->type = type;
|
|
|
|
switch (type)
|
|
{
|
|
case RGB_GIMAGE:
|
|
layer->bytes = 3; break;
|
|
case GRAY_GIMAGE:
|
|
layer->bytes = 1; break;
|
|
case RGBA_GIMAGE:
|
|
layer->bytes = 4; break;
|
|
case GRAYA_GIMAGE:
|
|
layer->bytes = 2; break;
|
|
case INDEXED_GIMAGE:
|
|
layer->bytes = 1; break;
|
|
case INDEXEDA_GIMAGE:
|
|
layer->bytes = 2; break;
|
|
default:
|
|
warning ("Layer type not supported.\n");
|
|
break;
|
|
}
|
|
|
|
/* allocate the memory for this layer */
|
|
layer_allocate (layer, width, height, layer->bytes);
|
|
layer->visible = 1;
|
|
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;
|
|
layer->dirty = 0;
|
|
|
|
/* give this layer an ID */
|
|
layer->ID = global_drawable_ID++;
|
|
layer->gimage_ID = gimage_ID;
|
|
|
|
/* preview variables */
|
|
layer->preview = NULL;
|
|
layer->preview_valid = FALSE;
|
|
|
|
/* floating selection variables */
|
|
layer->fs.backing_store = NULL;
|
|
layer->fs.drawable = -1;
|
|
layer->fs.initial = TRUE;
|
|
layer->fs.boundary_known = FALSE;
|
|
layer->fs.segs = NULL;
|
|
layer->fs.num_segs = 0;
|
|
|
|
/* add the new layer to the global list */
|
|
layer_list = append_to_list (layer_list, (void *) layer);
|
|
|
|
return layer;
|
|
}
|
|
|
|
|
|
Layer *
|
|
layer_copy (layer, add_alpha)
|
|
Layer * layer;
|
|
int add_alpha;
|
|
{
|
|
char * layer_name;
|
|
Layer * new_layer;
|
|
int new_type;
|
|
PixelRegion srcPR, destPR;
|
|
|
|
/* formulate the new layer name */
|
|
layer_name = (char *) g_malloc (strlen (layer->name) + 6);
|
|
sprintf (layer_name, "%s copy", layer->name);
|
|
|
|
/* when copying a layer, the copy ALWAYS has an alpha channel */
|
|
if (add_alpha)
|
|
{
|
|
switch (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 = layer->type;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
new_type = layer->type;
|
|
|
|
/* allocate a new layer object */
|
|
new_layer = layer_new (layer->gimage_ID, layer->width, layer->height,
|
|
new_type, layer_name, layer->opacity, layer->mode);
|
|
if (!new_layer) {
|
|
warning("layer_copy: could not allocate new layer");
|
|
goto cleanup;
|
|
}
|
|
|
|
new_layer->offset_x = layer->offset_x;
|
|
new_layer->offset_y = layer->offset_y;
|
|
new_layer->visible = layer->visible;
|
|
new_layer->linked = layer->linked;
|
|
new_layer->preserve_trans = layer->preserve_trans;
|
|
|
|
/* copy the contents across layers */
|
|
if (new_type == layer->type)
|
|
{
|
|
pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE);
|
|
pixel_region_init (&destPR, new_layer->tiles, 0, 0, layer->width, layer->height, TRUE);
|
|
copy_region (&srcPR, &destPR);
|
|
}
|
|
else
|
|
{
|
|
pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE);
|
|
pixel_region_init (&destPR, new_layer->tiles, 0, 0, layer->width, layer->height, TRUE);
|
|
add_alpha_region (&srcPR, &destPR);
|
|
}
|
|
|
|
/* duplicate the layer mask if necessary */
|
|
if (layer->mask)
|
|
{
|
|
new_layer->mask = channel_copy (layer->mask);
|
|
new_layer->apply_mask = layer->apply_mask;
|
|
new_layer->edit_mask = layer->edit_mask;
|
|
new_layer->show_mask = layer->show_mask;
|
|
}
|
|
|
|
cleanup:
|
|
/* free up the layer_name memory */
|
|
g_free (layer_name);
|
|
|
|
return new_layer;
|
|
}
|
|
|
|
|
|
Layer *
|
|
layer_from_tiles (gimage_ptr, drawable_id, tiles, name, opacity, mode)
|
|
void *gimage_ptr;
|
|
int drawable_id;
|
|
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_id);
|
|
|
|
/* Create the new layer */
|
|
new_layer = layer_new (0, tiles->levels[0].width, tiles->levels[0].height,
|
|
layer_type, name, opacity, mode);
|
|
|
|
if (!new_layer) {
|
|
warning("layer_from_tiles: could not allocate new layer");
|
|
return NULL;
|
|
}
|
|
|
|
/* Configure the pixel regions */
|
|
pixel_region_init (&layerPR, new_layer->tiles, 0, 0, new_layer->width, new_layer->height, TRUE);
|
|
pixel_region_init (&bufPR, tiles, 0, 0, new_layer->width, new_layer->height, FALSE);
|
|
|
|
if ((tiles->levels[0].bpp == 4 && new_layer->type == RGBA_GIMAGE) ||
|
|
(tiles->levels[0].bpp == 2 && 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, new_layer->ID,
|
|
(tiles->levels[0].bpp == 4) ? RGB : GRAY);
|
|
|
|
return new_layer;
|
|
}
|
|
|
|
|
|
Channel *
|
|
layer_add_mask (layer, mask_id)
|
|
Layer * layer;
|
|
int mask_id;
|
|
{
|
|
Channel *mask;
|
|
|
|
if (layer->mask)
|
|
return NULL;
|
|
if ((mask = channel_get_ID (mask_id)) == NULL)
|
|
return NULL;
|
|
|
|
layer->mask = mask;
|
|
mask->layer_ID = layer->ID;
|
|
|
|
/* Set the application mode in the layer to "apply" */
|
|
layer->apply_mask = 1;
|
|
layer->edit_mask = 1;
|
|
layer->show_mask = 0;
|
|
|
|
drawable_update (layer->ID, 0, 0, layer->width, layer->height);
|
|
|
|
return layer->mask;
|
|
}
|
|
|
|
|
|
Channel *
|
|
layer_create_mask (layer, add_mask_type)
|
|
Layer * layer;
|
|
AddMaskType add_mask_type;
|
|
{
|
|
PixelRegion maskPR, layerPR;
|
|
Channel *mask;
|
|
char * mask_name;
|
|
unsigned char black[3] = {0, 0, 0};
|
|
unsigned char white_mask = OPAQUE;
|
|
unsigned char black_mask = TRANSPARENT;
|
|
|
|
mask_name = (char *) g_malloc (strlen (layer->name) + strlen ("mask") + 2);
|
|
sprintf (mask_name, "%s mask", layer->name);
|
|
|
|
/* Create the layer mask */
|
|
mask = channel_new (layer->gimage_ID, layer->width, layer->height,
|
|
mask_name, OPAQUE, black);
|
|
|
|
pixel_region_init (&maskPR, mask->tiles, 0, 0, mask->width, 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, layer->tiles, 0, 0, layer->width, layer->height, FALSE);
|
|
extract_alpha_region (&layerPR, NULL, &maskPR);
|
|
}
|
|
break;
|
|
}
|
|
|
|
g_free (mask_name);
|
|
return mask;
|
|
}
|
|
|
|
|
|
Layer *
|
|
layer_get_ID (ID)
|
|
int ID;
|
|
{
|
|
link_ptr tmp = layer_list;
|
|
Layer * layer;
|
|
|
|
while (tmp)
|
|
{
|
|
layer = (Layer *) tmp->data;
|
|
if (layer->ID == ID)
|
|
return layer;
|
|
|
|
tmp = next_item (tmp);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
layer_delete (layer)
|
|
Layer * layer;
|
|
{
|
|
/* remove this image from the global list */
|
|
layer_list = remove_from_list (layer_list, (void *) layer);
|
|
|
|
/* free the shared memory */
|
|
layer_deallocate (layer);
|
|
|
|
/* if a layer mask exists, free it */
|
|
if (layer->mask)
|
|
channel_delete (layer->mask);
|
|
|
|
/* free the layer name buffer */
|
|
g_free (layer->name);
|
|
|
|
/* free the layer preview if it exists */
|
|
if (layer->preview)
|
|
temp_buf_free (layer->preview);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
g_free (layer);
|
|
}
|
|
|
|
|
|
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
|
|
*/
|
|
layer_apply_image (layer, 0, 0, layer->width, layer->height, NULL, FALSE);
|
|
|
|
/* Combine the current layer's alpha channel and the mask */
|
|
pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, TRUE);
|
|
pixel_region_init (&maskPR, layer->mask->tiles, 0, 0, layer->width, layer->height, FALSE);
|
|
|
|
apply_mask_to_region (&srcPR, &maskPR, OPAQUE);
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
layer_translate (layer, off_x, off_y)
|
|
Layer * layer;
|
|
int off_x, off_y;
|
|
{
|
|
/* the undo call goes here */
|
|
undo_push_layer_displace (gimage_get_ID (layer->gimage_ID), layer->ID);
|
|
|
|
/* update the affected region */
|
|
drawable_update (layer->ID, 0, 0, layer->width, layer->height);
|
|
|
|
/* invalidate the selection boundary because of a layer modification */
|
|
layer_invalidate_boundary (layer);
|
|
|
|
/* update the layer offsets */
|
|
layer->offset_x += off_x;
|
|
layer->offset_y += off_y;
|
|
|
|
/* update the affected region */
|
|
drawable_update (layer->ID, 0, 0, layer->width, layer->height);
|
|
|
|
/* invalidate the mask preview */
|
|
if (layer->mask)
|
|
drawable_invalidate_preview (layer->mask->ID);
|
|
}
|
|
|
|
|
|
void
|
|
layer_apply_image (layer, x1, y1, x2, y2, tiles, sparse)
|
|
Layer * layer;
|
|
int x1, y1, x2, y2;
|
|
TileManager * tiles;
|
|
int sparse;
|
|
{
|
|
if (! tiles)
|
|
/* Need to push an undo operation */
|
|
undo_push_image (gimage_get_ID (layer->gimage_ID), layer->ID, x1, y1, x2, y2);
|
|
else
|
|
undo_push_image_mod (gimage_get_ID (layer->gimage_ID), layer->ID, x1, y1, x2, y2, tiles, sparse);
|
|
}
|
|
|
|
|
|
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 (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, layer->tiles, 0, 0, layer->width, layer->height, FALSE);
|
|
|
|
/* Allocate the new layer, configure dest region */
|
|
new_tiles = tile_manager_new (layer->width, layer->height, (layer->bytes + 1));
|
|
pixel_region_init (&destPR, new_tiles, 0, 0, layer->width, layer->height, TRUE);
|
|
|
|
/* Add an alpha channel */
|
|
add_alpha_region (&srcPR, &destPR);
|
|
|
|
/* Push the layer on the undo stack */
|
|
undo_push_layer_mod (gimage_get_ID (layer->gimage_ID), layer);
|
|
|
|
/* Configure the new layer */
|
|
layer->tiles = new_tiles;
|
|
layer->type = type;
|
|
layer->bytes = layer->bytes + 1;
|
|
|
|
/* update gdisplay titles to reflect the possibility of
|
|
* this layer being the only layer in the gimage
|
|
*/
|
|
gdisplays_update_title (layer->gimage_ID);
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
/* If there is a layer mask, make sure it gets scaled also */
|
|
if (layer->mask)
|
|
channel_scale (layer->mask, new_width, new_height);
|
|
|
|
/* Update the old layer position */
|
|
drawable_update (layer->ID, 0, 0, layer->width, layer->height);
|
|
|
|
/* Configure the pixel regions */
|
|
pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE);
|
|
|
|
/* Allocate the new layer, configure dest region */
|
|
new_tiles = tile_manager_new (new_width, new_height, 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 ((layer->type == INDEXED_GIMAGE) || (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 (gimage_get_ID (layer->gimage_ID), layer);
|
|
|
|
/* Configure the new layer */
|
|
if (local_origin)
|
|
{
|
|
int cx, cy;
|
|
|
|
cx = layer->offset_x + layer->width / 2;
|
|
cy = layer->offset_y + layer->height / 2;
|
|
|
|
layer->offset_x = cx - (new_width / 2);
|
|
layer->offset_y = cy - (new_height / 2);
|
|
}
|
|
else
|
|
{
|
|
double xrat, yrat;
|
|
|
|
xrat = (double) new_width / (double) layer->width;
|
|
yrat = (double) new_height / (double) layer->height;
|
|
|
|
layer->offset_x = (int) (xrat * layer->offset_x);
|
|
layer->offset_y = (int) (yrat * layer->offset_y);
|
|
}
|
|
layer->tiles = new_tiles;
|
|
layer->width = new_width;
|
|
layer->height = new_height;
|
|
|
|
/* Update the new layer position */
|
|
drawable_update (layer->ID, 0, 0, layer->width, 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;
|
|
|
|
/* If there is a layer mask, make sure it gets resized also */
|
|
if (layer->mask)
|
|
channel_resize (layer->mask, new_width, new_height, offx, offy);
|
|
|
|
x1 = BOUNDS (offx, 0, new_width);
|
|
y1 = BOUNDS (offy, 0, new_height);
|
|
x2 = BOUNDS ((offx + layer->width), 0, new_width);
|
|
y2 = BOUNDS ((offy + 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 (layer->ID, 0, 0, layer->width, layer->height);
|
|
|
|
/* Configure the pixel regions */
|
|
pixel_region_init (&srcPR, layer->tiles, x1, y1, w, h, FALSE);
|
|
|
|
/* Allocate the new layer, configure dest region */
|
|
new_tiles = tile_manager_new (new_width, new_height, 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 (gimage_get_ID (layer->gimage_ID), layer->ID, 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 (gimage_get_ID (layer->gimage_ID), layer);
|
|
|
|
/* Configure the new layer */
|
|
layer->tiles = new_tiles;
|
|
layer->offset_x = x1 + layer->offset_x - x2;
|
|
layer->offset_y = y1 + layer->offset_y - y2;
|
|
layer->width = new_width;
|
|
layer->height = new_height;
|
|
|
|
/* update the new layer area */
|
|
drawable_update (layer->ID, 0, 0, layer->width, 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 = layer_get_ID (layer->fs.drawable);
|
|
}
|
|
|
|
new_segs[0].x1 = layer->offset_x;
|
|
new_segs[0].y1 = layer->offset_y;
|
|
new_segs[0].x2 = layer->offset_x;
|
|
new_segs[0].y2 = layer->offset_y + layer->height;
|
|
new_segs[0].open = 1;
|
|
|
|
new_segs[1].x1 = layer->offset_x;
|
|
new_segs[1].y1 = layer->offset_y;
|
|
new_segs[1].x2 = layer->offset_x + layer->width;
|
|
new_segs[1].y2 = layer->offset_y;
|
|
new_segs[1].open = 1;
|
|
|
|
new_segs[2].x1 = layer->offset_x + layer->width;
|
|
new_segs[2].y1 = layer->offset_y;
|
|
new_segs[2].x2 = layer->offset_x + layer->width;
|
|
new_segs[2].y2 = layer->offset_y + layer->height;
|
|
new_segs[2].open = 0;
|
|
|
|
new_segs[3].x1 = layer->offset_x;
|
|
new_segs[3].y1 = layer->offset_y + layer->height;
|
|
new_segs[3].x2 = layer->offset_x + layer->width;
|
|
new_segs[3].y2 = layer->offset_y + 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 = gimage_get_ID (layer->gimage_ID)))
|
|
return;
|
|
|
|
/* Turn the current selection off */
|
|
gdisplays_selection_visibility (gimage->ID, SelectionOff);
|
|
|
|
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;
|
|
}
|
|
|
|
/* clear the affected region surrounding the layer */
|
|
gdisplays_selection_visibility (layer->gimage_ID, SelectionLayerOff);
|
|
}
|
|
|
|
|
|
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 -= layer->offset_x;
|
|
y -= layer->offset_y;
|
|
if (x >= 0 && x < layer->width &&
|
|
y >= 0 && y < layer->height &&
|
|
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 (layer->tiles, x, y, 0);
|
|
tile_ref (tile);
|
|
|
|
val = tile->data[tile->bpp * (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH) + 1) - 1];
|
|
if (layer->mask)
|
|
{
|
|
mask_tile = tile_manager_get_tile (layer->mask->tiles, x, y, 0);
|
|
tile_ref (mask_tile);
|
|
val = (val * mask_tile->data[mask_tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH)]) / 255;
|
|
}
|
|
|
|
tile_unref (tile, FALSE);
|
|
|
|
if (val > 63)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/********************/
|
|
/* access functions */
|
|
|
|
unsigned char *
|
|
layer_data (layer)
|
|
Layer * layer;
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
Channel *
|
|
layer_mask (layer)
|
|
Layer * layer;
|
|
{
|
|
return layer->mask;
|
|
}
|
|
|
|
|
|
int
|
|
layer_has_alpha (layer)
|
|
Layer * layer;
|
|
{
|
|
if (layer->type == RGBA_GIMAGE ||
|
|
layer->type == GRAYA_GIMAGE ||
|
|
layer->type == INDEXEDA_GIMAGE)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
layer_is_floating_sel (layer)
|
|
Layer *layer;
|
|
{
|
|
if (layer->fs.drawable != -1)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
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 (layer->preview_valid &&
|
|
layer->preview->width == w &&
|
|
layer->preview->height == h)
|
|
return layer->preview;
|
|
/* The hard way */
|
|
else
|
|
{
|
|
gimage = gimage_get_ID (layer->gimage_ID);
|
|
switch (layer->type)
|
|
{
|
|
case RGB_GIMAGE: case RGBA_GIMAGE:
|
|
type = 0;
|
|
bytes = layer->bytes;
|
|
break;
|
|
case GRAY_GIMAGE: case GRAYA_GIMAGE:
|
|
type = 1;
|
|
bytes = layer->bytes;
|
|
break;
|
|
case INDEXED_GIMAGE: case INDEXEDA_GIMAGE:
|
|
type = 2;
|
|
bytes = (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 < layer->width) &&
|
|
(h * (subsample + 1) * 2 < layer->height))
|
|
subsample = subsample + 1;
|
|
|
|
pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, 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 (layer->preview)
|
|
temp_buf_free (layer->preview);
|
|
|
|
layer->preview = preview_buf;
|
|
layer->preview_valid = TRUE;
|
|
|
|
return layer->preview;
|
|
}
|
|
}
|
|
|
|
|
|
TempBuf *
|
|
layer_mask_preview (layer, w, h)
|
|
Layer *layer;
|
|
int w, h;
|
|
{
|
|
TempBuf *preview_buf;
|
|
Channel *mask;
|
|
PixelRegion srcPR, destPR;
|
|
int subsample;
|
|
|
|
mask = layer->mask;
|
|
if (!mask)
|
|
return NULL;
|
|
|
|
/* The easy way */
|
|
if (mask->preview_valid &&
|
|
mask->preview->width == w &&
|
|
mask->preview->height == h)
|
|
return 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 < layer->width) &&
|
|
(h * (subsample + 1) * 2 < layer->height))
|
|
subsample = subsample + 1;
|
|
|
|
pixel_region_init (&srcPR, mask->tiles, 0, 0, mask->width, 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 (mask->preview)
|
|
temp_buf_free (mask->preview);
|
|
|
|
mask->preview = preview_buf;
|
|
mask->preview_valid = TRUE;
|
|
|
|
return mask->preview;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
layer_invalidate_previews (gimage_id)
|
|
int gimage_id;
|
|
{
|
|
link_ptr tmp = layer_list;
|
|
Layer * layer;
|
|
|
|
while (tmp)
|
|
{
|
|
layer = (Layer *) tmp->data;
|
|
if (gimage_id == -1 || (layer->gimage_ID == gimage_id))
|
|
drawable_invalidate_preview (layer->ID);
|
|
tmp = next_item (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);
|
|
}
|