From 3897b71b72c111b74fb3a23a6eed09123c9752cf Mon Sep 17 00:00:00 2001 From: Lauri Alanko Date: Sun, 28 Jun 1998 10:39:58 +0000 Subject: [PATCH] Started doing a GtkObjectifying framework, and started cleaning images from all sorts of ugly dependencies. --- ChangeLog | 19 + app/Makefile.am | 6 + app/core/gimpimage-guides.c | 2920 ++++++++++++++++++++++++++ app/core/gimpimage-guides.h | 214 ++ app/core/gimpimage-merge.c | 2920 ++++++++++++++++++++++++++ app/core/gimpimage-merge.h | 214 ++ app/core/gimpimage-projection.c | 2920 ++++++++++++++++++++++++++ app/core/gimpimage-projection.h | 214 ++ app/core/gimpimage-resize.c | 2920 ++++++++++++++++++++++++++ app/core/gimpimage-resize.h | 214 ++ app/core/gimpimage-scale.c | 2920 ++++++++++++++++++++++++++ app/core/gimpimage-scale.h | 214 ++ app/core/gimpimage.c | 2920 ++++++++++++++++++++++++++ app/core/gimpimage.h | 214 ++ app/core/gimpobject.c | 31 + app/core/gimpobject.h | 20 + app/core/gimpprojection-construct.c | 2920 ++++++++++++++++++++++++++ app/core/gimpprojection-construct.h | 214 ++ app/gimage.c | 2988 +-------------------------- app/gimage.h | 322 +-- app/gimpimage.c | 2920 ++++++++++++++++++++++++++ app/gimpimage.h | 214 ++ app/gimpimageF.h | 6 + app/gimpimageP.h | 88 + app/gimpobject.c | 31 + app/gimpobject.h | 20 + app/gimpobjectF.h | 6 + app/gimpobjectP.h | 18 + app/gimpsignal.c | 114 + app/gimpsignal.h | 35 + docs/OO.txt | 48 + 31 files changed, 25708 insertions(+), 3116 deletions(-) create mode 100644 app/core/gimpimage-guides.c create mode 100644 app/core/gimpimage-guides.h create mode 100644 app/core/gimpimage-merge.c create mode 100644 app/core/gimpimage-merge.h create mode 100644 app/core/gimpimage-projection.c create mode 100644 app/core/gimpimage-projection.h create mode 100644 app/core/gimpimage-resize.c create mode 100644 app/core/gimpimage-resize.h create mode 100644 app/core/gimpimage-scale.c create mode 100644 app/core/gimpimage-scale.h create mode 100644 app/core/gimpimage.c create mode 100644 app/core/gimpimage.h create mode 100644 app/core/gimpobject.c create mode 100644 app/core/gimpobject.h create mode 100644 app/core/gimpprojection-construct.c create mode 100644 app/core/gimpprojection-construct.h create mode 100644 app/gimpimage.c create mode 100644 app/gimpimage.h create mode 100644 app/gimpimageF.h create mode 100644 app/gimpimageP.h create mode 100644 app/gimpobject.c create mode 100644 app/gimpobject.h create mode 100644 app/gimpobjectF.h create mode 100644 app/gimpobjectP.h create mode 100644 app/gimpsignal.c create mode 100644 app/gimpsignal.h create mode 100644 docs/OO.txt diff --git a/ChangeLog b/ChangeLog index e113b31626..cf09d5539c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +Sun Jun 28 13:30:20 EEST 1998 Lauri Alanko + + * app/gimage.c + * app/gimage.h + * app/gimpimage.c + * app/gimpimage.h + * app/gimpimageF.h + * app/gimpimageP.h + * app/gimpobject.c + * app/gimpobject.h + * app/gimpobjectF.h + * app/gimpobjectP.h + * app/gimpsignal.c + * app/gimpsignal.h + * docs/OO.txt + + Started doing a GtkObjectifying framework, and started cleaning + images from all sorts of ugly dependencies. + Sun Jun 28 08:32:20 1998 Tim Janik * app/layer.c (gimp_layer_mask_get_type): diff --git a/app/Makefile.am b/app/Makefile.am index 18eddc5713..0096664366 100644 --- a/app/Makefile.am +++ b/app/Makefile.am @@ -139,8 +139,14 @@ gimp_SOURCES = \ gimage_mask.h \ gimage_mask_cmds.c \ gimage_mask_cmds.h \ + gimpimage.c \ + gimpimage.h \ + gimpobject.c \ + gimpobject.h \ gimprc.c \ gimprc.h \ + gimpsignal.c \ + gimpsignal.h \ global_edit.c \ global_edit.h \ gradient.c \ diff --git a/app/core/gimpimage-guides.c b/app/core/gimpimage-guides.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpimage-guides.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpimage-guides.h b/app/core/gimpimage-guides.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpimage-guides.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/core/gimpimage-merge.c b/app/core/gimpimage-merge.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpimage-merge.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpimage-merge.h b/app/core/gimpimage-merge.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpimage-merge.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/core/gimpimage-projection.c b/app/core/gimpimage-projection.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpimage-projection.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpimage-projection.h b/app/core/gimpimage-projection.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpimage-projection.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/core/gimpimage-resize.c b/app/core/gimpimage-resize.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpimage-resize.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpimage-resize.h b/app/core/gimpimage-resize.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpimage-resize.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/core/gimpimage-scale.c b/app/core/gimpimage-scale.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpimage-scale.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpimage-scale.h b/app/core/gimpimage-scale.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpimage-scale.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpimage.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpimage.h b/app/core/gimpimage.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpimage.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/core/gimpobject.c b/app/core/gimpobject.c new file mode 100644 index 0000000000..c299a19b8a --- /dev/null +++ b/app/core/gimpobject.c @@ -0,0 +1,31 @@ +#include +#include "gimpobjectP.h" +#include "gimpobject.h" + +static void +gimp_object_init (GimpObject *gobject) +{ +} + +static void +gimp_object_class_init (GimpObjectClass *gobjectclass) +{ +} + +GtkType gimp_object_get_type (void) +{ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpObject", + sizeof(GimpObject), + sizeof(GimpObjectClass), + gimp_object_class_init, + gimp_object_init, + NULL, + NULL}; + type=gtk_type_unique(gtk_object_get_type(), &info); + } + return type; +} + diff --git a/app/core/gimpobject.h b/app/core/gimpobject.h new file mode 100644 index 0000000000..a787501aba --- /dev/null +++ b/app/core/gimpobject.h @@ -0,0 +1,20 @@ +#ifndef __GIMP_OBJECT_H__ +#define __GIMP_OBJECT_H__ + +#include "gimpobjectF.h" + +#define GIMP_OBJECT(obj) \ +GTK_CHECK_CAST (obj, gimp_object_get_type (), GimpObject) +#define GIMP_OBJECT_CLASS(klass) \ +GTK_CHECK_CLASS_CAST (klass, gimp_object_get_type(), GimpObjectClass) +#define GIMP_IS_OBJECT(obj) \ +GTK_CHECK_TYPE (obj, gimp_object_get_type()) + +guint gimp_object_get_type(void); + +#endif + + + + + diff --git a/app/core/gimpprojection-construct.c b/app/core/gimpprojection-construct.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/core/gimpprojection-construct.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/core/gimpprojection-construct.h b/app/core/gimpprojection-construct.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/core/gimpprojection-construct.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/gimage.c b/app/gimage.c index a88a8b114b..6d1d5c1b8b 100644 --- a/app/gimage.c +++ b/app/gimage.c @@ -1,436 +1,82 @@ -/* 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 -#include -#include -#include -#include -#include "appenv.h" -#include "channels_dialog.h" -#include "drawable.h" -#include "errors.h" -#include "floating_sel.h" -#include "gdisplay.h" -#include "general.h" +#include "gimpimageP.h" #include "gimage.h" -#include "gimage_mask.h" -#include "indexed_palette.h" -#include "interface.h" +#include "gimpimage.h" +#include "channels_dialog.h" #include "layers_dialog.h" -#include "paint_funcs.h" + +#include "indexed_palette.h" + +#include "drawable.h" +#include "gdisplay.h" + + #include "palette.h" -#include "plug_in.h" -#include "tools.h" #include "undo.h" -#include "tile_manager_pvt.h" /* ick. */ +#include "layer.h" #include "layer_pvt.h" -#include "drawable_pvt.h" /* ick ick. */ +#include "channel.h" +#include "tools.h" -/* Local function declarations */ -static void gimage_free_projection (GImage *); -static void gimage_allocate_shadow (GImage *, int, int, int); -static GImage * gimage_create (void); -static void gimage_allocate_projection (GImage *); -static void gimage_free_layers (GImage *); -static void gimage_free_channels (GImage *); -static void gimage_construct_layers (GImage *, int, int, int, int); -static void gimage_construct_channels (GImage *, int, int, int, int); -static void gimage_initialize_projection (GImage *, int, int, int, int); -static void gimage_get_active_channels (GImage *, GimpDrawable *, int *); +/* gimage.c: Junk (ugly dependencies) from gimpimage.c on its way + to proper places. That is, the handlers should be moved to + layers_dialog, gdisplay, tools, etc.. */ -/* projection functions */ -static void project_intensity (GImage *, Layer *, PixelRegion *, - PixelRegion *, PixelRegion *); -static void project_intensity_alpha (GImage *, Layer *, PixelRegion *, - PixelRegion *, PixelRegion *); -static void project_indexed (GImage *, Layer *, PixelRegion *, - PixelRegion *); -static void project_channel (GImage *, Channel *, PixelRegion *, - PixelRegion *); +/* Todo: obliterate the idiotic id system. We get rid of lotsa list + lookups, and it clears the code.. */ -/* - * Global variables - */ -int valid_combinations[][MAX_CHANNELS + 1] = -{ - /* RGB GIMAGE */ - { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, - /* RGBA GIMAGE */ - { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, - /* GRAY GIMAGE */ - { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, - /* GRAYA GIMAGE */ - { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, - /* INDEXED GIMAGE */ - { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, - /* INDEXEDA GIMAGE */ - { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, -}; - - -/* - * Static variables - */ static int global_gimage_ID = 1; -GSList *image_list = NULL; + +/* Any global vars like this are damn well app-specific, and don't + belong in class implementations */ + +GSList* image_list; + +static void gimage_dirty_handler (GimpImage* gimage); +static void gimage_destroy_handler (GimpImage* gimage); +static void gimage_rename_handler (GimpImage* gimage); +static void gimage_resize_handler (GimpImage* gimage); +static void gimage_restructure_handler (GimpImage* gimage); +static void gimage_repaint_handler (GimpImage* gimage, gint, gint, gint, gint); -/* static functions */ -static GImage * -gimage_create (void) +GImage* +gimage_new(int width, int height, GimpImageBaseType base_type) { - GImage *gimage; + GimpImage* gimage = gimp_image_new (width, height, base_type); - gimage = (GImage *) g_malloc (sizeof (GImage)); - - gimage->has_filename = 0; - gimage->num_cols = 0; - gimage->cmap = NULL; - gimage->ID = global_gimage_ID++; + gtk_signal_connect (GTK_OBJECT (gimage), "dirty", + GTK_SIGNAL_FUNC(gimage_dirty_handler), NULL); + gtk_signal_connect (GTK_OBJECT (gimage), "destroy", + GTK_SIGNAL_FUNC(gimage_destroy_handler), NULL); + gtk_signal_connect (GTK_OBJECT (gimage), "rename", + GTK_SIGNAL_FUNC(gimage_rename_handler), NULL); + gtk_signal_connect (GTK_OBJECT (gimage), "resize", + GTK_SIGNAL_FUNC(gimage_resize_handler), NULL); + gtk_signal_connect (GTK_OBJECT (gimage), "restructure", + GTK_SIGNAL_FUNC(gimage_restructure_handler), NULL); + gtk_signal_connect (GTK_OBJECT (gimage), "repaint", + GTK_SIGNAL_FUNC(gimage_repaint_handler), NULL); + gimage->ref_count = 0; - gimage->instance_count = 0; - gimage->shadow = NULL; - gimage->dirty = 1; - gimage->undo_on = TRUE; - gimage->flat = TRUE; - gimage->construct_flag = -1; - gimage->projection = NULL; - gimage->guides = NULL; - gimage->layers = NULL; - gimage->channels = NULL; - gimage->layer_stack = NULL; - gimage->undo_stack = NULL; - gimage->redo_stack = NULL; - gimage->undo_bytes = 0; - gimage->undo_levels = 0; - gimage->pushing_undo_group = 0; - gimage->comp_preview_valid[0] = FALSE; - gimage->comp_preview_valid[1] = FALSE; - gimage->comp_preview_valid[2] = FALSE; - gimage->comp_preview = NULL; - - image_list = g_slist_append (image_list, (void *) gimage); - + gimage->ID = global_gimage_ID ++; + image_list = g_slist_append (image_list, gimage); + + lc_dialog_update_image_list (); + indexed_palette_update_image_list (); return gimage; } -static void -gimage_allocate_projection (GImage *gimage) -{ - if (gimage->projection) - gimage_free_projection (gimage); - - /* Find the number of bytes required for the projection. - * This includes the intensity channels and an alpha channel - * if one doesn't exist. - */ - switch (gimage_base_type (gimage)) - { - case RGB: - case INDEXED: - gimage->proj_bytes = 4; - gimage->proj_type = RGBA_GIMAGE; - break; - case GRAY: - gimage->proj_bytes = 2; - gimage->proj_type = GRAYA_GIMAGE; - break; - default: - g_message ("gimage type unsupported.\n"); - break; - } - - /* allocate the new projection */ - gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); - gimage->projection->user_data = (void *) gimage; - tile_manager_set_validate_proc (gimage->projection, gimage_validate); -} - -static void -gimage_free_projection (GImage *gimage) -{ - if (gimage->projection) - tile_manager_destroy (gimage->projection); - - gimage->projection = NULL; -} - -static void -gimage_allocate_shadow (GImage *gimage, int width, int height, int bpp) -{ - /* allocate the new projection */ - gimage->shadow = tile_manager_new (width, height, bpp); -} - - -/* function definitions */ - -GImage * -gimage_new (int width, int height, int base_type) -{ - GImage *gimage; - int i; - - gimage = gimage_create (); - - gimage->filename = NULL; - gimage->width = width; - gimage->height = height; - - gimage->base_type = base_type; - - switch (base_type) - { - case RGB: - case GRAY: - break; - case INDEXED: - /* always allocate 256 colors for the colormap */ - gimage->num_cols = 0; - gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); - memset (gimage->cmap, 0, COLORMAP_SIZE); - break; - default: - break; - } - - /* configure the active pointers */ - gimage->active_layer = NULL; - gimage->active_channel = NULL; /* no default active channel */ - gimage->floating_sel = NULL; - - /* set all color channels visible and active */ - for (i = 0; i < MAX_CHANNELS; i++) - { - gimage->visible[i] = 1; - gimage->active[i] = 1; - } - - /* create the selection mask */ - gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); - - lc_dialog_update_image_list (); - indexed_palette_update_image_list (); - - return gimage; -} - - -void -gimage_set_filename (GImage *gimage, char *filename) -{ - char *new_filename; - - new_filename = g_strdup (filename); - if (gimage->has_filename) - g_free (gimage->filename); - - if (filename && filename[0]) - { - gimage->filename = new_filename; - gimage->has_filename = TRUE; - } - else - { - gimage->filename = NULL; - gimage->has_filename = FALSE; - } - - gdisplays_update_title (gimage->ID); - lc_dialog_update_image_list (); - indexed_palette_update_image_list (); -} - - -void -gimage_resize (GImage *gimage, int new_width, int new_height, - int offset_x, int offset_y) -{ - Channel *channel; - Layer *layer; - Layer *floating_layer; - GSList *list; - - if (new_width <= 0 || new_height <= 0) - { - g_message ("gimage_resize: width and height must be positive"); - return; - } - - /* Get the floating layer if one exists */ - floating_layer = gimage_floating_sel (gimage); - - undo_push_group_start (gimage, GIMAGE_MOD_UNDO); - - /* Relax the floating selection */ - if (floating_layer) - floating_sel_relax (floating_layer, TRUE); - - /* Push the image size to the stack */ - undo_push_gimage_mod (gimage); - - /* Set the new width and height */ - gimage->width = new_width; - gimage->height = new_height; - - /* Resize all channels */ - list = gimage->channels; - while (list) - { - channel = (Channel *) list->data; - channel_resize (channel, new_width, new_height, offset_x, offset_y); - list = g_slist_next (list); - } - - /* Don't forget the selection mask! */ - channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); - gimage_mask_invalidate (gimage); - - /* Reposition all layers */ - list = gimage->layers; - while (list) - { - layer = (Layer *) list->data; - layer_translate (layer, offset_x, offset_y); - list = g_slist_next (list); - } - - /* Make sure the projection matches the gimage size */ - gimage_projection_realloc (gimage); - - /* Rigor the floating selection */ - if (floating_layer) - floating_sel_rigor (floating_layer, TRUE); - - undo_push_group_end (gimage); - - /* shrink wrap and update all views */ - channel_invalidate_previews (gimage->ID); - layer_invalidate_previews (gimage->ID); - gimage_invalidate_preview (gimage); - gdisplays_update_full (gimage->ID); - gdisplays_shrink_wrap (gimage->ID); -} - - -void -gimage_scale (GImage *gimage, int new_width, int new_height) -{ - Channel *channel; - Layer *layer; - Layer *floating_layer; - GSList *list; - int old_width, old_height; - int layer_width, layer_height; - - /* Get the floating layer if one exists */ - floating_layer = gimage_floating_sel (gimage); - - undo_push_group_start (gimage, GIMAGE_MOD_UNDO); - - /* Relax the floating selection */ - if (floating_layer) - floating_sel_relax (floating_layer, TRUE); - - /* Push the image size to the stack */ - undo_push_gimage_mod (gimage); - - /* Set the new width and height */ - old_width = gimage->width; - old_height = gimage->height; - gimage->width = new_width; - gimage->height = new_height; - - /* Scale all channels */ - list = gimage->channels; - while (list) - { - channel = (Channel *) list->data; - channel_scale (channel, new_width, new_height); - list = g_slist_next (list); - } - - /* Don't forget the selection mask! */ - channel_scale (gimage->selection_mask, new_width, new_height); - gimage_mask_invalidate (gimage); - - /* Scale all layers */ - list = gimage->layers; - while (list) - { - layer = (Layer *) list->data; - - layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; - layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; - layer_scale (layer, layer_width, layer_height, FALSE); - list = g_slist_next (list); - } - - /* Make sure the projection matches the gimage size */ - gimage_projection_realloc (gimage); - - /* Rigor the floating selection */ - if (floating_layer) - floating_sel_rigor (floating_layer, TRUE); - - undo_push_group_end (gimage); - - /* shrink wrap and update all views */ - channel_invalidate_previews (gimage->ID); - layer_invalidate_previews (gimage->ID); - gimage_invalidate_preview (gimage); - gdisplays_update_full (gimage->ID); - gdisplays_shrink_wrap (gimage->ID); -} - - -GImage * -gimage_get_named (char *name) +GImage* +gimage_get_ID (gint ID) { GSList *tmp = image_list; - GImage *gimage; - char *str; + GimpImage *gimage; while (tmp) { - gimage = tmp->data; - str = prune_filename (gimage_filename (gimage)); - if (strcmp (str, name) == 0) - return gimage; - - tmp = g_slist_next (tmp); - } - - return NULL; -} - - -GImage * -gimage_get_ID (int ID) -{ - GSList *tmp = image_list; - GImage *gimage; - - while (tmp) - { - gimage = (GImage *) tmp->data; + gimage = (GimpImage *) tmp->data; if (gimage->ID == ID) return gimage; @@ -438,1186 +84,96 @@ gimage_get_ID (int ID) } return NULL; + } -TileManager * -gimage_shadow (GImage *gimage, int width, int height, int bpp) -{ - if (gimage->shadow && - ((width != gimage->shadow->levels[0].width) || - (height != gimage->shadow->levels[0].height) || - (bpp != gimage->shadow->levels[0].bpp))) - gimage_free_shadow (gimage); - else if (gimage->shadow) - return gimage->shadow; - - gimage_allocate_shadow (gimage, width, height, bpp); - - return gimage->shadow; -} - - -void -gimage_free_shadow (GImage *gimage) -{ - /* Free the shadow buffer from the specified gimage if it exists */ - if (gimage->shadow) - tile_manager_destroy (gimage->shadow); - - gimage->shadow = NULL; -} - +/* Ack! GImages have their own ref counts! This is going to cause + trouble.. It should be pretty easy to convert to proper GtkObject + ref counting, though. */ void gimage_delete (GImage *gimage) { gimage->ref_count--; - - if (gimage->ref_count <= 0) - { - /* free the undo list */ - undo_free (gimage); - - /* remove this image from the global list */ - image_list = g_slist_remove (image_list, (void *) gimage); - - gimage_free_projection (gimage); - gimage_free_shadow (gimage); - - if (gimage->cmap) - g_free (gimage->cmap); - - if (gimage->has_filename) - g_free (gimage->filename); - - gimage_free_layers (gimage); - gimage_free_channels (gimage); - channel_delete (gimage->selection_mask); - - g_free (gimage); - - lc_dialog_update_image_list (); - - indexed_palette_update_image_list (); - } -} + if (gimage->ref_count <= 0) + gtk_object_unref (GTK_OBJECT(gimage)); +}; void -gimage_apply_image (GImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, - int undo, int opacity, int mode, - /* alternative to using drawable tiles as src1: */ - TileManager *src1_tiles, - int x, int y) +gimage_invalidate_previews (void) { - Channel * mask; - int x1, y1, x2, y2; - int offset_x, offset_y; - PixelRegion src1PR, destPR, maskPR; - int operation; - int active [MAX_CHANNELS]; + GSList *tmp = image_list; + GimpImage *gimage; - /* get the selection mask if one exists */ - mask = (gimage_mask_is_empty (gimage)) ? - NULL : gimage_get_mask (gimage); - - /* configure the active channel array */ - gimage_get_active_channels (gimage, drawable, active); - - /* determine what sort of operation is being attempted and - * if it's actually legal... - */ - operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; - if (operation == -1) + while (tmp) { - g_message ("gimage_apply_image sent illegal parameters"); - return; - } - - /* get the layer offsets */ - drawable_offsets (drawable, &offset_x, &offset_y); - - /* make sure the image application coordinates are within gimage bounds */ - x1 = BOUNDS (x, 0, drawable_width (drawable)); - y1 = BOUNDS (y, 0, drawable_height (drawable)); - x2 = BOUNDS (x + src2PR->w, 0, drawable_width (drawable)); - y2 = BOUNDS (y + src2PR->h, 0, drawable_height (drawable)); - - if (mask) - { - /* make sure coordinates are in mask bounds ... - * we need to add the layer offset to transform coords - * into the mask coordinate system - */ - x1 = BOUNDS (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); - y1 = BOUNDS (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); - x2 = BOUNDS (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); - y2 = BOUNDS (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); - } - - /* If the calling procedure specified an undo step... */ - if (undo) - drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); - - /* configure the pixel regions - * If an alternative to using the drawable's data as src1 was provided... - */ - if (src1_tiles) - pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); - else - pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); - pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); - pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); - - if (mask) - { - int mx, my; - - /* configure the mask pixel region - * don't use x1 and y1 because they are in layer - * coordinate system. Need mask coordinate system - */ - mx = x1 + offset_x; - my = y1 + offset_y; - - pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); - combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, - opacity, mode, active, operation); - } - else - combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, - opacity, mode, active, operation); -} - -/* Similar to gimage_apply_image but works in "replace" mode (i.e. - transparent pixels in src2 make the result transparent rather - than opaque. - - Takes an additional mask pixel region as well. - -*/ -void -gimage_replace_image (GImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, - int undo, int opacity, - PixelRegion *maskPR, - int x, int y) -{ - Channel * mask; - int x1, y1, x2, y2; - int offset_x, offset_y; - PixelRegion src1PR, destPR; - PixelRegion mask2PR, tempPR; - unsigned char *temp_data; - int operation; - int active [MAX_CHANNELS]; - - /* get the selection mask if one exists */ - mask = (gimage_mask_is_empty (gimage)) ? - NULL : gimage_get_mask (gimage); - - /* configure the active channel array */ - gimage_get_active_channels (gimage, drawable, active); - - /* determine what sort of operation is being attempted and - * if it's actually legal... - */ - operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; - if (operation == -1) - { - g_message ("gimage_apply_image sent illegal parameters"); - return; - } - - /* get the layer offsets */ - drawable_offsets (drawable, &offset_x, &offset_y); - - /* make sure the image application coordinates are within gimage bounds */ - x1 = BOUNDS (x, 0, drawable_width (drawable)); - y1 = BOUNDS (y, 0, drawable_height (drawable)); - x2 = BOUNDS (x + src2PR->w, 0, drawable_width (drawable)); - y2 = BOUNDS (y + src2PR->h, 0, drawable_height (drawable)); - - if (mask) - { - /* make sure coordinates are in mask bounds ... - * we need to add the layer offset to transform coords - * into the mask coordinate system - */ - x1 = BOUNDS (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); - y1 = BOUNDS (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); - x2 = BOUNDS (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); - y2 = BOUNDS (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); - } - - /* If the calling procedure specified an undo step... */ - if (undo) - drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); - - /* configure the pixel regions - * If an alternative to using the drawable's data as src1 was provided... - */ - pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); - pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); - pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); - - if (mask) - { - int mx, my; - - /* configure the mask pixel region - * don't use x1 and y1 because they are in layer - * coordinate system. Need mask coordinate system - */ - mx = x1 + offset_x; - my = y1 + offset_y; - - pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); - - tempPR.bytes = 1; - tempPR.x = 0; - tempPR.y = 0; - tempPR.w = x2 - x1; - tempPR.h = y2 - y1; - tempPR.rowstride = mask2PR.rowstride; - temp_data = g_malloc (tempPR.h * tempPR.rowstride); - tempPR.data = temp_data; - - copy_region (&mask2PR, &tempPR); - - /* apparently, region operations can mutate some PR data. */ - tempPR.x = 0; - tempPR.y = 0; - tempPR.w = x2 - x1; - tempPR.h = y2 - y1; - tempPR.data = temp_data; - - apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); - - tempPR.x = 0; - tempPR.y = 0; - tempPR.w = x2 - x1; - tempPR.h = y2 - y1; - tempPR.data = temp_data; - - combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, - opacity, active, operation); - - g_free (temp_data); - } - else - combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, - opacity, active, operation); -} - - -void -gimage_get_foreground (GImage *gimage, GimpDrawable *drawable, unsigned char *fg) -{ - unsigned char pfg[3]; - - /* Get the palette color */ - palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); - - gimage_transform_color (gimage, drawable, pfg, fg, RGB); -} - - -void -gimage_get_background (GImage *gimage, GimpDrawable *drawable, unsigned char *bg) -{ - unsigned char pbg[3]; - - /* Get the palette color */ - palette_get_background (&pbg[0], &pbg[1], &pbg[2]); - - gimage_transform_color (gimage, drawable, pbg, bg, RGB); -} - - -void -gimage_get_color (GImage *gimage, int d_type, - unsigned char *rgb, unsigned char *src) -{ - switch (d_type) - { - case RGB_GIMAGE: case RGBA_GIMAGE: - map_to_color (0, NULL, src, rgb); - break; - case GRAY_GIMAGE: case GRAYA_GIMAGE: - map_to_color (1, NULL, src, rgb); - break; - case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: - map_to_color (2, gimage->cmap, src, rgb); - break; + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); } } - - -void -gimage_transform_color (GImage *gimage, GimpDrawable *drawable, - unsigned char *src, unsigned char *dest, int type) -{ -#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) - int d_type; - - d_type = (drawable != NULL) ? drawable_type (drawable) : - gimage_base_type_with_alpha (gimage); - - switch (type) - { - case RGB: - switch (d_type) - { - case RGB_GIMAGE: case RGBA_GIMAGE: - /* Straight copy */ - *dest++ = *src++; - *dest++ = *src++; - *dest++ = *src++; - break; - case GRAY_GIMAGE: case GRAYA_GIMAGE: - /* NTSC conversion */ - *dest = INTENSITY (src[RED_PIX], - src[GREEN_PIX], - src[BLUE_PIX]); - break; - case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: - /* Least squares method */ - *dest = map_rgb_to_indexed (gimage->cmap, - gimage->num_cols, - gimage->ID, - src[RED_PIX], - src[GREEN_PIX], - src[BLUE_PIX]); - break; - } - break; - case GRAY: - switch (d_type) - { - case RGB_GIMAGE: case RGBA_GIMAGE: - /* Gray to RG&B */ - *dest++ = *src; - *dest++ = *src; - *dest++ = *src; - break; - case GRAY_GIMAGE: case GRAYA_GIMAGE: - /* Straight copy */ - *dest = *src; - break; - case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: - /* Least squares method */ - *dest = map_rgb_to_indexed (gimage->cmap, - gimage->num_cols, - gimage->ID, - src[GRAY_PIX], - src[GRAY_PIX], - src[GRAY_PIX]); - break; - } - break; - default: - break; - } -} - -Guide* -gimage_add_hguide (GImage *gimage) -{ - Guide *guide; - - guide = g_new (Guide, 1); - guide->ref_count = 0; - guide->position = -1; - guide->orientation = HORIZONTAL_GUIDE; - - gimage->guides = g_list_prepend (gimage->guides, guide); - - return guide; -} - -Guide* -gimage_add_vguide (GImage *gimage) -{ - Guide *guide; - - guide = g_new (Guide, 1); - guide->ref_count = 0; - guide->position = -1; - guide->orientation = VERTICAL_GUIDE; - - gimage->guides = g_list_prepend (gimage->guides, guide); - - return guide; -} - -void -gimage_add_guide (GImage *gimage, - Guide *guide) -{ - gimage->guides = g_list_prepend (gimage->guides, guide); -} - -void -gimage_remove_guide (GImage *gimage, - Guide *guide) -{ - gimage->guides = g_list_remove (gimage->guides, guide); -} - -void -gimage_delete_guide (GImage *gimage, - Guide *guide) -{ - gimage->guides = g_list_remove (gimage->guides, guide); - g_free (guide); -} - - -/************************************************************/ -/* Projection functions */ -/************************************************************/ - static void -project_intensity (GImage *gimage, Layer *layer, - PixelRegion *src, PixelRegion *dest, PixelRegion *mask) -{ - if (! gimage->construct_flag) - initial_region (src, dest, mask, NULL, layer->opacity, - layer->mode, gimage->visible, INITIAL_INTENSITY); - else - combine_regions (dest, src, dest, mask, NULL, layer->opacity, - layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); -} - - -static void -project_intensity_alpha (GImage *gimage, Layer *layer, - PixelRegion *src, PixelRegion *dest, - PixelRegion *mask) -{ - if (! gimage->construct_flag) - initial_region (src, dest, mask, NULL, layer->opacity, - layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); - else - combine_regions (dest, src, dest, mask, NULL, layer->opacity, - layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); -} - - -static void -project_indexed (GImage *gimage, Layer *layer, - PixelRegion *src, PixelRegion *dest) -{ - if (! gimage->construct_flag) - initial_region (src, dest, NULL, gimage->cmap, layer->opacity, - layer->mode, gimage->visible, INITIAL_INDEXED); - else - g_message ("Unable to project indexed image."); -} - - -static void -project_indexed_alpha (GImage *gimage, Layer *layer, - PixelRegion *src, PixelRegion *dest, - PixelRegion *mask) -{ - if (! gimage->construct_flag) - initial_region (src, dest, mask, gimage->cmap, layer->opacity, - layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); - else - combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, - layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); -} - - -static void -project_channel (GImage *gimage, Channel *channel, - PixelRegion *src, PixelRegion *src2) -{ - int type; - - if (! gimage->construct_flag) - { - type = (channel->show_masked) ? - INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; - initial_region (src2, src, NULL, channel->col, channel->opacity, - NORMAL, NULL, type); - } - else - { - type = (channel->show_masked) ? - COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; - combine_regions (src, src2, src, NULL, channel->col, channel->opacity, - NORMAL, NULL, type); - } -} - - -/************************************************************/ -/* Layer/Channel functions */ -/************************************************************/ - - -static void -gimage_free_layers (GImage *gimage) -{ - GSList *list = gimage->layers; - Layer * layer; - - while (list) - { - layer = (Layer *) list->data; - layer_delete (layer); - list = g_slist_next (list); - } - g_slist_free (gimage->layers); - g_slist_free (gimage->layer_stack); -} - - -static void -gimage_free_channels (GImage *gimage) -{ - GSList *list = gimage->channels; - Channel * channel; - - while (list) - { - channel = (Channel *) list->data; - channel_delete (channel); - list = g_slist_next (list); - } - g_slist_free (gimage->channels); -} - - -static void -gimage_construct_layers (GImage *gimage, int x, int y, int w, int h) -{ - Layer * layer; - int x1, y1, x2, y2; - PixelRegion src1PR, src2PR, maskPR; - PixelRegion * mask; - GSList *list = gimage->layers; - GSList *reverse_list = NULL; - int off_x, off_y; - - /* composite the floating selection if it exists */ - if ((layer = gimage_floating_sel (gimage))) - floating_sel_composite (layer, x, y, w, h, FALSE); - - /* Note added by Raph Levien, 27 Jan 1998 - - This looks it was intended as an optimization, but it seems to - have correctness problems. In particular, if all channels are - turned off, the screen simply does not update the projected - image. It should be black. Turning off this optimization seems to - restore correct behavior. At some future point, it may be - desirable to turn the optimization back on. - - */ -#if 0 - /* If all channels are not visible, simply return */ - switch (gimage_base_type (gimage)) - { - case RGB: - if (! gimage_get_component_visible (gimage, Red) && - ! gimage_get_component_visible (gimage, Green) && - ! gimage_get_component_visible (gimage, Blue)) - return; - break; - case GRAY: - if (! gimage_get_component_visible (gimage, Gray)) - return; - break; - case INDEXED: - if (! gimage_get_component_visible (gimage, Indexed)) - return; - break; - } -#endif - - while (list) - { - layer = (Layer *) list->data; - - /* only add layers that are visible and not floating selections to the list */ - if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) - reverse_list = g_slist_prepend (reverse_list, layer); - - list = g_slist_next (list); - } - - while (reverse_list) - { - layer = (Layer *) reverse_list->data; - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - - x1 = BOUNDS (off_x, x, x + w); - y1 = BOUNDS (off_y, y, y + h); - x2 = BOUNDS (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); - y2 = BOUNDS (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); - - /* configure the pixel regions */ - pixel_region_init (&src1PR, gimage_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); - - /* If we're showing the layer mask instead of the layer... */ - if (layer->mask && layer->show_mask) - { - pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), - (x1 - off_x), (y1 - off_y), - (x2 - x1), (y2 - y1), FALSE); - - copy_gray_to_region (&src2PR, &src1PR); - } - /* Otherwise, normal */ +gimage_dirty_handler (GimpImage* gimage){ + if (active_tool && !active_tool->preserve) { + GDisplay* gdisp = active_tool->gdisp_ptr; + if (gdisp) { + if (gdisp->gimage->ID == gimage->ID) + tools_initialize (active_tool->type, gdisp); else - { - pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), - (x1 - off_x), (y1 - off_y), - (x2 - x1), (y2 - y1), FALSE); - - if (layer->mask && layer->apply_mask) - { - pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), - (x1 - off_x), (y1 - off_y), - (x2 - x1), (y2 - y1), FALSE); - mask = &maskPR; - } - else - mask = NULL; - - /* Based on the type of the layer, project the layer onto the - * projection image... - */ - switch (drawable_type (GIMP_DRAWABLE(layer))) - { - case RGB_GIMAGE: case GRAY_GIMAGE: - /* no mask possible */ - project_intensity (gimage, layer, &src2PR, &src1PR, mask); - break; - case RGBA_GIMAGE: case GRAYA_GIMAGE: - project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); - break; - case INDEXED_GIMAGE: - /* no mask possible */ - project_indexed (gimage, layer, &src2PR, &src1PR); - break; - case INDEXEDA_GIMAGE: - project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); - break; - default: - break; - } - } - gimage->construct_flag = 1; /* something was projected */ - - reverse_list = g_slist_next (reverse_list); + tools_initialize (active_tool->type, NULL); } - - g_slist_free (reverse_list); + } } - static void -gimage_construct_channels (GImage *gimage, int x, int y, int w, int h) +gimage_destroy_handler (GimpImage* gimage) { - Channel * channel; - PixelRegion src1PR, src2PR; - GSList *list = gimage->channels; - GSList *reverse_list = NULL; + /* free the undo list */ + undo_free (gimage); - /* reverse the channel list */ - while (list) - { - reverse_list = g_slist_prepend (reverse_list, list->data); - list = g_slist_next (list); - } + image_list = g_slist_remove (image_list, (void *) gimage); + lc_dialog_update_image_list (); - while (reverse_list) - { - channel = (Channel *) reverse_list->data; - - if (drawable_visible (GIMP_DRAWABLE(channel))) - { - /* configure the pixel regions */ - pixel_region_init (&src1PR, gimage_projection (gimage), x, y, w, h, TRUE); - pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); - - project_channel (gimage, channel, &src1PR, &src2PR); - - gimage->construct_flag = 1; - } - - reverse_list = g_slist_next (reverse_list); - } - - g_slist_free (reverse_list); + indexed_palette_update_image_list (); } - static void -gimage_initialize_projection (GImage *gimage, int x, int y, int w, int h) +gimage_rename_handler (GimpImage* gimage) { - GSList *list; - Layer *layer; - int coverage = 0; - PixelRegion PR; - unsigned char clear[4] = { 0, 0, 0, 0 }; - - /* this function determines whether a visible layer - * provides complete coverage over the image. If not, - * the projection is initialized to transparent - */ - list = gimage->layers; - while (list) - { - int off_x, off_y; - layer = (Layer *) list->data; - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - if (drawable_visible (GIMP_DRAWABLE(layer)) && - ! layer_has_alpha (layer) && - (off_x <= x) && - (off_y <= y) && - (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && - (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) - coverage = 1; - - list = g_slist_next (list); - } - - if (!coverage) - { - pixel_region_init (&PR, gimage_projection (gimage), x, y, w, h, TRUE); - color_region (&PR, clear); - } + gdisplays_update_title (gimage->ID); + lc_dialog_update_image_list (); + indexed_palette_update_image_list (); } - static void -gimage_get_active_channels (GImage *gimage, GimpDrawable *drawable, int *active) +gimage_resize_handler (GimpImage* gimage) { - Layer * layer; - int i; + undo_push_group_end (gimage); - /* first, blindly copy the gimage active channels */ - for (i = 0; i < MAX_CHANNELS; i++) - active[i] = gimage->active[i]; - - /* If the drawable is a channel (a saved selection, etc.) - * make sure that the alpha channel is not valid - */ - if (drawable_channel (drawable) != NULL) - active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ - else - { - /* otherwise, check whether preserve transparency is - * enabled in the layer and if the layer has alpha - */ - if ((layer = drawable_layer (drawable))) - if (layer_has_alpha (layer) && layer->preserve_trans) - active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; - } + /* shrink wrap and update all views */ + channel_invalidate_previews (gimage->ID); + layer_invalidate_previews (gimage->ID); + gimp_image_invalidate_preview (gimage); + gdisplays_update_full (gimage->ID); + gdisplays_shrink_wrap (gimage->ID); } - -void -gimage_inflate (GImage *gimage) +static void +gimage_restructure_handler (GimpImage* gimage) { - /* Make sure the projection image is allocated */ - gimage_allocate_projection (gimage); - - gimage->flat = FALSE; - - /* update the gdisplay titles */ gdisplays_update_title (gimage->ID); } - -void -gimage_deflate (GImage *gimage) +static void +gimage_repaint_handler (GimpImage* gimage, gint x, gint y, gint w, gint h) { - /* Make sure the projection image is deallocated */ - gimage_free_projection (gimage); - - gimage->flat = TRUE; - - /* update the gdisplay titles */ - gdisplays_update_title (gimage->ID); + gdisplays_update_area (gimage->ID, x, y, w, h); } + -void -gimage_construct (GImage *gimage, int x, int y, int w, int h) -{ - /* if the gimage is not flat, construction is necessary. */ - if (! gimage_is_flat (gimage)) - { - /* set the construct flag, used to determine if anything - * has been written to the gimage raw image yet. - */ - gimage->construct_flag = 0; - - /* First, determine if the projection image needs to be - * initialized--this is the case when there are no visible - * layers that cover the entire canvas--either because layers - * are offset or only a floating selection is visible - */ - gimage_initialize_projection (gimage, x, y, w, h); - - /* call functions which process the list of layers and - * the list of channels - */ - gimage_construct_layers (gimage, x, y, w, h); - gimage_construct_channels (gimage, x, y, w, h); - } -} - -void -gimage_invalidate (GImage *gimage, int x, int y, int w, int h, int x1, int y1, - int x2, int y2) -{ - Tile *tile; - TileManager *tm; - int i, j; - int startx, starty; - int endx, endy; - int tilex, tiley; - int flat; - - flat = gimage_is_flat (gimage); - tm = gimage_projection (gimage); - - startx = x; - starty = y; - endx = x + w; - endy = y + h; - - /* invalidate all tiles which are located outside of the displayed area - * all tiles inside the displayed area are constructed. - */ - for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) - for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) - { - tile = tile_manager_get_tile (tm, j, i, 0); - - /* invalidate all lower level tiles */ - /*tile_manager_invalidate_tiles (gimage_projection (gimage), tile);*/ - - if (! flat) - { - /* check if the tile is outside the bounds */ - if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) - { - tile->valid = FALSE; - if (j < x1) - startx = MAX (startx, (j + tile->ewidth)); - else - endx = MIN (endx, j); - } - else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) - { - tile->valid = FALSE; - if (i < y1) - starty = MAX (starty, (i + tile->eheight)); - else - endy = MIN (endy, i); - } - else - { - /* If the tile is not valid, make sure we get the entire tile - * in the construction extents - */ - if (tile->valid == FALSE) - { - tilex = j - (j % TILE_WIDTH); - tiley = i - (i % TILE_HEIGHT); - - startx = MIN (startx, tilex); - endx = MAX (endx, tilex + tile->ewidth); - starty = MIN (starty, tiley); - endy = MAX (endy, tiley + tile->eheight); - - tile->valid = TRUE; - } - } - } - } - - if (! flat && (endx - startx) > 0 && (endy - starty) > 0) - gimage_construct (gimage, startx, starty, (endx - startx), (endy - starty)); -} - -void -gimage_validate (TileManager *tm, Tile *tile, int level) -{ - GImage *gimage; - int x, y; - int w, h; - - /* Get the gimage from the tilemanager */ - gimage = (GImage *) tm->user_data; - - /* Find the coordinates of this tile */ - x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); - y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); - w = tile->ewidth; - h = tile->eheight; - - gimage_construct (gimage, x, y, w, h); -} - -int -gimage_get_layer_index (GImage *gimage, Layer *layer_arg) -{ - Layer *layer; - GSList *layers = gimage->layers; - int index = 0; - - while (layers) - { - layer = (Layer *) layers->data; - if (layer == layer_arg) - return index; - - index++; - layers = g_slist_next (layers); - } - - return -1; -} - -int -gimage_get_channel_index (GImage *gimage, Channel *channel_ID) -{ - Channel *channel; - GSList *channels = gimage->channels; - int index = 0; - - while (channels) - { - channel = (Channel *) channels->data; - if (channel == channel_ID) - return index; - - index++; - channels = g_slist_next (channels); - } - - return -1; -} - - -Layer * -gimage_get_active_layer (GImage *gimage) -{ - return gimage->active_layer; -} - - -Channel * -gimage_get_active_channel (GImage *gimage) -{ - return gimage->active_channel; -} - - -int -gimage_get_component_active (GImage *gimage, ChannelType type) -{ - /* No sanity checking here... */ - switch (type) - { - case Red: return gimage->active[RED_PIX]; break; - case Green: return gimage->active[GREEN_PIX]; break; - case Blue: return gimage->active[BLUE_PIX]; break; - case Gray: return gimage->active[GRAY_PIX]; break; - case Indexed: return gimage->active[INDEXED_PIX]; break; - default: return 0; break; - } -} - - -int -gimage_get_component_visible (GImage *gimage, ChannelType type) -{ - /* No sanity checking here... */ - switch (type) - { - case Red: return gimage->visible[RED_PIX]; break; - case Green: return gimage->visible[GREEN_PIX]; break; - case Blue: return gimage->visible[BLUE_PIX]; break; - case Gray: return gimage->visible[GRAY_PIX]; break; - case Indexed: return gimage->visible[INDEXED_PIX]; break; - default: return 0; break; - } -} - - -Channel * -gimage_get_mask (GImage *gimage) -{ - return gimage->selection_mask; -} - - -int -gimage_layer_boundary (GImage *gimage, BoundSeg **segs, int *num_segs) -{ - Layer *layer; - - /* The second boundary corresponds to the active layer's - * perimeter... - */ - if ((layer = gimage->active_layer)) - { - *segs = layer_boundary (layer, num_segs); - return 1; - } - else - { - *segs = NULL; - *num_segs = 0; - return 0; - } -} - - -Layer * -gimage_set_active_layer (GImage *gimage, Layer * layer) -{ - - /* First, find the layer in the gimage - * If it isn't valid, find the first layer that is - */ - if (gimage_get_layer_index (gimage, layer) == -1) - { - if (! gimage->layers) - return NULL; - layer = (Layer *) gimage->layers->data; - } - - if (! layer) - return NULL; - - /* Configure the layer stack to reflect this change */ - gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); - gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); - - /* invalidate the selection boundary because of a layer modification */ - layer_invalidate_boundary (layer); - - /* Set the active layer */ - gimage->active_layer = layer; - gimage->active_channel = NULL; - - /* return the layer */ - return layer; -} - - -Channel * -gimage_set_active_channel (GImage *gimage, Channel * channel) -{ - - /* Not if there is a floating selection */ - if (gimage_floating_sel (gimage)) - return NULL; - - /* First, find the channel - * If it doesn't exist, find the first channel that does - */ - if (! channel) - { - if (! gimage->channels) - { - gimage->active_channel = NULL; - return NULL; - } - channel = (Channel *) gimage->channels->data; - } - - /* Set the active channel */ - gimage->active_channel = channel; - - /* return the channel */ - return channel; -} - - -Channel * -gimage_unset_active_channel (GImage *gimage) -{ - Channel *channel; - - /* make sure there is an active channel */ - if (! (channel = gimage->active_channel)) - return NULL; - - /* Set the active channel */ - gimage->active_channel = NULL; - - return channel; -} - - -void -gimage_set_component_active (GImage *gimage, ChannelType type, int value) -{ - /* No sanity checking here... */ - switch (type) - { - case Red: gimage->active[RED_PIX] = value; break; - case Green: gimage->active[GREEN_PIX] = value; break; - case Blue: gimage->active[BLUE_PIX] = value; break; - case Gray: gimage->active[GRAY_PIX] = value; break; - case Indexed: gimage->active[INDEXED_PIX] = value; break; - case Auxillary: break; - } - - /* If there is an active channel and we mess with the components, - * the active channel gets unset... - */ - if (type != Auxillary) - gimage_unset_active_channel (gimage); -} - - -void -gimage_set_component_visible (GImage *gimage, ChannelType type, int value) -{ - /* No sanity checking here... */ - switch (type) - { - case Red: gimage->visible[RED_PIX] = value; break; - case Green: gimage->visible[GREEN_PIX] = value; break; - case Blue: gimage->visible[BLUE_PIX] = value; break; - case Gray: gimage->visible[GRAY_PIX] = value; break; - case Indexed: gimage->visible[INDEXED_PIX] = value; break; - default: break; - } -} - - -Layer * -gimage_pick_correlate_layer (GImage *gimage, int x, int y) -{ - Layer *layer; - GSList *list; - - list = gimage->layers; - while (list) - { - layer = (Layer *) list->data; - if (layer_pick_correlate (layer, x, y)) - return layer; - - list = g_slist_next (list); - } - - return NULL; -} - +/* These really belong in the layer class */ void gimage_set_layer_mask_apply (GImage *gimage, int layer_id) @@ -1639,6 +195,7 @@ gimage_set_layer_mask_apply (GImage *gimage, int layer_id) } + void gimage_set_layer_mask_edit (GImage *gimage, Layer * layer, int edit) { @@ -1669,1342 +226,3 @@ gimage_set_layer_mask_show (GImage *gimage, int layer_id) drawable_width (GIMP_DRAWABLE(layer)), drawable_height (GIMP_DRAWABLE(layer))); } - -Layer * -gimage_raise_layer (GImage *gimage, Layer *layer_arg) -{ - Layer *layer; - Layer *prev_layer; - GSList *list; - GSList *prev; - int x1, y1, x2, y2; - int index = -1; - int off_x, off_y; - int off2_x, off2_y; - - list = gimage->layers; - prev = NULL; prev_layer = NULL; - - while (list) - { - layer = (Layer *) list->data; - if (prev) - prev_layer = (Layer *) prev->data; - - if (layer == layer_arg) - { - /* We can only raise a layer if it has an alpha channel && - * If it's not already the top layer - */ - if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) - { - list->data = prev_layer; - prev->data = layer; - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); - - /* calculate minimum area to update */ - x1 = MAXIMUM (off_x, off2_x); - y1 = MAXIMUM (off_y, off2_y); - x2 = MINIMUM (off_x + drawable_width (GIMP_DRAWABLE(layer)), - off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); - y2 = MINIMUM (off_y + drawable_height (GIMP_DRAWABLE(layer)), - off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); - if ((x2 - x1) > 0 && (y2 - y1) > 0) - gdisplays_update_area (gimage->ID, x1, y1, (x2 - x1), (y2 - y1)); - - /* invalidate the composite preview */ - gimage_invalidate_preview (gimage); - - return prev_layer; - } - else - { - g_message ("Layer cannot be raised any further"); - return NULL; - } - } - - prev = list; - index++; - list = g_slist_next (list); - } - - return NULL; -} - - -Layer * -gimage_lower_layer (GImage *gimage, Layer *layer_arg) -{ - Layer *layer; - Layer *next_layer; - GSList *list; - GSList *next; - int x1, y1, x2, y2; - int index = 0; - int off_x, off_y; - int off2_x, off2_y; - - next_layer = NULL; - - list = gimage->layers; - - while (list) - { - layer = (Layer *) list->data; - next = g_slist_next (list); - - if (next) - next_layer = (Layer *) next->data; - index++; - - if (layer == layer_arg) - { - /* We can only lower a layer if it has an alpha channel && - * The layer beneath it has an alpha channel && - * If it's not already the bottom layer - */ - if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) - { - list->data = next_layer; - next->data = layer; - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); - - /* calculate minimum area to update */ - x1 = MAXIMUM (off_x, off2_x); - y1 = MAXIMUM (off_y, off2_y); - x2 = MINIMUM (off_x + drawable_width (GIMP_DRAWABLE(layer)), - off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); - y2 = MINIMUM (off_y + drawable_height (GIMP_DRAWABLE(layer)), - off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); - if ((x2 - x1) > 0 && (y2 - y1) > 0) - gdisplays_update_area (gimage->ID, x1, y1, (x2 - x1), (y2 - y1)); - - /* invalidate the composite preview */ - gimage_invalidate_preview (gimage); - - return next_layer; - } - else - { - g_message ("Layer cannot be lowered any further"); - return NULL; - } - } - - list = next; - } - - return NULL; -} - - -Layer * -gimage_merge_visible_layers (GImage *gimage, MergeType merge_type) -{ - GSList *layer_list; - GSList *merge_list = NULL; - Layer *layer; - - layer_list = gimage->layers; - while (layer_list) - { - layer = (Layer *) layer_list->data; - if (drawable_visible (GIMP_DRAWABLE(layer))) - merge_list = g_slist_append (merge_list, layer); - - layer_list = g_slist_next (layer_list); - } - - if (merge_list && merge_list->next) - { - layer = gimage_merge_layers (gimage, merge_list, merge_type); - g_slist_free (merge_list); - return layer; - } - else - { - g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); - g_slist_free (merge_list); - return NULL; - } -} - - -Layer * -gimage_flatten (GImage *gimage) -{ - GSList *layer_list; - GSList *merge_list = NULL; - Layer *layer; - - layer_list = gimage->layers; - while (layer_list) - { - layer = (Layer *) layer_list->data; - if (drawable_visible (GIMP_DRAWABLE(layer))) - merge_list = g_slist_append (merge_list, layer); - - layer_list = g_slist_next (layer_list); - } - - layer = gimage_merge_layers (gimage, merge_list, FlattenImage); - g_slist_free (merge_list); - return layer; -} - - -Layer * -gimage_merge_layers (GImage *gimage, GSList *merge_list, MergeType merge_type) -{ - GSList *reverse_list = NULL; - PixelRegion src1PR, src2PR, maskPR; - PixelRegion * mask; - Layer *merge_layer; - Layer *layer; - Layer *bottom; - unsigned char bg[4] = {0, 0, 0, 0}; - int type; - int count; - int x1, y1, x2, y2; - int x3, y3, x4, y4; - int operation; - int position; - int active[MAX_CHANNELS] = {1, 1, 1, 1}; - int off_x, off_y; - - layer = NULL; - type = RGBA_GIMAGE; - x1 = y1 = x2 = y2 = 0; - bottom = NULL; - - /* Get the layer extents */ - count = 0; - while (merge_list) - { - layer = (Layer *) merge_list->data; - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - - switch (merge_type) - { - case ExpandAsNecessary: - case ClipToImage: - if (!count) - { - x1 = off_x; - y1 = off_y; - x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); - y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); - } - else - { - if (off_x < x1) - x1 = off_x; - if (off_y < y1) - y1 = off_y; - if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) - x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); - if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) - y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); - } - if (merge_type == ClipToImage) - { - x1 = BOUNDS (x1, 0, gimage->width); - y1 = BOUNDS (y1, 0, gimage->height); - x2 = BOUNDS (x2, 0, gimage->width); - y2 = BOUNDS (y2, 0, gimage->height); - } - break; - case ClipToBottomLayer: - if (merge_list->next == NULL) - { - x1 = off_x; - y1 = off_y; - x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); - y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); - } - break; - case FlattenImage: - if (merge_list->next == NULL) - { - x1 = 0; - y1 = 0; - x2 = gimage->width; - y2 = gimage->height; - } - break; - } - - count ++; - reverse_list = g_slist_prepend (reverse_list, layer); - merge_list = g_slist_next (merge_list); - } - - if ((x2 - x1) == 0 || (y2 - y1) == 0) - return NULL; - - /* Start a merge undo group */ - undo_push_group_start (gimage, LAYER_MERGE_UNDO); - - if (merge_type == FlattenImage || - drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) - { - switch (gimage_base_type (gimage)) - { - case RGB: type = RGB_GIMAGE; break; - case GRAY: type = GRAY_GIMAGE; break; - case INDEXED: type = INDEXED_GIMAGE; break; - } - merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), - type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); - - if (!merge_layer) { - g_message ("gimage_merge_layers: could not allocate merge layer"); - return NULL; - } - - GIMP_DRAWABLE(merge_layer)->offset_x = x1; - GIMP_DRAWABLE(merge_layer)->offset_y = y1; - - /* get the background for compositing */ - gimage_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); - - /* init the pixel region */ - pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); - - /* set the region to the background color */ - color_region (&src1PR, bg); - - position = 0; - } - else - { - /* The final merged layer inherits the attributes of the bottomost layer, - * with a notable exception: The resulting layer has an alpha channel - * whether or not the original did - */ - merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), - drawable_type_with_alpha (GIMP_DRAWABLE(layer)), - drawable_name (GIMP_DRAWABLE(layer)), - layer->opacity, layer->mode); - - if (!merge_layer) { - g_message ("gimage_merge_layers: could not allocate merge layer"); - return NULL; - } - - GIMP_DRAWABLE(merge_layer)->offset_x = x1; - GIMP_DRAWABLE(merge_layer)->offset_y = y1; - - /* Set the layer to transparent */ - pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); - - /* set the region to 0's */ - color_region (&src1PR, bg); - - /* Find the index in the layer list of the bottom layer--we need this - * in order to add the final, merged layer to the layer list correctly - */ - layer = (Layer *) reverse_list->data; - position = g_slist_length (gimage->layers) - gimage_get_layer_index (gimage, layer); - - /* set the mode of the bottom layer to normal so that the contents - * aren't lost when merging with the all-alpha merge_layer - * Keep a pointer to it so that we can set the mode right after it's been - * merged so that undo works correctly. - */ - layer -> mode =NORMAL; - bottom = layer; - - } - - while (reverse_list) - { - layer = (Layer *) reverse_list->data; - - /* determine what sort of operation is being attempted and - * if it's actually legal... - */ - operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; - if (operation == -1) - { - g_message ("gimage_merge_layers attempting to merge incompatible layers\n"); - return NULL; - } - - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - x3 = BOUNDS (off_x, x1, x2); - y3 = BOUNDS (off_y, y1, y2); - x4 = BOUNDS (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); - y4 = BOUNDS (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); - - /* configure the pixel regions */ - pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); - pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), - (x4 - x3), (y4 - y3), FALSE); - - if (layer->mask) - { - pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), - (x4 - x3), (y4 - y3), FALSE); - mask = &maskPR; - } - else - mask = NULL; - - combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, - layer->opacity, layer->mode, active, operation); - - gimage_remove_layer (gimage, layer); - reverse_list = g_slist_next (reverse_list); - } - - /* Save old mode in undo */ - if (bottom) - bottom -> mode = merge_layer -> mode; - - g_slist_free (reverse_list); - - /* if the type is flatten, remove all the remaining layers */ - if (merge_type == FlattenImage) - { - merge_list = gimage->layers; - while (merge_list) - { - layer = (Layer *) merge_list->data; - merge_list = g_slist_next (merge_list); - gimage_remove_layer (gimage, layer); - } - - gimage_add_layer (gimage, merge_layer, position); - } - else - { - /* Add the layer to the gimage */ - gimage_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); - } - - /* End the merge undo group */ - undo_push_group_end (gimage); - - /* Update the gimage */ - GIMP_DRAWABLE(merge_layer)->visible = TRUE; - - /* update gdisplay titles to reflect the possibility of - * this layer being the only layer in the gimage - */ - gdisplays_update_title (gimage->ID); - - drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); - - return merge_layer; -} - - -Layer * -gimage_add_layer (GImage *gimage, Layer *float_layer, int position) -{ - LayerUndo * lu; - - if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && - GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) - { - g_message ("gimage_add_layer: attempt to add layer to wrong image"); - return NULL; - } - - { - GSList *ll = gimage->layers; - while (ll) - { - if (ll->data == float_layer) - { - g_message ("gimage_add_layer: trying to add layer to image twice"); - return NULL; - } - ll = g_slist_next(ll); - } - } - - /* Prepare a layer undo and push it */ - lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); - lu->layer = float_layer; - lu->prev_position = 0; - lu->prev_layer = gimage->active_layer; - lu->undo_type = 0; - undo_push_layer (gimage, lu); - - /* If the layer is a floating selection, set the ID */ - if (layer_is_floating_sel (float_layer)) - gimage->floating_sel = float_layer; - - /* let the layer know about the gimage */ - GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; - - /* add the layer to the list at the specified position */ - if (position == -1) - position = gimage_get_layer_index (gimage, gimage->active_layer); - if (position != -1) - { - /* If there is a floating selection (and this isn't it!), - * make sure the insert position is greater than 0 - */ - if (gimage_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) - position = 1; - gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); - } - else - gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); - gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); - - /* notify the layers dialog of the currently active layer */ - gimage_set_active_layer (gimage, float_layer); - - /* update the new layer's area */ - drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); - - /* invalidate the composite preview */ - gimage_invalidate_preview (gimage); - - return float_layer; -} - - -Layer * -gimage_remove_layer (GImage *gimage, Layer * layer) -{ - LayerUndo *lu; - int off_x, off_y; - - if (layer) - { - /* Prepare a layer undo--push it at the end */ - lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); - lu->layer = layer; - lu->prev_position = gimage_get_layer_index (gimage, layer); - lu->prev_layer = layer; - lu->undo_type = 1; - - gimage->layers = g_slist_remove (gimage->layers, layer); - gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); - - /* If this was the floating selection, reset the fs pointer */ - if (gimage->floating_sel == layer) - { - gimage->floating_sel = NULL; - - floating_sel_reset (layer); - } - if (gimage->active_layer == layer) - { - if (gimage->layers) - gimage->active_layer = (Layer *) gimage->layer_stack->data; - else - gimage->active_layer = NULL; - } - - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - gdisplays_update_area (gimage->ID, off_x, off_y, - drawable_width (GIMP_DRAWABLE(layer)), drawable_height (GIMP_DRAWABLE(layer))); - - /* Push the layer undo--It is important it goes here since layer might - * be immediately destroyed if the undo push fails - */ - undo_push_layer (gimage, lu); - - /* invalidate the composite preview */ - gimage_invalidate_preview (gimage); - - return NULL; - } - else - return NULL; -} - - -LayerMask * -gimage_add_layer_mask (GImage *gimage, Layer *layer, LayerMask *mask) -{ - LayerMaskUndo *lmu; - char *error = NULL;; - - if (layer->mask != NULL) - error = "Unable to add a layer mask since\nthe layer already has one."; - if (drawable_indexed (GIMP_DRAWABLE(layer))) - error = "Unable to add a layer mask to a\nlayer in an indexed image."; - if (! layer_has_alpha (layer)) - error = "Cannot add layer mask to a layer\nwith no alpha channel."; - if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) - error = "Cannot add layer mask of different dimensions than specified layer."; - - if (error) - { - g_message (error); - return NULL; - } - - layer_add_mask (layer, mask); - - /* Prepare a layer undo and push it */ - lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); - lmu->layer = layer; - lmu->mask = mask; - lmu->undo_type = 0; - lmu->apply_mask = layer->apply_mask; - lmu->edit_mask = layer->edit_mask; - lmu->show_mask = layer->show_mask; - undo_push_layer_mask (gimage, lmu); - - gimage_set_layer_mask_edit (gimage, layer, layer->edit_mask); - - return mask; -} - - -Channel * -gimage_remove_layer_mask (GImage *gimage, Layer *layer, int mode) -{ - LayerMaskUndo *lmu; - int off_x, off_y; - - if (! (layer) ) - return NULL; - if (! layer->mask) - return NULL; - - /* Start an undo group */ - undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); - - /* Prepare a layer mask undo--push it below */ - lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); - lmu->layer = layer; - lmu->mask = layer->mask; - lmu->undo_type = 1; - lmu->mode = mode; - lmu->apply_mask = layer->apply_mask; - lmu->edit_mask = layer->edit_mask; - lmu->show_mask = layer->show_mask; - - layer_apply_mask (layer, mode); - - /* Push the undo--Important to do it here, AFTER the call - * to layer_apply_mask, in case the undo push fails and the - * mask is delete : NULL)d - */ - undo_push_layer_mask (gimage, lmu); - - /* end the undo group */ - undo_push_group_end (gimage); - - /* If the layer mode is discard, update the layer--invalidate gimage also */ - if (mode == DISCARD) - { - gimage_invalidate_preview (gimage); - - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - gdisplays_update_area (gimage->ID, off_x, off_y, - drawable_width (GIMP_DRAWABLE(layer)), drawable_height (GIMP_DRAWABLE(layer))); - } - gdisplays_flush (); - - return NULL; -} - - -Channel * -gimage_raise_channel (GImage *gimage, Channel * channel_arg) -{ - Channel *channel; - Channel *prev_channel; - GSList *list; - GSList *prev; - int index = -1; - - list = gimage->channels; - prev = NULL; - prev_channel = NULL; - - while (list) - { - channel = (Channel *) list->data; - if (prev) - prev_channel = (Channel *) prev->data; - - if (channel == channel_arg) - { - if (prev) - { - list->data = prev_channel; - prev->data = channel; - drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); - return prev_channel; - } - else - { - g_message ("Channel cannot be raised any further"); - return NULL; - } - } - - prev = list; - index++; - list = g_slist_next (list); - } - - return NULL; -} - - -Channel * -gimage_lower_channel (GImage *gimage, Channel *channel_arg) -{ - Channel *channel; - Channel *next_channel; - GSList *list; - GSList *next; - int index = 0; - - list = gimage->channels; - next_channel = NULL; - - while (list) - { - channel = (Channel *) list->data; - next = g_slist_next (list); - - if (next) - next_channel = (Channel *) next->data; - index++; - - if (channel == channel_arg) - { - if (next) - { - list->data = next_channel; - next->data = channel; - drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); - - return next_channel; - } - else - { - g_message ("Channel cannot be lowered any further"); - return NULL; - } - } - - list = next; - } - - return NULL; -} - - -Channel * -gimage_add_channel (GImage *gimage, Channel *channel, int position) -{ - ChannelUndo * cu; - - if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && - GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) - { - g_message ("gimage_add_channel: attempt to add channel to wrong image"); - return NULL; - } - - { - GSList *cc = gimage->channels; - while (cc) - { - if (cc->data == channel) - { - g_message ("gimage_add_channel: trying to add channel to image twice"); - return NULL; - } - cc = g_slist_next (cc); - } - } - - - /* Prepare a channel undo and push it */ - cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); - cu->channel = channel; - cu->prev_position = 0; - cu->prev_channel = gimage->active_channel; - cu->undo_type = 0; - undo_push_channel (gimage, cu); - - /* add the channel to the list */ - gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); - - /* notify this gimage of the currently active channel */ - gimage_set_active_channel (gimage, channel); - - /* if channel is visible, update the image */ - if (drawable_visible (GIMP_DRAWABLE(channel))) - drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); - - return channel; -} - - -Channel * -gimage_remove_channel (GImage *gimage, Channel *channel) -{ - ChannelUndo * cu; - - if (channel) - { - /* Prepare a channel undo--push it below */ - cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); - cu->channel = channel; - cu->prev_position = gimage_get_channel_index (gimage, channel); - cu->prev_channel = gimage->active_channel; - cu->undo_type = 1; - - gimage->channels = g_slist_remove (gimage->channels, channel); - - if (gimage->active_channel == channel) - { - if (gimage->channels) - gimage->active_channel = (((Channel *) gimage->channels->data)); - else - gimage->active_channel = NULL; - } - - if (drawable_visible (GIMP_DRAWABLE(channel))) - drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); - - /* Important to push the undo here in case the push fails */ - undo_push_channel (gimage, cu); - - return channel; - } - else - return NULL; -} - - -/************************************************************/ -/* Access functions */ -/************************************************************/ - -int -gimage_is_flat (GImage *gimage) -{ - Layer *layer; - int ac_visible = TRUE; - int flat = TRUE; - int off_x, off_y; - - /* Are there no layers? */ - if (gimage_is_empty (gimage)) - flat = FALSE; - /* Is there more than one layer? */ - else if (gimage->layers->next) - flat = FALSE; - else - { - /* determine if all channels are visible */ - int a, b; - - layer = gimage->layers->data; - a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); - for (b = 0; b < a; b++) - if (gimage->visible[b] == FALSE) - ac_visible = FALSE; - - /* What makes a flat image? - * 1) the solitary layer is exactly gimage-sized and placed - * 2) no layer mask - * 3) opacity == OPAQUE_OPACITY - * 4) all channels must be visible - */ - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || - (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || - (off_x != 0) || - (off_y != 0) || - (layer->mask != NULL) || - (layer->opacity != OPAQUE_OPACITY) || - (ac_visible == FALSE)) - flat = FALSE; - } - - /* Are there any channels? */ - if (gimage->channels) - flat = FALSE; - - if (gimage->flat != flat) - { - if (flat) - gimage_deflate (gimage); - else - gimage_inflate (gimage); - } - - return gimage->flat; -} - -int -gimage_is_empty (GImage *gimage) -{ - return (! gimage->layers); -} - -GimpDrawable * -gimage_active_drawable (GImage *gimage) -{ - Layer *layer; - - /* If there is an active channel (a saved selection, etc.), - * we ignore the active layer - */ - if (gimage->active_channel != NULL) - return GIMP_DRAWABLE (gimage->active_channel); - else if (gimage->active_layer != NULL) - { - layer = gimage->active_layer; - if (layer->mask && layer->edit_mask) - return GIMP_DRAWABLE(layer->mask); - else - return GIMP_DRAWABLE(layer); - } - else - return NULL; -} - -int -gimage_base_type (GImage *gimage) -{ - return gimage->base_type; -} - -int -gimage_base_type_with_alpha (GImage *gimage) -{ - switch (gimage->base_type) - { - case RGB: - return RGBA_GIMAGE; - case GRAY: - return GRAYA_GIMAGE; - case INDEXED: - return INDEXEDA_GIMAGE; - } - return RGB_GIMAGE; -} - -char * -gimage_filename (GImage *gimage) -{ - if (gimage->has_filename) - return gimage->filename; - else - return "Untitled"; -} - -int -gimage_enable_undo (GImage *gimage) -{ - /* Free all undo steps as they are now invalidated */ - undo_free (gimage); - - gimage->undo_on = TRUE; - - return TRUE; -} - -int -gimage_disable_undo (GImage *gimage) -{ - gimage->undo_on = FALSE; - return TRUE; -} - -int -gimage_dirty (GImage *gimage) -{ - GDisplay *gdisp; - - if (gimage->dirty < 0) - gimage->dirty = 2; - else - gimage->dirty ++; - if (active_tool && !active_tool->preserve) { - gdisp = active_tool->gdisp_ptr; - if (gdisp) { - if (gdisp->gimage->ID == gimage->ID) - tools_initialize (active_tool->type, gdisp); - else - tools_initialize (active_tool->type, NULL); - } - } - - return gimage->dirty; -} - -int -gimage_clean (GImage *gimage) -{ - if (gimage->dirty <= 0) - gimage->dirty = 0; - else - gimage->dirty --; - return gimage->dirty; -} - -void -gimage_clean_all (GImage *gimage) -{ - gimage->dirty = 0; -} - -Layer * -gimage_floating_sel (GImage *gimage) -{ - if (gimage->floating_sel == NULL) - return NULL; - else return gimage->floating_sel; -} - -unsigned char * -gimage_cmap (GImage *gimage) -{ - return drawable_cmap (gimage_active_drawable (gimage)); -} - - -/************************************************************/ -/* Projection access functions */ -/************************************************************/ - -TileManager * -gimage_projection (GImage *gimage) -{ - Layer * layer; - - /* If the gimage is flat, we simply want the data of the - * first layer...Otherwise, we'll pass back the projection - */ - if (gimage_is_flat (gimage)) - { - if ((layer = gimage->active_layer)) - return drawable_data (GIMP_DRAWABLE(layer)); - else - return NULL; - } - else - { - if ((gimage->projection->levels[0].width != gimage->width) || - (gimage->projection->levels[0].height != gimage->height)) - gimage_allocate_projection (gimage); - - return gimage->projection; - } -} - -int -gimage_projection_type (GImage *gimage) -{ - Layer * layer; - - /* If the gimage is flat, we simply want the type of the - * first layer...Otherwise, we'll pass back the proj_type - */ - if (gimage_is_flat (gimage)) - { - if ((layer = (gimage->active_layer))) - return drawable_type (GIMP_DRAWABLE(layer)); - else - return -1; - } - else - return gimage->proj_type; -} - -int -gimage_projection_bytes (GImage *gimage) -{ - Layer * layer; - - /* If the gimage is flat, we simply want the bytes in the - * first layer...Otherwise, we'll pass back the proj_bytes - */ - if (gimage_is_flat (gimage)) - { - if ((layer = (gimage->active_layer))) - return drawable_bytes (GIMP_DRAWABLE(layer)); - else - return -1; - } - else - return gimage->proj_bytes; -} - -int -gimage_projection_opacity (GImage *gimage) -{ - Layer * layer; - - /* If the gimage is flat, return the opacity of the active layer - * Otherwise, we'll pass back OPAQUE_OPACITY - */ - if (gimage_is_flat (gimage)) - { - if ((layer = (gimage->active_layer))) - return layer->opacity; - else - return OPAQUE_OPACITY; - } - else - return OPAQUE_OPACITY; -} - -void -gimage_projection_realloc (GImage *gimage) -{ - if (! gimage_is_flat (gimage)) - gimage_allocate_projection (gimage); -} - -/************************************************************/ -/* Composition access functions */ -/************************************************************/ - -TileManager * -gimage_composite (GImage *gimage) -{ - return gimage_projection (gimage); -} - -int -gimage_composite_type (GImage *gimage) -{ - return gimage_projection_type (gimage); -} - -int -gimage_composite_bytes (GImage *gimage) -{ - return gimage_projection_bytes (gimage); -} - -static TempBuf * -gimage_construct_composite_preview (GImage *gimage, int width, int height) -{ - Layer * layer; - PixelRegion src1PR, src2PR, maskPR; - PixelRegion * mask; - TempBuf *comp; - TempBuf *layer_buf; - TempBuf *mask_buf; - GSList *list = gimage->layers; - GSList *reverse_list = NULL; - double ratio; - int x, y, w, h; - int x1, y1, x2, y2; - int bytes; - int construct_flag; - int visible[MAX_CHANNELS] = {1, 1, 1, 1}; - int off_x, off_y; - - ratio = (double) width / (double) gimage->width; - - switch (gimage_base_type (gimage)) - { - case RGB: - case INDEXED: - bytes = 4; - break; - case GRAY: - bytes = 2; - break; - default: - bytes = 0; - break; - } - - /* The construction buffer */ - comp = temp_buf_new (width, height, bytes, 0, 0, NULL); - memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); - - while (list) - { - layer = (Layer *) list->data; - - /* only add layers that are visible and not floating selections to the list */ - if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) - reverse_list = g_slist_prepend (reverse_list, layer); - - list = g_slist_next (list); - } - - construct_flag = 0; - - while (reverse_list) - { - layer = (Layer *) reverse_list->data; - - drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); - - x = (int) (ratio * off_x + 0.5); - y = (int) (ratio * off_y + 0.5); - w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); - h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); - - x1 = BOUNDS (x, 0, width); - y1 = BOUNDS (y, 0, height); - x2 = BOUNDS (x + w, 0, width); - y2 = BOUNDS (y + h, 0, height); - - src1PR.bytes = comp->bytes; - src1PR.x = x1; src1PR.y = y1; - src1PR.w = (x2 - x1); - src1PR.h = (y2 - y1); - src1PR.rowstride = comp->width * src1PR.bytes; - src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; - - layer_buf = layer_preview (layer, w, h); - src2PR.bytes = layer_buf->bytes; - src2PR.w = src1PR.w; src2PR.h = src1PR.h; - src2PR.x = src1PR.x; src2PR.y = src1PR.y; - src2PR.rowstride = layer_buf->width * src2PR.bytes; - src2PR.data = temp_buf_data (layer_buf) + - (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; - - if (layer->mask && layer->apply_mask) - { - mask_buf = layer_mask_preview (layer, w, h); - maskPR.bytes = mask_buf->bytes; - maskPR.rowstride = mask_buf->width; - maskPR.data = mask_buf_data (mask_buf) + - (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; - mask = &maskPR; - } - else - mask = NULL; - - /* Based on the type of the layer, project the layer onto the - * composite preview... - * Indexed images are actually already converted to RGB and RGBA, - * so just project them as if they were type "intensity" - * Send in all TRUE for visible since that info doesn't matter for previews - */ - switch (drawable_type (GIMP_DRAWABLE(layer))) - { - case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: - if (! construct_flag) - initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, - layer->mode, visible, INITIAL_INTENSITY); - else - combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, - layer->mode, visible, COMBINE_INTEN_A_INTEN); - break; - - case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: - if (! construct_flag) - initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, - layer->mode, visible, INITIAL_INTENSITY_ALPHA); - else - combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, - layer->mode, visible, COMBINE_INTEN_A_INTEN_A); - break; - - default: - break; - } - - construct_flag = 1; - - reverse_list = g_slist_next (reverse_list); - } - - g_slist_free (reverse_list); - - return comp; -} - -TempBuf * -gimage_composite_preview (GImage *gimage, ChannelType type, - int width, int height) -{ - int channel; - - switch (type) - { - case Red: channel = RED_PIX; break; - case Green: channel = GREEN_PIX; break; - case Blue: channel = BLUE_PIX; break; - case Gray: channel = GRAY_PIX; break; - case Indexed: channel = INDEXED_PIX; break; - default: return NULL; - } - - /* The easy way */ - if (gimage->comp_preview_valid[channel] && - gimage->comp_preview->width == width && - gimage->comp_preview->height == height) - return gimage->comp_preview; - /* The hard way */ - else - { - if (gimage->comp_preview) - temp_buf_free (gimage->comp_preview); - - /* Actually construct the composite preview from the layer previews! - * This might seem ridiculous, but it's actually the best way, given - * a number of unsavory alternatives. - */ - gimage->comp_preview = gimage_construct_composite_preview (gimage, width, height); - gimage->comp_preview_valid[channel] = TRUE; - - return gimage->comp_preview; - } -} - -int -gimage_preview_valid (gimage, type) - GImage *gimage; - ChannelType type; -{ - switch (type) - { - case Red: return gimage->comp_preview_valid [RED_PIX]; break; - case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; - case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; - case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; - case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; - default: return TRUE; - } -} - -void -gimage_invalidate_preview (GImage *gimage) -{ - Layer *layer; - /* Invalidate the floating sel if it exists */ - if ((layer = gimage_floating_sel (gimage))) - floating_sel_invalidate (layer); - - gimage->comp_preview_valid[0] = FALSE; - gimage->comp_preview_valid[1] = FALSE; - gimage->comp_preview_valid[2] = FALSE; -} - -void -gimage_invalidate_previews (void) -{ - GSList *tmp = image_list; - GImage *gimage; - - while (tmp) - { - gimage = (GImage *) tmp->data; - gimage_invalidate_preview (gimage); - tmp = g_slist_next (tmp); - } -} - diff --git a/app/gimage.h b/app/gimage.h index 036b72bcf1..ecfbd3f37e 100644 --- a/app/gimage.h +++ b/app/gimage.h @@ -18,246 +18,106 @@ #ifndef __GIMAGE_H__ #define __GIMAGE_H__ -#include "boundary.h" -#include "drawable.h" -#include "channel.h" -#include "layer.h" -#include "paint_funcs.h" -#include "temp_buf.h" -#include "tile_manager.h" +#include "gimpimage.h" +/* icky.. this is temporary */ +#include "gimpimageP.h" -/* the image types */ -#define RGB_GIMAGE 0 -#define RGBA_GIMAGE 1 -#define GRAY_GIMAGE 2 -#define GRAYA_GIMAGE 3 -#define INDEXED_GIMAGE 4 -#define INDEXEDA_GIMAGE 5 +typedef GimpImage GImage; -#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) +GImage* +gimage_new(int width, int height, GimpImageBaseType base_type); -#define GRAY_PIX 0 -#define ALPHA_G_PIX 1 -#define RED_PIX 0 -#define GREEN_PIX 1 -#define BLUE_PIX 2 -#define ALPHA_PIX 3 -#define INDEXED_PIX 0 -#define ALPHA_I_PIX 1 +GImage* +gimage_get_ID (gint ID); -#define RGB 0 -#define GRAY 1 -#define INDEXED 2 +void +gimage_delete (GImage *gimage); -#define MAX_CHANNELS 4 +void +gimage_invalidate_previews (void); -/* the image fill types */ -#define BACKGROUND_FILL 0 -#define WHITE_FILL 1 -#define TRANSPARENT_FILL 2 -#define NO_FILL 3 +void +gimage_set_layer_mask_apply (GImage *gimage, int layer_id); -#define COLORMAP_SIZE 768 +void +gimage_set_layer_mask_edit (GImage *gimage, Layer * layer, int edit); -#define HORIZONTAL_GUIDE 1 -#define VERTICAL_GUIDE 2 - -typedef enum -{ - Red, - Green, - Blue, - Gray, - Indexed, - Auxillary -} ChannelType; - -typedef enum -{ - ExpandAsNecessary, - ClipToImage, - ClipToBottomLayer, - FlattenImage -} MergeType; - -/* structure declarations */ - -typedef struct _Guide Guide; -typedef struct _GImage GImage; - -struct _Guide -{ - int ref_count; - int position; - int orientation; -}; - -struct _GImage -{ - char *filename; /* original filename */ - int has_filename; /* has a valid filename */ - - int width, height; /* width and height attributes */ - int base_type; /* base gimage type */ - - unsigned char * cmap; /* colormap--for indexed */ - int num_cols; /* number of cols--for indexed */ - - int dirty; /* dirty flag -- # of ops */ - int undo_on; /* Is undo enabled? */ - - int instance_count; /* number of instances */ - int ref_count; /* number of references */ - - TileManager *shadow; /* shadow buffer tiles */ - - int ID; /* Unique gimage identifier */ - - /* Projection attributes */ - int flat; /* Is the gimage flat? */ - int construct_flag; /* flag for construction */ - int proj_type; /* type of the projection image */ - int proj_bytes; /* bpp in projection image */ - int proj_level; /* projection level */ - TileManager *projection; /* The projection--layers & */ - /* channels */ - - GList *guides; /* guides */ - - /* Layer/Channel attributes */ - GSList *layers; /* the list of layers */ - GSList *channels; /* the list of masks */ - GSList *layer_stack; /* the layers in MRU order */ - - Layer * active_layer; /* ID of active layer */ - Channel * active_channel; /* ID of active channel */ - Layer * floating_sel; /* ID of fs layer */ - Channel * selection_mask; /* selection mask channel */ - - int visible [MAX_CHANNELS]; /* visible channels */ - int active [MAX_CHANNELS]; /* active channels */ - - int by_color_select; /* TRUE if there's an active */ - /* "by color" selection dialog */ - - /* Undo apparatus */ - GSList *undo_stack; /* stack for undo operations */ - GSList *redo_stack; /* stack for redo operations */ - int undo_bytes; /* bytes in undo stack */ - int undo_levels; /* levels in undo stack */ - int pushing_undo_group; /* undo group status flag */ - - /* Composite preview */ - TempBuf *comp_preview; /* the composite preview */ - int comp_preview_valid[3]; /* preview valid-1/channel */ -}; +void +gimage_set_layer_mask_show (GImage *gimage, int layer_id); -/* function declarations */ - -GImage * gimage_new (int, int, int); -void gimage_set_filename (GImage *, char *); -void gimage_resize (GImage *, int, int, int, int); -void gimage_scale (GImage *, int, int); -GImage * gimage_get_named (char *); -GImage * gimage_get_ID (int); -TileManager * gimage_shadow (GImage *, int, int, int); -void gimage_free_shadow (GImage *); -void gimage_delete (GImage *); -void gimage_apply_image (GImage *, GimpDrawable *, PixelRegion *, int, int, int, - TileManager *, int, int); -void gimage_replace_image (GImage *, GimpDrawable *, PixelRegion *, int, int, - PixelRegion *, int, int); -void gimage_get_foreground (GImage *, GimpDrawable *, unsigned char *); -void gimage_get_background (GImage *, GimpDrawable *, unsigned char *); -void gimage_get_color (GImage *, int, unsigned char *, - unsigned char *); -void gimage_transform_color (GImage *, GimpDrawable *, unsigned char *, - unsigned char *, int); -Guide* gimage_add_hguide (GImage *); -Guide* gimage_add_vguide (GImage *); -void gimage_add_guide (GImage *, Guide *); -void gimage_remove_guide (GImage *, Guide *); -void gimage_delete_guide (GImage *, Guide *); - - -/* layer/channel functions */ - -int gimage_get_layer_index (GImage *, Layer *); -int gimage_get_channel_index (GImage *, Channel *); -Layer * gimage_get_active_layer (GImage *); -Channel * gimage_get_active_channel (GImage *); -Channel * gimage_get_mask (GImage *); -int gimage_get_component_active (GImage *, ChannelType); -int gimage_get_component_visible (GImage *, ChannelType); -int gimage_layer_boundary (GImage *, BoundSeg **, int *); -Layer * gimage_set_active_layer (GImage *, Layer *); -Channel * gimage_set_active_channel (GImage *, Channel *); -Channel * gimage_unset_active_channel (GImage *); -void gimage_set_component_active (GImage *, ChannelType, int); -void gimage_set_component_visible (GImage *, ChannelType, int); -Layer * gimage_pick_correlate_layer (GImage *, int, int); -void gimage_set_layer_mask_apply (GImage *, int); -void gimage_set_layer_mask_edit (GImage *, Layer *, int); -void gimage_set_layer_mask_show (GImage *, int); -Layer * gimage_raise_layer (GImage *, Layer *); -Layer * gimage_lower_layer (GImage *, Layer *); -Layer * gimage_merge_visible_layers (GImage *, MergeType); -Layer * gimage_flatten (GImage *); -Layer * gimage_merge_layers (GImage *, GSList *, MergeType); -Layer * gimage_add_layer (GImage *, Layer *, int); -Layer * gimage_remove_layer (GImage *, Layer *); -LayerMask * gimage_add_layer_mask (GImage *, Layer *, LayerMask *); -Channel * gimage_remove_layer_mask (GImage *, Layer *, int); -Channel * gimage_raise_channel (GImage *, Channel *); -Channel * gimage_lower_channel (GImage *, Channel *); -Channel * gimage_add_channel (GImage *, Channel *, int); -Channel * gimage_remove_channel (GImage *, Channel *); -void gimage_construct (GImage *, int, int, int, int); -void gimage_invalidate (GImage *, int, int, int, int, int, int, int, int); -void gimage_validate (TileManager *, Tile *, int); -void gimage_inflate (GImage *); -void gimage_deflate (GImage *); - - -/* Access functions */ - -int gimage_is_flat (GImage *); -int gimage_is_empty (GImage *); -GimpDrawable * gimage_active_drawable (GImage *); -int gimage_base_type (GImage *); -int gimage_base_type_with_alpha (GImage *); -char * gimage_filename (GImage *); -int gimage_enable_undo (GImage *); -int gimage_disable_undo (GImage *); -int gimage_dirty (GImage *); -int gimage_clean (GImage *); -void gimage_clean_all (GImage *); -Layer * gimage_floating_sel (GImage *); -unsigned char * gimage_cmap (GImage *); - - -/* projection access functions */ - -TileManager * gimage_projection (GImage *); -int gimage_projection_type (GImage *); -int gimage_projection_bytes (GImage *); -int gimage_projection_opacity (GImage *); -void gimage_projection_realloc (GImage *); - - -/* composite access functions */ - -TileManager * gimage_composite (GImage *); -int gimage_composite_type (GImage *); -int gimage_composite_bytes (GImage *); -TempBuf * gimage_composite_preview (GImage *, ChannelType, int, int); -int gimage_preview_valid (GImage *, ChannelType); -void gimage_invalidate_preview (GImage *); - -void gimage_invalidate_previews (void); - -/* from drawable.c */ -GImage * drawable_gimage (GimpDrawable*); +#define gimage_set_filename gimp_image_set_filename +#define gimage_resize gimp_image_resize +#define gimage_scale gimp_image_scale +#define gimage_get_named gimp_image_get_named +#define gimage_shadow gimp_image_shadow +#define gimage_free_shadow gimp_image_free_shadow +#define gimage_apply_image gimp_image_apply_image +#define gimage_replace_image gimp_image_replace_image +#define gimage_get_foreground gimp_image_get_foreground +#define gimage_get_background gimp_image_get_background +#define gimage_get_color gimp_image_get_color +#define gimage_transform_color gimp_image_transform_color +#define gimage_add_hguide gimp_image_add_hguide +#define gimage_add_vguide gimp_image_add_vguide +#define gimage_add_guide gimp_image_add_guide +#define gimage_remove_guide gimp_image_remove_guide +#define gimage_delete_guide gimp_image_delete_guide +#define gimage_get_layer_index gimp_image_get_layer_index +#define gimage_get_channel_index gimp_image_get_channel_index +#define gimage_get_active_layer gimp_image_get_active_layer +#define gimage_get_active_channel gimp_image_get_active_channel +#define gimage_get_mask gimp_image_get_mask +#define gimage_get_component_active gimp_image_get_component_active +#define gimage_get_component_visible gimp_image_get_component_visible +#define gimage_layer_boundary gimp_image_layer_boundary +#define gimage_set_active_layer gimp_image_set_active_layer +#define gimage_set_active_channel gimp_image_set_active_channel +#define gimage_unset_active_channel gimp_image_unset_active_channel +#define gimage_set_component_active gimp_image_set_component_active +#define gimage_set_component_visible gimp_image_set_component_visible +#define gimage_pick_correlate_layer gimp_image_pick_correlate_layer +#define gimage_raise_layer gimp_image_raise_layer +#define gimage_lower_layer gimp_image_lower_layer +#define gimage_merge_visible_layers gimp_image_merge_visible_layers +#define gimage_flatten gimp_image_flatten +#define gimage_merge_layers gimp_image_merge_layers +#define gimage_add_layer gimp_image_add_layer +#define gimage_remove_layer gimp_image_remove_layer +#define gimage_add_layer_mask gimp_image_add_layer_mask +#define gimage_remove_layer_mask gimp_image_remove_layer_mask +#define gimage_raise_channel gimp_image_raise_channel +#define gimage_lower_channel gimp_image_lower_channel +#define gimage_add_channel gimp_image_add_channel +#define gimage_remove_channel gimp_image_remove_channel +#define gimage_construct gimp_image_construct +#define gimage_invalidate gimp_image_invalidate +#define gimage_validate gimp_image_validate +#define gimage_inflate gimp_image_inflate +#define gimage_deflate gimp_image_deflate +#define gimage_is_flat gimp_image_is_flat +#define gimage_is_empty gimp_image_is_empty +#define gimage_active_drawable gimp_image_active_drawable +#define gimage_base_type gimp_image_base_type +#define gimage_base_type_with_alpha gimp_image_base_type_with_alpha +#define gimage_filename gimp_image_filename +#define gimage_enable_undo gimp_image_enable_undo +#define gimage_disable_undo gimp_image_disable_undo +#define gimage_dirty gimp_image_dirty +#define gimage_clean gimp_image_clean +#define gimage_clean_all gimp_image_clean_all +#define gimage_floating_sel gimp_image_floating_sel +#define gimage_cmap gimp_image_cmap +#define gimage_projection gimp_image_projection +#define gimage_projection_type gimp_image_projection_type +#define gimage_projection_bytes gimp_image_projection_bytes +#define gimage_projection_opacity gimp_image_projection_opacity +#define gimage_projection_realloc gimp_image_projection_realloc +#define gimage_composite gimp_image_composite +#define gimage_composite_type gimp_image_composite_type +#define gimage_composite_bytes gimp_image_composite_bytes +#define gimage_composite_preview gimp_image_composite_preview +#define gimage_preview_valid gimp_image_preview_valid +#define gimage_invalidate_preview gimp_image_invalidate_preview #endif /* __GIMAGE_H__ */ diff --git a/app/gimpimage.c b/app/gimpimage.c new file mode 100644 index 0000000000..3ee8dec5f8 --- /dev/null +++ b/app/gimpimage.c @@ -0,0 +1,2920 @@ +/* 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 +#include +#include +#include "drawable.h" +#include "errors.h" +#include "floating_sel.h" +#include "general.h" +#include "gimpimageP.h" +#include "gimpimage.h" +#include "gimage_mask.h" +#include "paint_funcs.h" +#include "palette.h" +#include "undo.h" +#include "gimpsignal.h" + +#include "tile_manager_pvt.h" /* ick. */ +#include "layer_pvt.h" +#include "drawable_pvt.h" /* ick ick. */ + +/* Local function declarations */ +static void gimp_image_free_projection (GimpImage *); +static void gimp_image_allocate_shadow (GimpImage *, int, int, int); +static void gimp_image_allocate_projection (GimpImage *); +static void gimp_image_free_layers (GimpImage *); +static void gimp_image_free_channels (GimpImage *); +static void gimp_image_construct_layers (GimpImage *, int, int, int, int); +static void gimp_image_construct_channels (GimpImage *, int, int, int, int); +static void gimp_image_initialize_projection (GimpImage *, int, int, int, int); +static void gimp_image_get_active_channels (GimpImage *, GimpDrawable *, int *); + +/* projection functions */ +static void project_intensity (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_intensity_alpha (GimpImage *, Layer *, PixelRegion *, + PixelRegion *, PixelRegion *); +static void project_indexed (GimpImage *, Layer *, PixelRegion *, + PixelRegion *); +static void project_channel (GimpImage *, Channel *, PixelRegion *, + PixelRegion *); + +/* + * Global variables + */ +int valid_combinations[][MAX_CHANNELS + 1] = +{ + /* RGB GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A }, + /* RGBA GIMAGE */ + { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A }, + /* GRAY GIMAGE */ + { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 }, + /* GRAYA GIMAGE */ + { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 }, + /* INDEXED GIMAGE */ + { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 }, + /* INDEXEDA GIMAGE */ + { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 }, +}; + + +/* + * Static variables + */ +GSList *image_list = NULL; + + +enum{ + DIRTY, + REPAINT, + RENAME, + RESIZE, + RESTRUCTURE, + LAST_SIGNAL +}; +static void gimp_image_destroy (GtkObject *); + +static guint gimp_image_signals[LAST_SIGNAL]; +static GimpObjectClass* parent_class; + +static void +gimp_image_class_init (GimpImageClass *klass) +{ + GtkObjectClass *object_class; + GtkType type; + + object_class = GTK_OBJECT_CLASS(klass); + parent_class = gtk_type_class (gimp_object_get_type ()); + + type=object_class->type; + + object_class->destroy = gimp_image_destroy; + + gimp_image_signals[DIRTY] = + gimp_signal_new ("dirty", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[REPAINT] = + gimp_signal_new ("repaint", 0, type, 0, gimp_sigtype_int_int_int_int); + gimp_image_signals[RENAME] = + gimp_signal_new ("rename", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESIZE] = + gimp_signal_new ("resize", 0, type, 0, gimp_sigtype_void); + gimp_image_signals[RESTRUCTURE] = + gimp_signal_new ("restructure", 0, type, 0, gimp_sigtype_void); + + gtk_object_class_add_signals (object_class, gimp_image_signals, LAST_SIGNAL); +} + + +/* static functions */ + +static void gimp_image_init (GimpImage *gimage) +{ + gimage->has_filename = 0; + gimage->num_cols = 0; + gimage->cmap = NULL; + /* ID and ref_count handled in gimage.c */ + gimage->instance_count = 0; + gimage->shadow = NULL; + gimage->dirty = 1; + gimage->undo_on = TRUE; + gimage->flat = TRUE; + gimage->construct_flag = -1; + gimage->projection = NULL; + gimage->guides = NULL; + gimage->layers = NULL; + gimage->channels = NULL; + gimage->layer_stack = NULL; + gimage->undo_stack = NULL; + gimage->redo_stack = NULL; + gimage->undo_bytes = 0; + gimage->undo_levels = 0; + gimage->pushing_undo_group = 0; + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; + gimage->comp_preview = NULL; +} + +GtkType gimp_image_get_type(void){ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpImage", + sizeof(GimpImage), + sizeof(GimpImageClass), + (GtkClassInitFunc)gimp_image_class_init, + (GtkObjectInitFunc)gimp_image_init, + NULL, + NULL}; + type=gtk_type_unique(gimp_object_get_type(), &info); + } + return type; +} + + +/* static functions */ + +static void +gimp_image_allocate_projection (GimpImage *gimage) +{ + if (gimage->projection) + gimp_image_free_projection (gimage); + + /* Find the number of bytes required for the projection. + * This includes the intensity channels and an alpha channel + * if one doesn't exist. + */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + gimage->proj_bytes = 4; + gimage->proj_type = RGBA_GIMAGE; + break; + case GRAY: + gimage->proj_bytes = 2; + gimage->proj_type = GRAYA_GIMAGE; + break; + default: + g_message ("gimage type unsupported.\n"); + break; + } + + /* allocate the new projection */ + gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); + gimage->projection->user_data = (void *) gimage; + tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); +} + +static void +gimp_image_free_projection (GimpImage *gimage) +{ + if (gimage->projection) + tile_manager_destroy (gimage->projection); + + gimage->projection = NULL; +} + +static void +gimp_image_allocate_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + /* allocate the new projection */ + gimage->shadow = tile_manager_new (width, height, bpp); +} + + +/* function definitions */ + +GimpImage * +gimp_image_new (int width, int height, int base_type) +{ + GimpImage *gimage=GIMP_IMAGE(gtk_type_new(gimp_image_get_type ())); + int i; + + gimage->filename = NULL; + gimage->width = width; + gimage->height = height; + + gimage->base_type = base_type; + + switch (base_type) + { + case RGB: + case GRAY: + break; + case INDEXED: + /* always allocate 256 colors for the colormap */ + gimage->num_cols = 0; + gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE); + memset (gimage->cmap, 0, COLORMAP_SIZE); + break; + default: + break; + } + + /* configure the active pointers */ + gimage->active_layer = NULL; + gimage->active_channel = NULL; /* no default active channel */ + gimage->floating_sel = NULL; + + /* set all color channels visible and active */ + for (i = 0; i < MAX_CHANNELS; i++) + { + gimage->visible[i] = 1; + gimage->active[i] = 1; + } + + /* create the selection mask */ + gimage->selection_mask = channel_new_mask (gimage->ID, gimage->width, gimage->height); + + + return gimage; +} + + +void +gimp_image_set_filename (GimpImage *gimage, char *filename) +{ + char *new_filename; + + new_filename = g_strdup (filename); + if (gimage->has_filename) + g_free (gimage->filename); + + if (filename && filename[0]) + { + gimage->filename = new_filename; + gimage->has_filename = TRUE; + } + else + { + gimage->filename = NULL; + gimage->has_filename = FALSE; + } + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RENAME]); +} + + +void +gimp_image_resize (GimpImage *gimage, int new_width, int new_height, + int offset_x, int offset_y) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + + if (new_width <= 0 || new_height <= 0) + { + g_message ("gimp_image_resize: width and height must be positive"); + return; + } + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + gimage->width = new_width; + gimage->height = new_height; + + /* Resize all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_resize (channel, new_width, new_height, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); + gimage_mask_invalidate (gimage); + + /* Reposition all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + layer_translate (layer, offset_x, offset_y); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +void +gimp_image_scale (GimpImage *gimage, int new_width, int new_height) +{ + Channel *channel; + Layer *layer; + Layer *floating_layer; + GSList *list; + int old_width, old_height; + int layer_width, layer_height; + + /* Get the floating layer if one exists */ + floating_layer = gimp_image_floating_sel (gimage); + + undo_push_group_start (gimage, GIMAGE_MOD_UNDO); + + /* Relax the floating selection */ + if (floating_layer) + floating_sel_relax (floating_layer, TRUE); + + /* Push the image size to the stack */ + undo_push_gimage_mod (gimage); + + /* Set the new width and height */ + old_width = gimage->width; + old_height = gimage->height; + gimage->width = new_width; + gimage->height = new_height; + + /* Scale all channels */ + list = gimage->channels; + while (list) + { + channel = (Channel *) list->data; + channel_scale (channel, new_width, new_height); + list = g_slist_next (list); + } + + /* Don't forget the selection mask! */ + channel_scale (gimage->selection_mask, new_width, new_height); + gimage_mask_invalidate (gimage); + + /* Scale all layers */ + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + + layer_width = (new_width * drawable_width (GIMP_DRAWABLE(layer))) / old_width; + layer_height = (new_height * drawable_height (GIMP_DRAWABLE(layer))) / old_height; + layer_scale (layer, layer_width, layer_height, FALSE); + list = g_slist_next (list); + } + + /* Make sure the projection matches the gimage size */ + gimp_image_projection_realloc (gimage); + + /* Rigor the floating selection */ + if (floating_layer) + floating_sel_rigor (floating_layer, TRUE); + + gtk_signal_emit (GTK_OBJECT (gimage), gimp_image_signals[RESIZE]); +} + + +GimpImage * +gimp_image_get_named (char *name) +{ + GSList *tmp = image_list; + GimpImage *gimage; + char *str; + + while (tmp) + { + gimage = tmp->data; + str = prune_filename (gimp_image_filename (gimage)); + if (strcmp (str, name) == 0) + return gimage; + + tmp = g_slist_next (tmp); + } + + return NULL; +} + + + +TileManager * +gimp_image_shadow (GimpImage *gimage, int width, int height, int bpp) +{ + if (gimage->shadow && + ((width != gimage->shadow->levels[0].width) || + (height != gimage->shadow->levels[0].height) || + (bpp != gimage->shadow->levels[0].bpp))) + gimp_image_free_shadow (gimage); + else if (gimage->shadow) + return gimage->shadow; + + gimp_image_allocate_shadow (gimage, width, height, bpp); + + return gimage->shadow; +} + + +void +gimp_image_free_shadow (GimpImage *gimage) +{ + /* Free the shadow buffer from the specified gimage if it exists */ + if (gimage->shadow) + tile_manager_destroy (gimage->shadow); + + gimage->shadow = NULL; +} + + +static void +gimp_image_destroy (GtkObject *object) +{ + GimpImage* gimage=GIMP_IMAGE(object); + gimp_image_free_projection (gimage); + gimp_image_free_shadow (gimage); + + if (gimage->cmap) + g_free (gimage->cmap); + + if (gimage->has_filename) + g_free (gimage->filename); + + gimp_image_free_layers (gimage); + gimp_image_free_channels (gimage); + channel_delete (gimage->selection_mask); +} + +void +gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, int mode, + /* alternative to using drawable tiles as src1: */ + TileManager *src1_tiles, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR, maskPR; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + if (src1_tiles) + pixel_region_init (&src1PR, src1_tiles, x1, y1, (x2 - x1), (y2 - y1), FALSE); + else + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL, + opacity, mode, active, operation); + } + else + combine_regions (&src1PR, src2PR, &destPR, NULL, NULL, + opacity, mode, active, operation); +} + +/* Similar to gimp_image_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimp_image_replace_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimp_image_get_mask (gimage); + + /* configure the active channel array */ + gimp_image_get_active_channels (gimage, drawable, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable)][src2PR->bytes]; + if (operation == -1) + { + g_message ("gimp_image_apply_image sent illegal parameters"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = CLAMP (x, 0, drawable_width (drawable)); + y1 = CLAMP (y, 0, drawable_height (drawable)); + x2 = CLAMP (x + src2PR->w, 0, drawable_width (drawable)); + y2 = CLAMP (y + src2PR->h, 0, drawable_height (drawable)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = CLAMP (x1, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y1 = CLAMP (y1, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + x2 = CLAMP (x2, -offset_x, drawable_width (GIMP_DRAWABLE(mask)) - offset_x); + y2 = CLAMP (y2, -offset_y, drawable_height (GIMP_DRAWABLE(mask)) - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, drawable_data (GIMP_DRAWABLE(mask)), mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE_OPACITY); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + +/* Get rid of these! A "foreground" is an UI concept.. */ + +void +gimp_image_get_foreground (GimpImage *gimage, GimpDrawable *drawable, unsigned char *fg) +{ + unsigned char pfg[3]; + + /* Get the palette color */ + palette_get_foreground (&pfg[0], &pfg[1], &pfg[2]); + + gimp_image_transform_color (gimage, drawable, pfg, fg, RGB); +} + + +void +gimp_image_get_background (GimpImage *gimage, GimpDrawable *drawable, unsigned char *bg) +{ + unsigned char pbg[3]; + + /* Get the palette color */ + palette_get_background (&pbg[0], &pbg[1], &pbg[2]); + + gimp_image_transform_color (gimage, drawable, pbg, bg, RGB); +} + +void +gimp_image_get_color (GimpImage *gimage, int d_type, + unsigned char *rgb, unsigned char *src) +{ + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + map_to_color (0, NULL, src, rgb); + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + map_to_color (1, NULL, src, rgb); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + map_to_color (2, gimage->cmap, src, rgb); + break; + } +} + + +void +gimp_image_transform_color (GimpImage *gimage, GimpDrawable *drawable, + unsigned char *src, unsigned char *dest, int type) +{ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + int d_type; + + d_type = (drawable != NULL) ? drawable_type (drawable) : + gimp_image_base_type_with_alpha (gimage); + + switch (type) + { + case RGB: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Straight copy */ + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* NTSC conversion */ + *dest = INTENSITY (src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[RED_PIX], + src[GREEN_PIX], + src[BLUE_PIX]); + break; + } + break; + case GRAY: + switch (d_type) + { + case RGB_GIMAGE: case RGBA_GIMAGE: + /* Gray to RG&B */ + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + break; + case GRAY_GIMAGE: case GRAYA_GIMAGE: + /* Straight copy */ + *dest = *src; + break; + case INDEXED_GIMAGE: case INDEXEDA_GIMAGE: + /* Least squares method */ + *dest = map_rgb_to_indexed (gimage->cmap, + gimage->num_cols, + gimage->ID, + src[GRAY_PIX], + src[GRAY_PIX], + src[GRAY_PIX]); + break; + } + break; + default: + break; + } +} + +Guide* +gimp_image_add_hguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = HORIZONTAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +Guide* +gimp_image_add_vguide (GimpImage *gimage) +{ + Guide *guide; + + guide = g_new (Guide, 1); + guide->ref_count = 0; + guide->position = -1; + guide->orientation = VERTICAL_GUIDE; + + gimage->guides = g_list_prepend (gimage->guides, guide); + + return guide; +} + +void +gimp_image_add_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_prepend (gimage->guides, guide); +} + +void +gimp_image_remove_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); +} + +void +gimp_image_delete_guide (GimpImage *gimage, + Guide *guide) +{ + gimage->guides = g_list_remove (gimage->guides, guide); + g_free (guide); +} + + +/************************************************************/ +/* Projection functions */ +/************************************************************/ + +static void +project_intensity (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN); +} + + +static void +project_intensity_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (dest, src, dest, mask, NULL, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A); +} + + +static void +project_indexed (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest) +{ + if (! gimage->construct_flag) + initial_region (src, dest, NULL, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED); + else + g_message ("Unable to project indexed image."); +} + + +static void +project_indexed_alpha (GimpImage *gimage, Layer *layer, + PixelRegion *src, PixelRegion *dest, + PixelRegion *mask) +{ + if (! gimage->construct_flag) + initial_region (src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA); + else + combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity, + layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A); +} + + +static void +project_channel (GimpImage *gimage, Channel *channel, + PixelRegion *src, PixelRegion *src2) +{ + int type; + + if (! gimage->construct_flag) + { + type = (channel->show_masked) ? + INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; + initial_region (src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } + else + { + type = (channel->show_masked) ? + COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; + combine_regions (src, src2, src, NULL, channel->col, channel->opacity, + NORMAL, NULL, type); + } +} + + +/************************************************************/ +/* Layer/Channel functions */ +/************************************************************/ + + +static void +gimp_image_free_layers (GimpImage *gimage) +{ + GSList *list = gimage->layers; + Layer * layer; + + while (list) + { + layer = (Layer *) list->data; + layer_delete (layer); + list = g_slist_next (list); + } + g_slist_free (gimage->layers); + g_slist_free (gimage->layer_stack); +} + + +static void +gimp_image_free_channels (GimpImage *gimage) +{ + GSList *list = gimage->channels; + Channel * channel; + + while (list) + { + channel = (Channel *) list->data; + channel_delete (channel); + list = g_slist_next (list); + } + g_slist_free (gimage->channels); +} + + +static void +gimp_image_construct_layers (GimpImage *gimage, int x, int y, int w, int h) +{ + Layer * layer; + int x1, y1, x2, y2; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + int off_x, off_y; + + /* composite the floating selection if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_composite (layer, x, y, w, h, FALSE); + + /* Note added by Raph Levien, 27 Jan 1998 + + This looks it was intended as an optimization, but it seems to + have correctness problems. In particular, if all channels are + turned off, the screen simply does not update the projected + image. It should be black. Turning off this optimization seems to + restore correct behavior. At some future point, it may be + desirable to turn the optimization back on. + + */ +#if 0 + /* If all channels are not visible, simply return */ + switch (gimp_image_base_type (gimage)) + { + case RGB: + if (! gimp_image_get_component_visible (gimage, Red) && + ! gimp_image_get_component_visible (gimage, Green) && + ! gimp_image_get_component_visible (gimage, Blue)) + return; + break; + case GRAY: + if (! gimp_image_get_component_visible (gimage, Gray)) + return; + break; + case INDEXED: + if (! gimp_image_get_component_visible (gimage, Indexed)) + return; + break; + } +#endif + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x1 = CLAMP (off_x, x, x + w); + y1 = CLAMP (off_y, y, y + h); + x2 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x, x + w); + y2 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y, y + h); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x1, y1, (x2 - x1), (y2 - y1), TRUE); + + /* If we're showing the layer mask instead of the layer... */ + if (layer->mask && layer->show_mask) + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + copy_gray_to_region (&src2PR, &src1PR); + } + /* Otherwise, normal */ + else + { + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + + if (layer->mask && layer->apply_mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), + (x1 - off_x), (y1 - off_y), + (x2 - x1), (y2 - y1), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * projection image... + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: + /* no mask possible */ + project_intensity (gimage, layer, &src2PR, &src1PR, mask); + break; + case RGBA_GIMAGE: case GRAYA_GIMAGE: + project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + case INDEXED_GIMAGE: + /* no mask possible */ + project_indexed (gimage, layer, &src2PR, &src1PR); + break; + case INDEXEDA_GIMAGE: + project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask); + break; + default: + break; + } + } + gimage->construct_flag = 1; /* something was projected */ + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_construct_channels (GimpImage *gimage, int x, int y, int w, int h) +{ + Channel * channel; + PixelRegion src1PR, src2PR; + GSList *list = gimage->channels; + GSList *reverse_list = NULL; + + /* reverse the channel list */ + while (list) + { + reverse_list = g_slist_prepend (reverse_list, list->data); + list = g_slist_next (list); + } + + while (reverse_list) + { + channel = (Channel *) reverse_list->data; + + if (drawable_visible (GIMP_DRAWABLE(channel))) + { + /* configure the pixel regions */ + pixel_region_init (&src1PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(channel)), x, y, w, h, FALSE); + + project_channel (gimage, channel, &src1PR, &src2PR); + + gimage->construct_flag = 1; + } + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); +} + + +static void +gimp_image_initialize_projection (GimpImage *gimage, int x, int y, int w, int h) +{ + GSList *list; + Layer *layer; + int coverage = 0; + PixelRegion PR; + unsigned char clear[4] = { 0, 0, 0, 0 }; + + /* this function determines whether a visible layer + * provides complete coverage over the image. If not, + * the projection is initialized to transparent + */ + list = gimage->layers; + while (list) + { + int off_x, off_y; + layer = (Layer *) list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if (drawable_visible (GIMP_DRAWABLE(layer)) && + ! layer_has_alpha (layer) && + (off_x <= x) && + (off_y <= y) && + (off_x + drawable_width (GIMP_DRAWABLE(layer)) >= x + w) && + (off_y + drawable_height (GIMP_DRAWABLE(layer)) >= y + h)) + coverage = 1; + + list = g_slist_next (list); + } + + if (!coverage) + { + pixel_region_init (&PR, gimp_image_projection (gimage), x, y, w, h, TRUE); + color_region (&PR, clear); + } +} + + +static void +gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, int *active) +{ + Layer * layer; + int i; + + /* first, blindly copy the gimage active channels */ + for (i = 0; i < MAX_CHANNELS; i++) + active[i] = gimage->active[i]; + + /* If the drawable is a channel (a saved selection, etc.) + * make sure that the alpha channel is not valid + */ + if (drawable_channel (drawable) != NULL) + active[ALPHA_G_PIX] = 0; /* no alpha values in channels */ + else + { + /* otherwise, check whether preserve transparency is + * enabled in the layer and if the layer has alpha + */ + if ((layer = drawable_layer (drawable))) + if (layer_has_alpha (layer) && layer->preserve_trans) + active[drawable_bytes (GIMP_DRAWABLE(layer)) - 1] = 0; + } +} + + + +void +gimp_image_construct (GimpImage *gimage, int x, int y, int w, int h) +{ + /* if the gimage is not flat, construction is necessary. */ + if (! gimp_image_is_flat (gimage)) + { + /* set the construct flag, used to determine if anything + * has been written to the gimage raw image yet. + */ + gimage->construct_flag = 0; + + /* First, determine if the projection image needs to be + * initialized--this is the case when there are no visible + * layers that cover the entire canvas--either because layers + * are offset or only a floating selection is visible + */ + gimp_image_initialize_projection (gimage, x, y, w, h); + + /* call functions which process the list of layers and + * the list of channels + */ + gimp_image_construct_layers (gimage, x, y, w, h); + gimp_image_construct_channels (gimage, x, y, w, h); + } +} + +void +gimp_image_invalidate (GimpImage *gimage, int x, int y, int w, int h, int x1, int y1, + int x2, int y2) +{ + Tile *tile; + TileManager *tm; + int i, j; + int startx, starty; + int endx, endy; + int tilex, tiley; + int flat; + + flat = gimp_image_is_flat (gimage); + tm = gimp_image_projection (gimage); + + startx = x; + starty = y; + endx = x + w; + endy = y + h; + + /* invalidate all tiles which are located outside of the displayed area + * all tiles inside the displayed area are constructed. + */ + for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT))) + for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH))) + { + tile = tile_manager_get_tile (tm, j, i, 0); + + /* invalidate all lower level tiles */ + /*tile_manager_invalidate_tiles (gimp_image_projection (gimage), tile);*/ + + if (! flat) + { + /* check if the tile is outside the bounds */ + if ((MIN ((j + tile->ewidth), x2) - MAX (j, x1)) <= 0) + { + tile->valid = FALSE; + if (j < x1) + startx = MAX (startx, (j + tile->ewidth)); + else + endx = MIN (endx, j); + } + else if (MIN ((i + tile->eheight), y2) - MAX (i, y1) <= 0) + { + tile->valid = FALSE; + if (i < y1) + starty = MAX (starty, (i + tile->eheight)); + else + endy = MIN (endy, i); + } + else + { + /* If the tile is not valid, make sure we get the entire tile + * in the construction extents + */ + if (tile->valid == FALSE) + { + tilex = j - (j % TILE_WIDTH); + tiley = i - (i % TILE_HEIGHT); + + startx = MIN (startx, tilex); + endx = MAX (endx, tilex + tile->ewidth); + starty = MIN (starty, tiley); + endy = MAX (endy, tiley + tile->eheight); + + tile->valid = TRUE; + } + } + } + } + + if (! flat && (endx - startx) > 0 && (endy - starty) > 0) + gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); +} + +void +gimp_image_validate (TileManager *tm, Tile *tile, int level) +{ + GimpImage *gimage; + int x, y; + int w, h; + + /* Get the gimage from the tilemanager */ + gimage = (GimpImage *) tm->user_data; + + /* Find the coordinates of this tile */ + x = TILE_WIDTH * (tile->tile_num % tm->levels[0].ntile_cols); + y = TILE_HEIGHT * (tile->tile_num / tm->levels[0].ntile_cols); + w = tile->ewidth; + h = tile->eheight; + + gimp_image_construct (gimage, x, y, w, h); +} + +int +gimp_image_get_layer_index (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + GSList *layers = gimage->layers; + int index = 0; + + while (layers) + { + layer = (Layer *) layers->data; + if (layer == layer_arg) + return index; + + index++; + layers = g_slist_next (layers); + } + + return -1; +} + +int +gimp_image_get_channel_index (GimpImage *gimage, Channel *channel_ID) +{ + Channel *channel; + GSList *channels = gimage->channels; + int index = 0; + + while (channels) + { + channel = (Channel *) channels->data; + if (channel == channel_ID) + return index; + + index++; + channels = g_slist_next (channels); + } + + return -1; +} + + +Layer * +gimp_image_get_active_layer (GimpImage *gimage) +{ + return gimage->active_layer; +} + + +Channel * +gimp_image_get_active_channel (GimpImage *gimage) +{ + return gimage->active_channel; +} + + +int +gimp_image_get_component_active (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->active[RED_PIX]; break; + case Green: return gimage->active[GREEN_PIX]; break; + case Blue: return gimage->active[BLUE_PIX]; break; + case Gray: return gimage->active[GRAY_PIX]; break; + case Indexed: return gimage->active[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +int +gimp_image_get_component_visible (GimpImage *gimage, ChannelType type) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: return gimage->visible[RED_PIX]; break; + case Green: return gimage->visible[GREEN_PIX]; break; + case Blue: return gimage->visible[BLUE_PIX]; break; + case Gray: return gimage->visible[GRAY_PIX]; break; + case Indexed: return gimage->visible[INDEXED_PIX]; break; + default: return 0; break; + } +} + + +Channel * +gimp_image_get_mask (GimpImage *gimage) +{ + return gimage->selection_mask; +} + + +int +gimp_image_layer_boundary (GimpImage *gimage, BoundSeg **segs, int *num_segs) +{ + Layer *layer; + + /* The second boundary corresponds to the active layer's + * perimeter... + */ + if ((layer = gimage->active_layer)) + { + *segs = layer_boundary (layer, num_segs); + return 1; + } + else + { + *segs = NULL; + *num_segs = 0; + return 0; + } +} + + +Layer * +gimp_image_set_active_layer (GimpImage *gimage, Layer * layer) +{ + + /* First, find the layer in the gimage + * If it isn't valid, find the first layer that is + */ + if (gimp_image_get_layer_index (gimage, layer) == -1) + { + if (! gimage->layers) + return NULL; + layer = (Layer *) gimage->layers->data; + } + + if (! layer) + return NULL; + + /* Configure the layer stack to reflect this change */ + gimage->layer_stack = g_slist_remove (gimage->layer_stack, (void *) layer); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, (void *) layer); + + /* invalidate the selection boundary because of a layer modification */ + layer_invalidate_boundary (layer); + + /* Set the active layer */ + gimage->active_layer = layer; + gimage->active_channel = NULL; + + /* return the layer */ + return layer; +} + + +Channel * +gimp_image_set_active_channel (GimpImage *gimage, Channel * channel) +{ + + /* Not if there is a floating selection */ + if (gimp_image_floating_sel (gimage)) + return NULL; + + /* First, find the channel + * If it doesn't exist, find the first channel that does + */ + if (! channel) + { + if (! gimage->channels) + { + gimage->active_channel = NULL; + return NULL; + } + channel = (Channel *) gimage->channels->data; + } + + /* Set the active channel */ + gimage->active_channel = channel; + + /* return the channel */ + return channel; +} + + +Channel * +gimp_image_unset_active_channel (GimpImage *gimage) +{ + Channel *channel; + + /* make sure there is an active channel */ + if (! (channel = gimage->active_channel)) + return NULL; + + /* Set the active channel */ + gimage->active_channel = NULL; + + return channel; +} + + +void +gimp_image_set_component_active (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->active[RED_PIX] = value; break; + case Green: gimage->active[GREEN_PIX] = value; break; + case Blue: gimage->active[BLUE_PIX] = value; break; + case Gray: gimage->active[GRAY_PIX] = value; break; + case Indexed: gimage->active[INDEXED_PIX] = value; break; + case Auxillary: break; + } + + /* If there is an active channel and we mess with the components, + * the active channel gets unset... + */ + if (type != Auxillary) + gimp_image_unset_active_channel (gimage); +} + + +void +gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, int value) +{ + /* No sanity checking here... */ + switch (type) + { + case Red: gimage->visible[RED_PIX] = value; break; + case Green: gimage->visible[GREEN_PIX] = value; break; + case Blue: gimage->visible[BLUE_PIX] = value; break; + case Gray: gimage->visible[GRAY_PIX] = value; break; + case Indexed: gimage->visible[INDEXED_PIX] = value; break; + default: break; + } +} + + +Layer * +gimp_image_pick_correlate_layer (GimpImage *gimage, int x, int y) +{ + Layer *layer; + GSList *list; + + list = gimage->layers; + while (list) + { + layer = (Layer *) list->data; + if (layer_pick_correlate (layer, x, y)) + return layer; + + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_raise_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *prev_layer; + GSList *list; + GSList *prev; + int x1, y1, x2, y2; + int index = -1; + int off_x, off_y; + int off2_x, off2_y; + + list = gimage->layers; + prev = NULL; prev_layer = NULL; + + while (list) + { + layer = (Layer *) list->data; + if (prev) + prev_layer = (Layer *) prev->data; + + if (layer == layer_arg) + { + /* We can only raise a layer if it has an alpha channel && + * If it's not already the top layer + */ + if (prev && layer_has_alpha (layer) && layer_has_alpha (prev_layer)) + { + list->data = prev_layer; + prev->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(prev_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(prev_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(prev_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, x2, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return prev_layer; + } + else + { + g_message ("Layer cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Layer * +gimp_image_lower_layer (GimpImage *gimage, Layer *layer_arg) +{ + Layer *layer; + Layer *next_layer; + GSList *list; + GSList *next; + int x1, y1, x2, y2; + int index = 0; + int off_x, off_y; + int off2_x, off2_y; + + next_layer = NULL; + + list = gimage->layers; + + while (list) + { + layer = (Layer *) list->data; + next = g_slist_next (list); + + if (next) + next_layer = (Layer *) next->data; + index++; + + if (layer == layer_arg) + { + /* We can only lower a layer if it has an alpha channel && + * The layer beneath it has an alpha channel && + * If it's not already the bottom layer + */ + if (next && layer_has_alpha (layer) && layer_has_alpha (next_layer)) + { + list->data = next_layer; + next->data = layer; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + drawable_offsets (GIMP_DRAWABLE(next_layer), &off2_x, &off2_y); + + /* calculate minimum area to update */ + x1 = MAX (off_x, off2_x); + y1 = MAX (off_y, off2_y); + x2 = MIN (off_x + drawable_width (GIMP_DRAWABLE(layer)), + off2_x + drawable_width (GIMP_DRAWABLE(next_layer))); + y2 = MIN (off_y + drawable_height (GIMP_DRAWABLE(layer)), + off2_y + drawable_height (GIMP_DRAWABLE(next_layer))); + if ((x2 - x1) > 0 && (y2 - y1) > 0) + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + x1, y1, x2-x1, y2-y1); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return next_layer; + } + else + { + g_message ("Layer cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Layer * +gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + if (merge_list && merge_list->next) + { + layer = gimp_image_merge_layers (gimage, merge_list, merge_type); + g_slist_free (merge_list); + return layer; + } + else + { + g_message ("There are not enough visible layers for a merge.\nThere must be at least two."); + g_slist_free (merge_list); + return NULL; + } +} + + +Layer * +gimp_image_flatten (GimpImage *gimage) +{ + GSList *layer_list; + GSList *merge_list = NULL; + Layer *layer; + + layer_list = gimage->layers; + while (layer_list) + { + layer = (Layer *) layer_list->data; + if (drawable_visible (GIMP_DRAWABLE(layer))) + merge_list = g_slist_append (merge_list, layer); + + layer_list = g_slist_next (layer_list); + } + + layer = gimp_image_merge_layers (gimage, merge_list, FlattenImage); + g_slist_free (merge_list); + return layer; +} + + +Layer * +gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) +{ + GSList *reverse_list = NULL; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + Layer *merge_layer; + Layer *layer; + Layer *bottom; + unsigned char bg[4] = {0, 0, 0, 0}; + int type; + int count; + int x1, y1, x2, y2; + int x3, y3, x4, y4; + int operation; + int position; + int active[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + layer = NULL; + type = RGBA_GIMAGE; + x1 = y1 = x2 = y2 = 0; + bottom = NULL; + + /* Get the layer extents */ + count = 0; + while (merge_list) + { + layer = (Layer *) merge_list->data; + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + switch (merge_type) + { + case ExpandAsNecessary: + case ClipToImage: + if (!count) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + else + { + if (off_x < x1) + x1 = off_x; + if (off_y < y1) + y1 = off_y; + if ((off_x + drawable_width (GIMP_DRAWABLE(layer))) > x2) + x2 = (off_x + drawable_width (GIMP_DRAWABLE(layer))); + if ((off_y + drawable_height (GIMP_DRAWABLE(layer))) > y2) + y2 = (off_y + drawable_height (GIMP_DRAWABLE(layer))); + } + if (merge_type == ClipToImage) + { + x1 = CLAMP (x1, 0, gimage->width); + y1 = CLAMP (y1, 0, gimage->height); + x2 = CLAMP (x2, 0, gimage->width); + y2 = CLAMP (y2, 0, gimage->height); + } + break; + case ClipToBottomLayer: + if (merge_list->next == NULL) + { + x1 = off_x; + y1 = off_y; + x2 = off_x + drawable_width (GIMP_DRAWABLE(layer)); + y2 = off_y + drawable_height (GIMP_DRAWABLE(layer)); + } + break; + case FlattenImage: + if (merge_list->next == NULL) + { + x1 = 0; + y1 = 0; + x2 = gimage->width; + y2 = gimage->height; + } + break; + } + + count ++; + reverse_list = g_slist_prepend (reverse_list, layer); + merge_list = g_slist_next (merge_list); + } + + if ((x2 - x1) == 0 || (y2 - y1) == 0) + return NULL; + + /* Start a merge undo group */ + undo_push_group_start (gimage, LAYER_MERGE_UNDO); + + if (merge_type == FlattenImage || + drawable_type (GIMP_DRAWABLE (layer)) == INDEXED_GIMAGE) + { + switch (gimp_image_base_type (gimage)) + { + case RGB: type = RGB_GIMAGE; break; + case GRAY: type = GRAY_GIMAGE; break; + case INDEXED: type = INDEXED_GIMAGE; break; + } + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + type, drawable_name (GIMP_DRAWABLE(layer)), OPAQUE_OPACITY, NORMAL_MODE); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* get the background for compositing */ + gimp_image_get_background (gimage, GIMP_DRAWABLE(merge_layer), bg); + + /* init the pixel region */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, gimage->width, gimage->height, TRUE); + + /* set the region to the background color */ + color_region (&src1PR, bg); + + position = 0; + } + else + { + /* The final merged layer inherits the attributes of the bottomost layer, + * with a notable exception: The resulting layer has an alpha channel + * whether or not the original did + */ + merge_layer = layer_new (gimage->ID, (x2 - x1), (y2 - y1), + drawable_type_with_alpha (GIMP_DRAWABLE(layer)), + drawable_name (GIMP_DRAWABLE(layer)), + layer->opacity, layer->mode); + + if (!merge_layer) { + g_message ("gimp_image_merge_layers: could not allocate merge layer"); + return NULL; + } + + GIMP_DRAWABLE(merge_layer)->offset_x = x1; + GIMP_DRAWABLE(merge_layer)->offset_y = y1; + + /* Set the layer to transparent */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), 0, 0, (x2 - x1), (y2 - y1), TRUE); + + /* set the region to 0's */ + color_region (&src1PR, bg); + + /* Find the index in the layer list of the bottom layer--we need this + * in order to add the final, merged layer to the layer list correctly + */ + layer = (Layer *) reverse_list->data; + position = g_slist_length (gimage->layers) - gimp_image_get_layer_index (gimage, layer); + + /* set the mode of the bottom layer to normal so that the contents + * aren't lost when merging with the all-alpha merge_layer + * Keep a pointer to it so that we can set the mode right after it's been + * merged so that undo works correctly. + */ + layer -> mode =NORMAL; + bottom = layer; + + } + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (GIMP_DRAWABLE(merge_layer))][drawable_bytes (GIMP_DRAWABLE(layer))]; + if (operation == -1) + { + g_message ("gimp_image_merge_layers attempting to merge incompatible layers\n"); + return NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + x3 = CLAMP (off_x, x1, x2); + y3 = CLAMP (off_y, y1, y2); + x4 = CLAMP (off_x + drawable_width (GIMP_DRAWABLE(layer)), x1, x2); + y4 = CLAMP (off_y + drawable_height (GIMP_DRAWABLE(layer)), y1, y2); + + /* configure the pixel regions */ + pixel_region_init (&src1PR, drawable_data (GIMP_DRAWABLE(merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); + pixel_region_init (&src2PR, drawable_data (GIMP_DRAWABLE(layer)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + + if (layer->mask) + { + pixel_region_init (&maskPR, drawable_data (GIMP_DRAWABLE(layer->mask)), (x3 - off_x), (y3 - off_y), + (x4 - x3), (y4 - y3), FALSE); + mask = &maskPR; + } + else + mask = NULL; + + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, + layer->opacity, layer->mode, active, operation); + + gimp_image_remove_layer (gimage, layer); + reverse_list = g_slist_next (reverse_list); + } + + /* Save old mode in undo */ + if (bottom) + bottom -> mode = merge_layer -> mode; + + g_slist_free (reverse_list); + + /* if the type is flatten, remove all the remaining layers */ + if (merge_type == FlattenImage) + { + merge_list = gimage->layers; + while (merge_list) + { + layer = (Layer *) merge_list->data; + merge_list = g_slist_next (merge_list); + gimp_image_remove_layer (gimage, layer); + } + + gimp_image_add_layer (gimage, merge_layer, position); + } + else + { + /* Add the layer to the gimage */ + gimp_image_add_layer (gimage, merge_layer, (g_slist_length (gimage->layers) - position + 1)); + } + + /* End the merge undo group */ + undo_push_group_end (gimage); + + /* Update the gimage */ + GIMP_DRAWABLE(merge_layer)->visible = TRUE; + + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[RESTRUCTURE]); + + drawable_update (GIMP_DRAWABLE(merge_layer), 0, 0, drawable_width (GIMP_DRAWABLE(merge_layer)), drawable_height (GIMP_DRAWABLE(merge_layer))); + + return merge_layer; +} + + +Layer * +gimp_image_add_layer (GimpImage *gimage, Layer *float_layer, int position) +{ + LayerUndo * lu; + + if (GIMP_DRAWABLE(float_layer)->gimage_ID != 0 && + GIMP_DRAWABLE(float_layer)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_layer: attempt to add layer to wrong image"); + return NULL; + } + + { + GSList *ll = gimage->layers; + while (ll) + { + if (ll->data == float_layer) + { + g_message ("gimp_image_add_layer: trying to add layer to image twice"); + return NULL; + } + ll = g_slist_next(ll); + } + } + + /* Prepare a layer undo and push it */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = float_layer; + lu->prev_position = 0; + lu->prev_layer = gimage->active_layer; + lu->undo_type = 0; + undo_push_layer (gimage, lu); + + /* If the layer is a floating selection, set the ID */ + if (layer_is_floating_sel (float_layer)) + gimage->floating_sel = float_layer; + + /* let the layer know about the gimage */ + GIMP_DRAWABLE(float_layer)->gimage_ID = gimage->ID; + + /* add the layer to the list at the specified position */ + if (position == -1) + position = gimp_image_get_layer_index (gimage, gimage->active_layer); + if (position != -1) + { + /* If there is a floating selection (and this isn't it!), + * make sure the insert position is greater than 0 + */ + if (gimp_image_floating_sel (gimage) && (gimage->floating_sel != float_layer) && position == 0) + position = 1; + gimage->layers = g_slist_insert (gimage->layers, layer_ref (float_layer), position); + } + else + gimage->layers = g_slist_prepend (gimage->layers, layer_ref (float_layer)); + gimage->layer_stack = g_slist_prepend (gimage->layer_stack, float_layer); + + /* notify the layers dialog of the currently active layer */ + gimp_image_set_active_layer (gimage, float_layer); + + /* update the new layer's area */ + drawable_update (GIMP_DRAWABLE(float_layer), 0, 0, drawable_width (GIMP_DRAWABLE(float_layer)), drawable_height (GIMP_DRAWABLE(float_layer))); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return float_layer; +} + + +Layer * +gimp_image_remove_layer (GimpImage *gimage, Layer * layer) +{ + LayerUndo *lu; + int off_x, off_y; + + if (layer) + { + /* Prepare a layer undo--push it at the end */ + lu = (LayerUndo *) g_malloc (sizeof (LayerUndo)); + lu->layer = layer; + lu->prev_position = gimp_image_get_layer_index (gimage, layer); + lu->prev_layer = layer; + lu->undo_type = 1; + + gimage->layers = g_slist_remove (gimage->layers, layer); + gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer); + + /* If this was the floating selection, reset the fs pointer */ + if (gimage->floating_sel == layer) + { + gimage->floating_sel = NULL; + + floating_sel_reset (layer); + } + if (gimage->active_layer == layer) + { + if (gimage->layers) + gimage->active_layer = (Layer *) gimage->layer_stack->data; + else + gimage->active_layer = NULL; + } + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + + /* Push the layer undo--It is important it goes here since layer might + * be immediately destroyed if the undo push fails + */ + undo_push_layer (gimage, lu); + + /* invalidate the composite preview */ + gimp_image_invalidate_preview (gimage); + + return NULL; + } + else + return NULL; +} + + +LayerMask * +gimp_image_add_layer_mask (GimpImage *gimage, Layer *layer, LayerMask *mask) +{ + LayerMaskUndo *lmu; + char *error = NULL;; + + if (layer->mask != NULL) + error = "Unable to add a layer mask since\nthe layer already has one."; + if (drawable_indexed (GIMP_DRAWABLE(layer))) + error = "Unable to add a layer mask to a\nlayer in an indexed image."; + if (! layer_has_alpha (layer)) + error = "Cannot add layer mask to a layer\nwith no alpha channel."; + if (drawable_width (GIMP_DRAWABLE(layer)) != drawable_width (GIMP_DRAWABLE(mask)) || drawable_height (GIMP_DRAWABLE(layer)) != drawable_height (GIMP_DRAWABLE(mask))) + error = "Cannot add layer mask of different dimensions than specified layer."; + + if (error) + { + g_message (error); + return NULL; + } + + layer_add_mask (layer, mask); + + /* Prepare a layer undo and push it */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = mask; + lmu->undo_type = 0; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + undo_push_layer_mask (gimage, lmu); + + + return mask; +} + + +Channel * +gimp_image_remove_layer_mask (GimpImage *gimage, Layer *layer, int mode) +{ + LayerMaskUndo *lmu; + int off_x, off_y; + + if (! (layer) ) + return NULL; + if (! layer->mask) + return NULL; + + /* Start an undo group */ + undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); + + /* Prepare a layer mask undo--push it below */ + lmu = (LayerMaskUndo *) g_malloc (sizeof (LayerMaskUndo)); + lmu->layer = layer; + lmu->mask = layer->mask; + lmu->undo_type = 1; + lmu->mode = mode; + lmu->apply_mask = layer->apply_mask; + lmu->edit_mask = layer->edit_mask; + lmu->show_mask = layer->show_mask; + + layer_apply_mask (layer, mode); + + /* Push the undo--Important to do it here, AFTER the call + * to layer_apply_mask, in case the undo push fails and the + * mask is delete : NULL)d + */ + undo_push_layer_mask (gimage, lmu); + + /* end the undo group */ + undo_push_group_end (gimage); + + /* If the layer mode is discard, update the layer--invalidate gimage also */ + if (mode == DISCARD) + { + gimp_image_invalidate_preview (gimage); + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[REPAINT], + off_x, off_y, + drawable_width (GIMP_DRAWABLE(layer)), + drawable_height (GIMP_DRAWABLE(layer))); + } + + return NULL; +} + + +Channel * +gimp_image_raise_channel (GimpImage *gimage, Channel * channel_arg) +{ + Channel *channel; + Channel *prev_channel; + GSList *list; + GSList *prev; + int index = -1; + + list = gimage->channels; + prev = NULL; + prev_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + if (prev) + prev_channel = (Channel *) prev->data; + + if (channel == channel_arg) + { + if (prev) + { + list->data = prev_channel; + prev->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + return prev_channel; + } + else + { + g_message ("Channel cannot be raised any further"); + return NULL; + } + } + + prev = list; + index++; + list = g_slist_next (list); + } + + return NULL; +} + + +Channel * +gimp_image_lower_channel (GimpImage *gimage, Channel *channel_arg) +{ + Channel *channel; + Channel *next_channel; + GSList *list; + GSList *next; + int index = 0; + + list = gimage->channels; + next_channel = NULL; + + while (list) + { + channel = (Channel *) list->data; + next = g_slist_next (list); + + if (next) + next_channel = (Channel *) next->data; + index++; + + if (channel == channel_arg) + { + if (next) + { + list->data = next_channel; + next->data = channel; + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return next_channel; + } + else + { + g_message ("Channel cannot be lowered any further"); + return NULL; + } + } + + list = next; + } + + return NULL; +} + + +Channel * +gimp_image_add_channel (GimpImage *gimage, Channel *channel, int position) +{ + ChannelUndo * cu; + + if (GIMP_DRAWABLE(channel)->gimage_ID != 0 && + GIMP_DRAWABLE(channel)->gimage_ID != gimage->ID) + { + g_message ("gimp_image_add_channel: attempt to add channel to wrong image"); + return NULL; + } + + { + GSList *cc = gimage->channels; + while (cc) + { + if (cc->data == channel) + { + g_message ("gimp_image_add_channel: trying to add channel to image twice"); + return NULL; + } + cc = g_slist_next (cc); + } + } + + + /* Prepare a channel undo and push it */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = 0; + cu->prev_channel = gimage->active_channel; + cu->undo_type = 0; + undo_push_channel (gimage, cu); + + /* add the channel to the list */ + gimage->channels = g_slist_prepend (gimage->channels, channel_ref (channel)); + + /* notify this gimage of the currently active channel */ + gimp_image_set_active_channel (gimage, channel); + + /* if channel is visible, update the image */ + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + return channel; +} + + +Channel * +gimp_image_remove_channel (GimpImage *gimage, Channel *channel) +{ + ChannelUndo * cu; + + if (channel) + { + /* Prepare a channel undo--push it below */ + cu = (ChannelUndo *) g_malloc (sizeof (ChannelUndo)); + cu->channel = channel; + cu->prev_position = gimp_image_get_channel_index (gimage, channel); + cu->prev_channel = gimage->active_channel; + cu->undo_type = 1; + + gimage->channels = g_slist_remove (gimage->channels, channel); + + if (gimage->active_channel == channel) + { + if (gimage->channels) + gimage->active_channel = (((Channel *) gimage->channels->data)); + else + gimage->active_channel = NULL; + } + + if (drawable_visible (GIMP_DRAWABLE(channel))) + drawable_update (GIMP_DRAWABLE(channel), 0, 0, drawable_width (GIMP_DRAWABLE(channel)), drawable_height (GIMP_DRAWABLE(channel))); + + /* Important to push the undo here in case the push fails */ + undo_push_channel (gimage, cu); + + return channel; + } + else + return NULL; +} + + +/************************************************************/ +/* Access functions */ +/************************************************************/ + +int +gimp_image_is_flat (GimpImage *gimage) +{ + Layer *layer; + int ac_visible = TRUE; + int flat = TRUE; + int off_x, off_y; + + /* Are there no layers? */ + if (gimp_image_is_empty (gimage)) + flat = FALSE; + /* Is there more than one layer? */ + else if (gimage->layers->next) + flat = FALSE; + else + { + /* determine if all channels are visible */ + int a, b; + + layer = gimage->layers->data; + a = layer_has_alpha (layer) ? drawable_bytes (GIMP_DRAWABLE(layer)) - 1 : drawable_bytes (GIMP_DRAWABLE(layer)); + for (b = 0; b < a; b++) + if (gimage->visible[b] == FALSE) + ac_visible = FALSE; + + /* What makes a flat image? + * 1) the solitary layer is exactly gimage-sized and placed + * 2) no layer mask + * 3) opacity == OPAQUE_OPACITY + * 4) all channels must be visible + */ + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + if ((drawable_width (GIMP_DRAWABLE(layer)) != gimage->width) || + (drawable_height (GIMP_DRAWABLE(layer)) != gimage->height) || + (off_x != 0) || + (off_y != 0) || + (layer->mask != NULL) || + (layer->opacity != OPAQUE_OPACITY) || + (ac_visible == FALSE)) + flat = FALSE; + } + + /* Are there any channels? */ + if (gimage->channels) + flat = FALSE; + + /* AUGH! This is supposed to be a _predicate_ function */ + if (gimage->flat != flat) + { + if (flat) + gimp_image_free_projection (gimage); + else + gimp_image_allocate_projection (gimage); + gimage->flat=flat; + gtk_signal_emit(GTK_OBJECT(gimage), + gimp_image_signals[RESTRUCTURE]); + } + + + return gimage->flat; +} + +int +gimp_image_is_empty (GimpImage *gimage) +{ + return (! gimage->layers); +} + +GimpDrawable * +gimp_image_active_drawable (GimpImage *gimage) +{ + Layer *layer; + + /* If there is an active channel (a saved selection, etc.), + * we ignore the active layer + */ + if (gimage->active_channel != NULL) + return GIMP_DRAWABLE (gimage->active_channel); + else if (gimage->active_layer != NULL) + { + layer = gimage->active_layer; + if (layer->mask && layer->edit_mask) + return GIMP_DRAWABLE(layer->mask); + else + return GIMP_DRAWABLE(layer); + } + else + return NULL; +} + +int +gimp_image_base_type (GimpImage *gimage) +{ + return gimage->base_type; +} + +int +gimp_image_base_type_with_alpha (GimpImage *gimage) +{ + switch (gimage->base_type) + { + case RGB: + return RGBA_GIMAGE; + case GRAY: + return GRAYA_GIMAGE; + case INDEXED: + return INDEXEDA_GIMAGE; + } + return RGB_GIMAGE; +} + +char * +gimp_image_filename (GimpImage *gimage) +{ + if (gimage->has_filename) + return gimage->filename; + else + return "Untitled"; +} + +int +gimp_image_enable_undo (GimpImage *gimage) +{ + /* Free all undo steps as they are now invalidated */ + undo_free (gimage); + + gimage->undo_on = TRUE; + + return TRUE; +} + +int +gimp_image_disable_undo (GimpImage *gimage) +{ + gimage->undo_on = FALSE; + return TRUE; +} + +int +gimp_image_dirty (GimpImage *gimage) +{ + if (gimage->dirty < 0) + gimage->dirty = 2; + else + gimage->dirty ++; + gtk_signal_emit(GTK_OBJECT(gimage), gimp_image_signals[DIRTY]); + + return gimage->dirty; +} + +int +gimp_image_clean (GimpImage *gimage) +{ + if (gimage->dirty <= 0) + gimage->dirty = 0; + else + gimage->dirty --; + return gimage->dirty; +} + +void +gimp_image_clean_all (GimpImage *gimage) +{ + gimage->dirty = 0; +} + +Layer * +gimp_image_floating_sel (GimpImage *gimage) +{ + if (gimage->floating_sel == NULL) + return NULL; + else return gimage->floating_sel; +} + +unsigned char * +gimp_image_cmap (GimpImage *gimage) +{ + return drawable_cmap (gimp_image_active_drawable (gimage)); +} + + +/************************************************************/ +/* Projection access functions */ +/************************************************************/ + +TileManager * +gimp_image_projection (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the data of the + * first layer...Otherwise, we'll pass back the projection + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = gimage->active_layer)) + return drawable_data (GIMP_DRAWABLE(layer)); + else + return NULL; + } + else + { + if ((gimage->projection->levels[0].width != gimage->width) || + (gimage->projection->levels[0].height != gimage->height)) + gimp_image_allocate_projection (gimage); + + return gimage->projection; + } +} + +int +gimp_image_projection_type (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the type of the + * first layer...Otherwise, we'll pass back the proj_type + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_type (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_type; +} + +int +gimp_image_projection_bytes (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, we simply want the bytes in the + * first layer...Otherwise, we'll pass back the proj_bytes + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return drawable_bytes (GIMP_DRAWABLE(layer)); + else + return -1; + } + else + return gimage->proj_bytes; +} + +int +gimp_image_projection_opacity (GimpImage *gimage) +{ + Layer * layer; + + /* If the gimage is flat, return the opacity of the active layer + * Otherwise, we'll pass back OPAQUE_OPACITY + */ + if (gimp_image_is_flat (gimage)) + { + if ((layer = (gimage->active_layer))) + return layer->opacity; + else + return OPAQUE_OPACITY; + } + else + return OPAQUE_OPACITY; +} + +void +gimp_image_projection_realloc (GimpImage *gimage) +{ + if (! gimp_image_is_flat (gimage)) + gimp_image_allocate_projection (gimage); +} + +/************************************************************/ +/* Composition access functions */ +/************************************************************/ + +TileManager * +gimp_image_composite (GimpImage *gimage) +{ + return gimp_image_projection (gimage); +} + +int +gimp_image_composite_type (GimpImage *gimage) +{ + return gimp_image_projection_type (gimage); +} + +int +gimp_image_composite_bytes (GimpImage *gimage) +{ + return gimp_image_projection_bytes (gimage); +} + +static TempBuf * +gimp_image_construct_composite_preview (GimpImage *gimage, int width, int height) +{ + Layer * layer; + PixelRegion src1PR, src2PR, maskPR; + PixelRegion * mask; + TempBuf *comp; + TempBuf *layer_buf; + TempBuf *mask_buf; + GSList *list = gimage->layers; + GSList *reverse_list = NULL; + double ratio; + int x, y, w, h; + int x1, y1, x2, y2; + int bytes; + int construct_flag; + int visible[MAX_CHANNELS] = {1, 1, 1, 1}; + int off_x, off_y; + + ratio = (double) width / (double) gimage->width; + + switch (gimp_image_base_type (gimage)) + { + case RGB: + case INDEXED: + bytes = 4; + break; + case GRAY: + bytes = 2; + break; + default: + bytes = 0; + break; + } + + /* The construction buffer */ + comp = temp_buf_new (width, height, bytes, 0, 0, NULL); + memset (temp_buf_data (comp), 0, comp->width * comp->height * comp->bytes); + + while (list) + { + layer = (Layer *) list->data; + + /* only add layers that are visible and not floating selections to the list */ + if (!layer_is_floating_sel (layer) && drawable_visible (GIMP_DRAWABLE(layer))) + reverse_list = g_slist_prepend (reverse_list, layer); + + list = g_slist_next (list); + } + + construct_flag = 0; + + while (reverse_list) + { + layer = (Layer *) reverse_list->data; + + drawable_offsets (GIMP_DRAWABLE(layer), &off_x, &off_y); + + x = (int) (ratio * off_x + 0.5); + y = (int) (ratio * off_y + 0.5); + w = (int) (ratio * drawable_width (GIMP_DRAWABLE(layer)) + 0.5); + h = (int) (ratio * drawable_height (GIMP_DRAWABLE(layer)) + 0.5); + + x1 = CLAMP (x, 0, width); + y1 = CLAMP (y, 0, height); + x2 = CLAMP (x + w, 0, width); + y2 = CLAMP (y + h, 0, height); + + src1PR.bytes = comp->bytes; + src1PR.x = x1; src1PR.y = y1; + src1PR.w = (x2 - x1); + src1PR.h = (y2 - y1); + src1PR.rowstride = comp->width * src1PR.bytes; + src1PR.data = temp_buf_data (comp) + y1 * src1PR.rowstride + x1 * src1PR.bytes; + + layer_buf = layer_preview (layer, w, h); + src2PR.bytes = layer_buf->bytes; + src2PR.w = src1PR.w; src2PR.h = src1PR.h; + src2PR.x = src1PR.x; src2PR.y = src1PR.y; + src2PR.rowstride = layer_buf->width * src2PR.bytes; + src2PR.data = temp_buf_data (layer_buf) + + (y1 - y) * src2PR.rowstride + (x1 - x) * src2PR.bytes; + + if (layer->mask && layer->apply_mask) + { + mask_buf = layer_mask_preview (layer, w, h); + maskPR.bytes = mask_buf->bytes; + maskPR.rowstride = mask_buf->width; + maskPR.data = mask_buf_data (mask_buf) + + (y1 - y) * maskPR.rowstride + (x1 - x) * maskPR.bytes; + mask = &maskPR; + } + else + mask = NULL; + + /* Based on the type of the layer, project the layer onto the + * composite preview... + * Indexed images are actually already converted to RGB and RGBA, + * so just project them as if they were type "intensity" + * Send in all TRUE for visible since that info doesn't matter for previews + */ + switch (drawable_type (GIMP_DRAWABLE(layer))) + { + case RGB_GIMAGE: case GRAY_GIMAGE: case INDEXED_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN); + break; + + case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: + if (! construct_flag) + initial_region (&src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, INITIAL_INTENSITY_ALPHA); + else + combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL, layer->opacity, + layer->mode, visible, COMBINE_INTEN_A_INTEN_A); + break; + + default: + break; + } + + construct_flag = 1; + + reverse_list = g_slist_next (reverse_list); + } + + g_slist_free (reverse_list); + + return comp; +} + +TempBuf * +gimp_image_composite_preview (GimpImage *gimage, ChannelType type, + int width, int height) +{ + int channel; + + switch (type) + { + case Red: channel = RED_PIX; break; + case Green: channel = GREEN_PIX; break; + case Blue: channel = BLUE_PIX; break; + case Gray: channel = GRAY_PIX; break; + case Indexed: channel = INDEXED_PIX; break; + default: return NULL; + } + + /* The easy way */ + if (gimage->comp_preview_valid[channel] && + gimage->comp_preview->width == width && + gimage->comp_preview->height == height) + return gimage->comp_preview; + /* The hard way */ + else + { + if (gimage->comp_preview) + temp_buf_free (gimage->comp_preview); + + /* Actually construct the composite preview from the layer previews! + * This might seem ridiculous, but it's actually the best way, given + * a number of unsavory alternatives. + */ + gimage->comp_preview = gimp_image_construct_composite_preview (gimage, width, height); + gimage->comp_preview_valid[channel] = TRUE; + + return gimage->comp_preview; + } +} + +int +gimp_image_preview_valid (gimage, type) + GimpImage *gimage; + ChannelType type; +{ + switch (type) + { + case Red: return gimage->comp_preview_valid [RED_PIX]; break; + case Green: return gimage->comp_preview_valid [GREEN_PIX]; break; + case Blue: return gimage->comp_preview_valid [BLUE_PIX]; break; + case Gray: return gimage->comp_preview_valid [GRAY_PIX]; break; + case Indexed: return gimage->comp_preview_valid [INDEXED_PIX]; break; + default: return TRUE; + } +} + +void +gimp_image_invalidate_preview (GimpImage *gimage) +{ + Layer *layer; + /* Invalidate the floating sel if it exists */ + if ((layer = gimp_image_floating_sel (gimage))) + floating_sel_invalidate (layer); + + gimage->comp_preview_valid[0] = FALSE; + gimage->comp_preview_valid[1] = FALSE; + gimage->comp_preview_valid[2] = FALSE; +} + +void +gimp_image_invalidate_previews (void) +{ + GSList *tmp = image_list; + GimpImage *gimage; + + while (tmp) + { + gimage = (GimpImage *) tmp->data; + gimp_image_invalidate_preview (gimage); + tmp = g_slist_next (tmp); + } +} + diff --git a/app/gimpimage.h b/app/gimpimage.h new file mode 100644 index 0000000000..bf3c713ffc --- /dev/null +++ b/app/gimpimage.h @@ -0,0 +1,214 @@ +#ifndef __GIMPIMAGE_H__ +#define __GIMPIMAGE_H__ + +#include "gimpimageF.h" + +#include "boundary.h" +#include "drawable.h" +#include "channel.h" +#include "layer.h" +#include "temp_buf.h" +#include "tile_manager.h" +#define GIMP_IMAGE(obj) GTK_CHECK_CAST (obj, gimp_image_get_type (), GimpImage) + + + +#define GIMP_IS_GIMAGE(obj) GTK_CHECK_TYPE (obj, gimp_image_get_type()) + + +/* the image types */ +typedef enum +{ + RGB_GIMAGE, + RGBA_GIMAGE, + GRAY_GIMAGE, + GRAYA_GIMAGE, + INDEXED_GIMAGE, + INDEXEDA_GIMAGE +} GimpImageType; + + + +#define TYPE_HAS_ALPHA(t) ((t)==RGBA_GIMAGE || (t)==GRAYA_GIMAGE || (t)==INDEXEDA_GIMAGE) + +#define GRAY_PIX 0 +#define ALPHA_G_PIX 1 +#define RED_PIX 0 +#define GREEN_PIX 1 +#define BLUE_PIX 2 +#define ALPHA_PIX 3 +#define INDEXED_PIX 0 +#define ALPHA_I_PIX 1 + +typedef enum +{ + RGB, + GRAY, + INDEXED +} GimpImageBaseType; + + + + +/* the image fill types */ +#define BACKGROUND_FILL 0 +#define WHITE_FILL 1 +#define TRANSPARENT_FILL 2 +#define NO_FILL 3 + +#define COLORMAP_SIZE 768 + +#define HORIZONTAL_GUIDE 1 +#define VERTICAL_GUIDE 2 + +typedef enum +{ + Red, + Green, + Blue, + Gray, + Indexed, + Auxillary +} ChannelType; + +typedef enum +{ + ExpandAsNecessary, + ClipToImage, + ClipToBottomLayer, + FlattenImage +} MergeType; + + +/* Ugly! Move this someplace else! */ +typedef struct _Guide Guide; + +struct _Guide +{ + int ref_count; + int position; + int orientation; +}; + + +typedef struct _GimpImageRepaintArg{ + Layer* layer; + guint x; + guint y; + guint width; + guint height; +} GimpImageRepaintArg; + + +GtkType gimp_image_get_type(void); + + +/* function declarations */ + +GimpImage * gimp_image_new (int, int, int); +void gimp_image_set_filename (GimpImage *, char *); +void gimp_image_resize (GimpImage *, int, int, int, int); +void gimp_image_scale (GimpImage *, int, int); +GimpImage * gimp_image_get_named (char *); +GimpImage * gimp_image_get_ID (int); +TileManager * gimp_image_shadow (GimpImage *, int, int, int); +void gimp_image_free_shadow (GimpImage *); +void gimp_image_apply_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, int, + TileManager *, int, int); +void gimp_image_replace_image (GimpImage *, GimpDrawable *, PixelRegion *, int, int, + PixelRegion *, int, int); +void gimp_image_get_foreground (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_background (GimpImage *, GimpDrawable *, unsigned char *); +void gimp_image_get_color (GimpImage *, int, unsigned char *, + unsigned char *); +void gimp_image_transform_color (GimpImage *, GimpDrawable *, unsigned char *, + unsigned char *, int); +Guide* gimp_image_add_hguide (GimpImage *); +Guide* gimp_image_add_vguide (GimpImage *); +void gimp_image_add_guide (GimpImage *, Guide *); +void gimp_image_remove_guide (GimpImage *, Guide *); +void gimp_image_delete_guide (GimpImage *, Guide *); + + +/* layer/channel functions */ + +int gimp_image_get_layer_index (GimpImage *, Layer *); +int gimp_image_get_channel_index (GimpImage *, Channel *); +Layer * gimp_image_get_active_layer (GimpImage *); +Channel * gimp_image_get_active_channel (GimpImage *); +Channel * gimp_image_get_mask (GimpImage *); +int gimp_image_get_component_active (GimpImage *, ChannelType); +int gimp_image_get_component_visible (GimpImage *, ChannelType); +int gimp_image_layer_boundary (GimpImage *, BoundSeg **, int *); +Layer * gimp_image_set_active_layer (GimpImage *, Layer *); +Channel * gimp_image_set_active_channel (GimpImage *, Channel *); +Channel * gimp_image_unset_active_channel (GimpImage *); +void gimp_image_set_component_active (GimpImage *, ChannelType, int); +void gimp_image_set_component_visible (GimpImage *, ChannelType, int); +Layer * gimp_image_pick_correlate_layer (GimpImage *, int, int); +void gimp_image_set_layer_mask_apply (GimpImage *, int); +void gimp_image_set_layer_mask_edit (GimpImage *, Layer *, int); +void gimp_image_set_layer_mask_show (GimpImage *, int); +Layer * gimp_image_raise_layer (GimpImage *, Layer *); +Layer * gimp_image_lower_layer (GimpImage *, Layer *); +Layer * gimp_image_merge_visible_layers (GimpImage *, MergeType); +Layer * gimp_image_flatten (GimpImage *); +Layer * gimp_image_merge_layers (GimpImage *, GSList *, MergeType); +Layer * gimp_image_add_layer (GimpImage *, Layer *, int); +Layer * gimp_image_remove_layer (GimpImage *, Layer *); +LayerMask * gimp_image_add_layer_mask (GimpImage *, Layer *, LayerMask *); +Channel * gimp_image_remove_layer_mask (GimpImage *, Layer *, int); +Channel * gimp_image_raise_channel (GimpImage *, Channel *); +Channel * gimp_image_lower_channel (GimpImage *, Channel *); +Channel * gimp_image_add_channel (GimpImage *, Channel *, int); +Channel * gimp_image_remove_channel (GimpImage *, Channel *); +void gimp_image_construct (GimpImage *, int, int, int, int); +void gimp_image_invalidate (GimpImage *, int, int, int, int, int, int, int, int); +void gimp_image_validate (TileManager *, Tile *, int); +void gimp_image_inflate (GimpImage *); +void gimp_image_deflate (GimpImage *); + + +/* Access functions */ + +int gimp_image_is_flat (GimpImage *); +int gimp_image_is_empty (GimpImage *); +GimpDrawable * gimp_image_active_drawable (GimpImage *); +int gimp_image_base_type (GimpImage *); +int gimp_image_base_type_with_alpha (GimpImage *); +char * gimp_image_filename (GimpImage *); +int gimp_image_enable_undo (GimpImage *); +int gimp_image_disable_undo (GimpImage *); +int gimp_image_dirty (GimpImage *); +int gimp_image_clean (GimpImage *); +void gimp_image_clean_all (GimpImage *); +Layer * gimp_image_floating_sel (GimpImage *); +unsigned char * gimp_image_cmap (GimpImage *); + + +/* projection access functions */ + +TileManager * gimp_image_projection (GimpImage *); +int gimp_image_projection_type (GimpImage *); +int gimp_image_projection_bytes (GimpImage *); +int gimp_image_projection_opacity (GimpImage *); +void gimp_image_projection_realloc (GimpImage *); + + +/* composite access functions */ + +TileManager * gimp_image_composite (GimpImage *); +int gimp_image_composite_type (GimpImage *); +int gimp_image_composite_bytes (GimpImage *); +TempBuf * gimp_image_composite_preview (GimpImage *, ChannelType, int, int); +int gimp_image_preview_valid (GimpImage *, ChannelType); +void gimp_image_invalidate_preview (GimpImage *); + +void gimp_image_invalidate_previews (void); + +/* from drawable.c */ +/* Ugly! */ +GimpImage * drawable_gimage (GimpDrawable*); + + +#endif diff --git a/app/gimpimageF.h b/app/gimpimageF.h new file mode 100644 index 0000000000..9826c79d8b --- /dev/null +++ b/app/gimpimageF.h @@ -0,0 +1,6 @@ +#ifndef __GIMPIMAGEF_H__ +#define __GIMPIMAGEF_H__ + +typedef struct _GimpImage GimpImage; + +#endif diff --git a/app/gimpimageP.h b/app/gimpimageP.h new file mode 100644 index 0000000000..799bee3183 --- /dev/null +++ b/app/gimpimageP.h @@ -0,0 +1,88 @@ +#ifndef __GIMPIMAGEP_H__ +#define __GIMPIMAGEP_H__ + +#include "gimpobjectP.h" +#include "gimpimageF.h" + +#include "tile_manager.h" +#include "temp_buf.h" +#include "channel.h" +#include "layer.h" + + +#define MAX_CHANNELS 4 + +struct _GimpImage +{ + GimpObject gobject; + char *filename; /* original filename */ + int has_filename; /* has a valid filename */ + + int width, height; /* width and height attributes */ + int base_type; /* base gimp_image type */ + + unsigned char * cmap; /* colormap--for indexed */ + int num_cols; /* number of cols--for indexed */ + + int dirty; /* dirty flag -- # of ops */ + int undo_on; /* Is undo enabled? */ + + int instance_count; /* number of instances */ + int ref_count; /* number of references */ + + TileManager *shadow; /* shadow buffer tiles */ + + int ID; /* Unique gimp_image identifier */ + + /* Projection attributes */ + int flat; /* Is the gimp_image flat? */ + int construct_flag; /* flag for construction */ + int proj_type; /* type of the projection image */ + int proj_bytes; /* bpp in projection image */ + int proj_level; /* projection level */ + TileManager *projection; /* The projection--layers & */ + /* channels */ + + GList *guides; /* guides */ + + /* Layer/Channel attributes */ + GSList *layers; /* the list of layers */ + GSList *channels; /* the list of masks */ + GSList *layer_stack; /* the layers in MRU order */ + + Layer * active_layer; /* ID of active layer */ + Channel * active_channel; /* ID of active channel */ + Layer * floating_sel; /* ID of fs layer */ + Channel * selection_mask; /* selection mask channel */ + + int visible [MAX_CHANNELS]; /* visible channels */ + int active [MAX_CHANNELS]; /* active channels */ + + int by_color_select; /* TRUE if there's an active */ + /* "by color" selection dialog */ + + /* Undo apparatus */ + GSList *undo_stack; /* stack for undo operations */ + GSList *redo_stack; /* stack for redo operations */ + int undo_bytes; /* bytes in undo stack */ + int undo_levels; /* levels in undo stack */ + int pushing_undo_group; /* undo group status flag */ + + /* Composite preview */ + TempBuf *comp_preview; /* the composite preview */ + int comp_preview_valid[3]; /* preview valid-1/channel */ +}; + +struct _GimpImageClass +{ + GimpObjectClass parent_class; + void (*dirty) (GtkObject*); + void (*repaint) (GtkObject*); + void (*rename) (GtkObject*); +}; +typedef struct _GimpImageClass GimpImageClass; + +#define GIMP_IMAGE_CLASS(klass) \ +GTK_CHECK_CLASS_CAST (klass, gimp_image_get_type(), GimpImageClass) + +#endif diff --git a/app/gimpobject.c b/app/gimpobject.c new file mode 100644 index 0000000000..c299a19b8a --- /dev/null +++ b/app/gimpobject.c @@ -0,0 +1,31 @@ +#include +#include "gimpobjectP.h" +#include "gimpobject.h" + +static void +gimp_object_init (GimpObject *gobject) +{ +} + +static void +gimp_object_class_init (GimpObjectClass *gobjectclass) +{ +} + +GtkType gimp_object_get_type (void) +{ + static GtkType type; + if(!type){ + GtkTypeInfo info={ + "GimpObject", + sizeof(GimpObject), + sizeof(GimpObjectClass), + gimp_object_class_init, + gimp_object_init, + NULL, + NULL}; + type=gtk_type_unique(gtk_object_get_type(), &info); + } + return type; +} + diff --git a/app/gimpobject.h b/app/gimpobject.h new file mode 100644 index 0000000000..a787501aba --- /dev/null +++ b/app/gimpobject.h @@ -0,0 +1,20 @@ +#ifndef __GIMP_OBJECT_H__ +#define __GIMP_OBJECT_H__ + +#include "gimpobjectF.h" + +#define GIMP_OBJECT(obj) \ +GTK_CHECK_CAST (obj, gimp_object_get_type (), GimpObject) +#define GIMP_OBJECT_CLASS(klass) \ +GTK_CHECK_CLASS_CAST (klass, gimp_object_get_type(), GimpObjectClass) +#define GIMP_IS_OBJECT(obj) \ +GTK_CHECK_TYPE (obj, gimp_object_get_type()) + +guint gimp_object_get_type(void); + +#endif + + + + + diff --git a/app/gimpobjectF.h b/app/gimpobjectF.h new file mode 100644 index 0000000000..77458f3a7c --- /dev/null +++ b/app/gimpobjectF.h @@ -0,0 +1,6 @@ +#ifndef __GIMP_OBJECT_F_H__ +#define __GIMP_OBJECT_F_H__ + +typedef struct _GimpObject GimpObject; + +#endif diff --git a/app/gimpobjectP.h b/app/gimpobjectP.h new file mode 100644 index 0000000000..9f4117e18c --- /dev/null +++ b/app/gimpobjectP.h @@ -0,0 +1,18 @@ +#ifndef __GIMP_OBJECT_P_H__ +#define __GIMP_OBJECT_P_H__ + +#include +#include "gimpobjectF.h" + +struct _GimpObject +{ + GtkObject object; +}; + +typedef struct +{ + GtkObjectClass parent_class; +}GimpObjectClass; + +guint gimp_object_get_type(void); +#endif diff --git a/app/gimpsignal.c b/app/gimpsignal.c new file mode 100644 index 0000000000..27bb566dee --- /dev/null +++ b/app/gimpsignal.c @@ -0,0 +1,114 @@ +#include "gimpsignal.h" + +struct _GimpSignalType{ + GtkSignalMarshaller marshaller; + GtkType return_type; + guint nparams; + const GtkType* param_types; +}; + +typedef const GtkType TypeArr[]; + +GimpSignalID gimp_signal_new(const gchar* name, + GtkSignalRunType signal_flags, + GtkType object_type, + guint function_offset, + GimpSignalType* sig_type){ + return gtk_signal_newv(name, + signal_flags, + object_type, + function_offset, + sig_type->marshaller, + sig_type->return_type, + sig_type->nparams, + /* Bah. We try to be const correct, but + gtk isn't.. */ + (GtkType*)sig_type->param_types); +} + +static GimpSignalType sigtype_void={ + gtk_signal_default_marshaller, + GTK_TYPE_NONE, + 0, + NULL +}; + +GimpSignalType* const gimp_sigtype_void=&sigtype_void; + +static void +gimp_marshaller_pointer (GtkObject* object, + GtkSignalFunc func, + gpointer func_data, + GtkArg* args) +{ + (*(GimpHandlerPointer)func) (object, + GTK_VALUE_POINTER (args[0]), + func_data); +} + +static TypeArr pointer_types={ + GTK_TYPE_POINTER +}; + +static GimpSignalType sigtype_pointer={ + gimp_marshaller_pointer, + GTK_TYPE_NONE, + 1, + pointer_types +}; + +GimpSignalType* const gimp_sigtype_pointer=&sigtype_pointer; + +static void +gimp_marshaller_int (GtkObject* object, + GtkSignalFunc func, + gpointer func_data, + GtkArg* args) +{ + (*(GimpHandlerInt)func) (object, + GTK_VALUE_INT (args[0]), + func_data); +} + +static TypeArr int_types={ + GTK_TYPE_INT +}; + +static GimpSignalType sigtype_int={ + gimp_marshaller_int, + GTK_TYPE_NONE, + 1, + int_types +}; + +GimpSignalType* const gimp_sigtype_int=&sigtype_int; + +static void +gimp_marshaller_int_int_int_int (GtkObject* object, + GtkSignalFunc func, + gpointer func_data, + GtkArg* args) +{ + (*(GimpHandlerIntIntIntInt)func) (object, + GTK_VALUE_INT (args[0]), + GTK_VALUE_INT (args[1]), + GTK_VALUE_INT (args[2]), + GTK_VALUE_INT (args[3]), + func_data); +} + +static TypeArr int_int_int_int_types={ + GTK_TYPE_INT, + GTK_TYPE_INT, + GTK_TYPE_INT, + GTK_TYPE_INT +}; + +static GimpSignalType sigtype_int_int_int_int={ + gimp_marshaller_int_int_int_int, + GTK_TYPE_NONE, + 4, + int_int_int_int_types +}; + +GimpSignalType* const gimp_sigtype_int_int_int_int=&sigtype_int_int_int_int; diff --git a/app/gimpsignal.h b/app/gimpsignal.h new file mode 100644 index 0000000000..a44cf15353 --- /dev/null +++ b/app/gimpsignal.h @@ -0,0 +1,35 @@ +#ifndef __GIMPSIGNAL_H__ +#define __GIMPSIGNAL_H__ + +#include + +/* This is the gtk "signal id" */ +typedef guint GimpSignalID; + + +typedef const struct _GimpSignalType GimpSignalType; +/* The arguments are encoded in the names.. */ + +GimpSignalType* const gimp_sigtype_void; +typedef void (*GimpHandlerVoid)(GtkObject*, gpointer); + +GimpSignalType* const gimp_sigtype_pointer; +typedef void (*GimpHandlerPointer)(GtkObject*, gpointer, gpointer); + +GimpSignalType* const gimp_sigtype_int; +typedef void (*GimpHandlerInt)(GtkObject*, gint, gpointer); + +GimpSignalType* const gimp_sigtype_int_int_int_int; +typedef void (*GimpHandlerIntIntIntInt) (GtkObject*, gint, gint, gint, gint, + gpointer); + +GimpSignalID gimp_signal_new(const gchar* name, + GtkSignalRunType signal_flags, + GtkType object_type, + guint function_offset, + GimpSignalType* sig_type); + + + + +#endif diff --git a/docs/OO.txt b/docs/OO.txt new file mode 100644 index 0000000000..9c91af651d --- /dev/null +++ b/docs/OO.txt @@ -0,0 +1,48 @@ + On making Gimp OO + +This document outlines the ideas of the conversion to using the GTK +object system in gimp core. + +The basic problem with gimp's internals is that it is _old_. Some of +the stuff dates from the 0.54 era, before layers, before GTK. This has +caused the current source to be what some people call a "mess". You +don't want to hear what the other people call it. + +Some of the main problems are that there are far, far too many headers +included everywhere. That is, encapsulation doesn't work. This causes +nasty dependencies, and doesn't exactly do good for compile times, +either. In addition, there are too much integer ids on objects. These +should only be used for pdb, and even there there'd probably be better +ways of passing them. The gtk object system will better facilitate +data hiding and encapsulation. + +Then there are the tools. The tools have a primitive object hierarchy, +but it is a mess when compared to gtk's system. Restructuring the +tools will make the world a better place, and new tools easier to +implement. + +GTK's object system has many other features that will make gimp +programming easier, the chief one being signals. When gimp's images +and displays have signals that you can connect callbacks to, different +components of the program will be better able to keep up with the +state of things. + +Also, having all types in the gimp core use a standard object system +will make it easier to export these types as CORBA objects when the +time comes for that. + + +Some guidelines: + +Everything should be as modular and independent as possible. Core +image manipulation classes should have no hard-coded relations with an +"UI". There should be no global variables in the classes. + +All gimp classes should derive from GimpObject. This is just in case +we need some common debugging functionality or something. + +For a future locking system, and just for code clarity, things should +be made const correct. That is, if a function doesn't modify an +object, it should take a pointer to a const object. + +