/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>

#include <glib-object.h>

#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"

#include "core-types.h"

#include "base/pixel-region.h"
#include "base/temp-buf.h"
#include "base/tile-manager.h"

#include "paint-funcs/paint-funcs.h"

#include "config/gimpcoreconfig.h"

#include "gimp.h"
#include "gimp-parasites.h"
#include "gimpcontext.h"
#include "gimpimage.h"
#include "gimpimage-colorhash.h"
#include "gimpimage-colormap.h"
#include "gimpimage-guides.h"
#include "gimpimage-mask.h"
#include "gimpimage-preview.h"
#include "gimpimage-projection.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimplayer.h"
#include "gimplayer-floating-sel.h"
#include "gimplayermask.h"
#include "gimplist.h"
#include "gimpmarshal.h"
#include "gimpparasitelist.h"
#include "gimpundostack.h"

#include "file/file-utils.h"

#include "vectors/gimpvectors.h"

#include "gimp-intl.h"


#ifdef DEBUG
#define TRC(x) printf x
#else
#define TRC(x)
#endif


enum
{
  MODE_CHANGED,
  ALPHA_CHANGED,
  FLOATING_SELECTION_CHANGED,
  ACTIVE_LAYER_CHANGED,
  ACTIVE_CHANNEL_CHANGED,
  ACTIVE_VECTORS_CHANGED,
  COMPONENT_VISIBILITY_CHANGED,
  COMPONENT_ACTIVE_CHANGED,
  MASK_CHANGED,
  RESOLUTION_CHANGED,
  UNIT_CHANGED,
  QMASK_CHANGED,
  SELECTION_CONTROL,
  CLEAN,
  DIRTY,
  UPDATE,
  UPDATE_GUIDE,
  COLORMAP_CHANGED,
  UNDO_START,
  UNDO_EVENT,
  FLUSH,
  LAST_SIGNAL
};


/*  local function prototypes  */

static void     gimp_image_class_init            (GimpImageClass *klass);
static void     gimp_image_init                  (GimpImage      *gimage);

static void     gimp_image_dispose               (GObject        *object);
static void     gimp_image_finalize              (GObject        *object);

static void     gimp_image_name_changed          (GimpObject     *object);
static gsize    gimp_image_get_memsize           (GimpObject     *object);

static void     gimp_image_invalidate_preview    (GimpViewable   *viewable);
static void     gimp_image_size_changed          (GimpViewable   *viewable);
static gchar  * gimp_image_get_description       (GimpViewable   *viewable,
                                                  gchar         **tooltip);
static void     gimp_image_real_colormap_changed (GimpImage      *gimage,
						  gint            ncol);

static void     gimp_image_get_active_components (const GimpImage    *gimage,
                                                  const GimpDrawable *drawable,
                                                  gboolean           *active);


static gint valid_combinations[][MAX_CHANNELS + 1] =
{
  /* GIMP_RGB_IMAGE */
  { -1, -1, -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A },
  /* GIMP_RGBA_IMAGE */
  { -1, -1, -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A },
  /* GIMP_GRAY_IMAGE */
  { -1, COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A, -1, -1 },
  /* GIMP_GRAYA_IMAGE */
  { -1, COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A, -1, -1 },
  /* GIMP_INDEXED_IMAGE */
  { -1, COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A, -1, -1 },
  /* GIMP_INDEXEDA_IMAGE */
  { -1, -1, COMBINE_INDEXED_A_INDEXED_A, -1, -1 },
};

static guint gimp_image_signals[LAST_SIGNAL] = { 0 };

static GimpViewableClass *parent_class = NULL;


GType 
gimp_image_get_type (void) 
{
  static GType image_type = 0;

  if (! image_type)
    {
      static const GTypeInfo image_info =
      {
        sizeof (GimpImageClass),
        NULL,           /* base_init */
        NULL,           /* base_finalize */
        (GClassInitFunc) gimp_image_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (GimpImage),
        0,              /* n_preallocs */
        (GInstanceInitFunc) gimp_image_init,
      };

      image_type = g_type_register_static (GIMP_TYPE_VIEWABLE,
					   "GimpImage",
					   &image_info, 0);
    }

  return image_type;
}


/*  private functions  */

static void
gimp_image_class_init (GimpImageClass *klass)
{
  GObjectClass      *object_class;
  GimpObjectClass   *gimp_object_class;
  GimpViewableClass *viewable_class;

  object_class      = G_OBJECT_CLASS (klass);
  gimp_object_class = GIMP_OBJECT_CLASS (klass);
  viewable_class    = GIMP_VIEWABLE_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  gimp_image_signals[MODE_CHANGED] =
    g_signal_new ("mode_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, mode_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[ALPHA_CHANGED] =
    g_signal_new ("alpha_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, alpha_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[FLOATING_SELECTION_CHANGED] =
    g_signal_new ("floating_selection_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, floating_selection_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[ACTIVE_LAYER_CHANGED] =
    g_signal_new ("active_layer_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, active_layer_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[ACTIVE_CHANNEL_CHANGED] =
    g_signal_new ("active_channel_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, active_channel_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[ACTIVE_VECTORS_CHANGED] =
    g_signal_new ("active_vectors_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, active_vectors_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[COMPONENT_VISIBILITY_CHANGED] =
    g_signal_new ("component_visibility_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, component_visibility_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__ENUM,
		  G_TYPE_NONE, 1,
		  GIMP_TYPE_CHANNEL_TYPE);

  gimp_image_signals[COMPONENT_ACTIVE_CHANGED] =
    g_signal_new ("component_active_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, component_active_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__ENUM,
		  G_TYPE_NONE, 1,
		  GIMP_TYPE_CHANNEL_TYPE);

  gimp_image_signals[MASK_CHANGED] =
    g_signal_new ("mask_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, mask_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[RESOLUTION_CHANGED] =
    g_signal_new ("resolution_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, resolution_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[UNIT_CHANGED] =
    g_signal_new ("unit_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, unit_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[QMASK_CHANGED] =
    g_signal_new ("qmask_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, qmask_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[SELECTION_CONTROL] =
    g_signal_new ("selection_control",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, selection_control),
		  NULL, NULL,
		  gimp_marshal_VOID__ENUM,
		  G_TYPE_NONE, 1,
                  GIMP_TYPE_SELECTION_CONTROL);

  gimp_image_signals[CLEAN] =
    g_signal_new ("clean",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, clean),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[DIRTY] =
    g_signal_new ("dirty",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, dirty),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[UPDATE] =
    g_signal_new ("update",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, update),
		  NULL, NULL,
		  gimp_marshal_VOID__INT_INT_INT_INT,
		  G_TYPE_NONE, 4,
		  G_TYPE_INT,
		  G_TYPE_INT,
		  G_TYPE_INT,
		  G_TYPE_INT);

  gimp_image_signals[UPDATE_GUIDE] =
    g_signal_new ("update_guide",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, update_guide),
		  NULL, NULL,
		  gimp_marshal_VOID__POINTER,
		  G_TYPE_NONE, 1,
		  G_TYPE_POINTER);

  gimp_image_signals[COLORMAP_CHANGED] =
    g_signal_new ("colormap_changed",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, colormap_changed),
		  NULL, NULL,
		  gimp_marshal_VOID__INT,
		  G_TYPE_NONE, 1,
		  G_TYPE_INT);

  gimp_image_signals[UNDO_START] =
    g_signal_new ("undo_start",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, undo_start),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  gimp_image_signals[UNDO_EVENT] = 
    g_signal_new ("undo_event",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, undo_event),
		  NULL, NULL,
		  gimp_marshal_VOID__ENUM_OBJECT,
		  G_TYPE_NONE, 2,
		  GIMP_TYPE_UNDO_EVENT,
                  GIMP_TYPE_UNDO);

  gimp_image_signals[FLUSH] =
    g_signal_new ("flush",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpImageClass, flush),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  object_class->dispose               = gimp_image_dispose;
  object_class->finalize              = gimp_image_finalize;

  gimp_object_class->name_changed     = gimp_image_name_changed;
  gimp_object_class->get_memsize      = gimp_image_get_memsize;

  viewable_class->default_stock_id    = "gimp-image";
  viewable_class->invalidate_preview  = gimp_image_invalidate_preview;
  viewable_class->size_changed        = gimp_image_size_changed;
  viewable_class->get_preview_size    = gimp_image_get_preview_size;
  viewable_class->get_popup_size      = gimp_image_get_popup_size;
  viewable_class->get_preview         = gimp_image_get_preview;
  viewable_class->get_new_preview     = gimp_image_get_new_preview;
  viewable_class->get_description     = gimp_image_get_description;

  klass->mode_changed                 = NULL;
  klass->alpha_changed                = NULL;
  klass->floating_selection_changed   = NULL;
  klass->active_layer_changed         = NULL;
  klass->active_channel_changed       = NULL;
  klass->active_vectors_changed       = NULL;
  klass->component_visibility_changed = NULL;
  klass->component_active_changed     = NULL;
  klass->mask_changed                 = NULL;

  klass->clean                        = NULL;
  klass->dirty                        = NULL;
  klass->update                       = NULL;
  klass->update_guide                 = NULL;
  klass->colormap_changed             = gimp_image_real_colormap_changed;
  klass->undo_start                   = NULL;
  klass->undo_event                   = NULL;
  klass->flush                        = NULL;

  gimp_image_color_hash_init ();
}

static void 
gimp_image_init (GimpImage *gimage)
{
  gimage->ID                    = 0;

  gimage->save_proc             = NULL;

  gimage->width                 = 0;
  gimage->height                = 0;
  gimage->xresolution           = 1.0;
  gimage->yresolution           = 1.0;
  gimage->unit                  = GIMP_UNIT_INCH;
  gimage->base_type             = GIMP_RGB;

  gimage->cmap                  = NULL;
  gimage->num_cols              = 0;

  gimage->dirty                 = 1;
  gimage->undo_on               = TRUE;

  gimage->instance_count        = 0;
  gimage->disp_count            = 0;

  gimage->tattoo_state          = 0;

  gimage->shadow                = NULL;

  gimage->construct_flag        = FALSE;
  gimage->proj_type             = GIMP_RGBA_IMAGE;
  gimage->projection            = NULL;

  gimage->guides                = NULL;

  gimage->layers                = gimp_list_new (GIMP_TYPE_LAYER, 
						 GIMP_CONTAINER_POLICY_STRONG);
  gimage->channels              = gimp_list_new (GIMP_TYPE_CHANNEL, 
						 GIMP_CONTAINER_POLICY_STRONG);
  gimage->vectors               = gimp_list_new (GIMP_TYPE_VECTORS,
                                                 GIMP_CONTAINER_POLICY_STRONG);
  gimage->layer_stack           = NULL;

  gimage->active_layer          = NULL;
  gimage->active_channel        = NULL;
  gimage->active_vectors        = NULL;

  gimage->floating_sel          = NULL;
  gimage->selection_mask        = NULL;

  gimage->parasites             = gimp_parasite_list_new ();

  gimage->qmask_state           = FALSE;
  gimage->qmask_inverted        = FALSE;
  gimage->qmask_color.r         = 1.0;
  gimage->qmask_color.g         = 0.0;
  gimage->qmask_color.b         = 0.0;
  gimage->qmask_color.a         = 0.5;

  gimage->undo_stack            = gimp_undo_stack_new (gimage);
  gimage->redo_stack            = gimp_undo_stack_new (gimage);
  gimage->group_count           = 0;
  gimage->pushing_undo_group    = GIMP_UNDO_GROUP_NONE;


  gimage->comp_preview          = NULL;
  gimage->comp_preview_valid    = FALSE;
}

static void
gimp_image_dispose (GObject *object)
{
  GimpImage *gimage;

  gimage = GIMP_IMAGE (object);

  gimp_image_undo_free (gimage);

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gimp_image_finalize (GObject *object)
{
  GimpImage *gimage;

  gimage = GIMP_IMAGE (object);

  if (gimage->gimp && gimage->gimp->image_table)
    {
      g_hash_table_remove (gimage->gimp->image_table,
			   GINT_TO_POINTER (gimage->ID));
      gimage->gimp = NULL;
    }

  if (gimage->projection)
    gimp_image_projection_free (gimage);

  if (gimage->shadow)
    gimp_image_free_shadow (gimage);

  if (gimage->cmap)
    {
      g_free (gimage->cmap);
      gimage->cmap = NULL;
    }

  if (gimage->layers)
    {
      g_object_unref (gimage->layers);
      gimage->layers = NULL;
    }
  if (gimage->channels)
    {
      g_object_unref (gimage->channels);
      gimage->channels = NULL;
    }
  if (gimage->vectors)
    {
      g_object_unref (gimage->vectors);
      gimage->vectors = NULL;
    }
  if (gimage->layer_stack)
    {
      g_slist_free (gimage->layer_stack);
      gimage->layer_stack = NULL;
    }

  if (gimage->selection_mask)
    {
      g_object_unref (gimage->selection_mask);
      gimage->selection_mask = NULL;
    }

  if (gimage->comp_preview)
    {
      temp_buf_free (gimage->comp_preview);
      gimage->comp_preview = NULL;
    }

  if (gimage->parasites)
    {
      g_object_unref (gimage->parasites);
      gimage->parasites = NULL;
    }

  if (gimage->guides)
    {
      g_list_foreach (gimage->guides, (GFunc) gimp_image_guide_unref, NULL);
      g_list_free (gimage->guides);
      gimage->guides = NULL;
    }

  if (gimage->undo_stack)
    {
      g_object_unref (gimage->undo_stack);
      gimage->undo_stack = NULL;
    }
  if (gimage->redo_stack)
    {
      g_object_unref (gimage->redo_stack);
      gimage->redo_stack = NULL;
    }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_image_name_changed (GimpObject *object)
{
  GimpImage   *gimage;
  const gchar *name;

  if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
    GIMP_OBJECT_CLASS (parent_class)->name_changed (object);

  gimage = GIMP_IMAGE (object);
  name   = gimp_object_get_name (object);

  if (! (name && strlen (name)))
    {
      g_free (object->name);
      object->name = NULL;
    }
}

static gsize
gimp_image_get_memsize (GimpObject *object)
{
  GimpImage *gimage;
  gsize      memsize = 0;

  gimage = GIMP_IMAGE (object);

  if (gimage->cmap)
    memsize += GIMP_IMAGE_COLORMAP_SIZE;

  if (gimage->shadow)
    memsize += tile_manager_get_memsize (gimage->shadow);

  if (gimage->projection)
    memsize += tile_manager_get_memsize (gimage->projection);

  memsize += (g_list_length (gimage->guides) * (sizeof (GList) +
                                                sizeof (GimpGuide)));

  memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->layers));
  memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->channels));
  memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->vectors));

  memsize += g_slist_length (gimage->layer_stack) * sizeof (GSList);

  if (gimage->selection_mask)
    memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->selection_mask));

  memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->parasites));

  memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->undo_stack));
  memsize += gimp_object_get_memsize (GIMP_OBJECT (gimage->redo_stack));

  if (gimage->comp_preview)
    memsize += temp_buf_get_memsize (gimage->comp_preview);

  return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object);
}

static void
gimp_image_invalidate_preview (GimpViewable *viewable)
{
  GimpImage *gimage;

  if (GIMP_VIEWABLE_CLASS (parent_class)->invalidate_preview)
    GIMP_VIEWABLE_CLASS (parent_class)->invalidate_preview (viewable);

  gimage = GIMP_IMAGE (viewable);

  gimage->comp_preview_valid = FALSE;

  if (gimage->comp_preview)
    {
      temp_buf_free (gimage->comp_preview);
      gimage->comp_preview = NULL;
    }
}

static void
gimp_image_size_changed (GimpViewable *viewable)
{
  GimpImage *gimage;
  GList     *list;

  if (GIMP_VIEWABLE_CLASS (parent_class)->size_changed)
    GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable);

  gimage = GIMP_IMAGE (viewable);

  gimp_container_foreach (gimage->layers, 
			  (GFunc) gimp_viewable_size_changed,
			  NULL);
  gimp_container_foreach (gimage->channels,
			  (GFunc) gimp_viewable_size_changed,
			  NULL);
  gimp_container_foreach (gimage->vectors,
			  (GFunc) gimp_viewable_size_changed,
			  NULL);

  for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list))
    {
      GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (list->data));

      if (mask)
        gimp_viewable_size_changed (GIMP_VIEWABLE (mask));
    }

  gimp_viewable_size_changed (GIMP_VIEWABLE (gimp_image_get_mask (gimage)));
}

static gchar *
gimp_image_get_description (GimpViewable  *viewable,
                            gchar        **tooltip)
{
  GimpImage   *gimage;
  const gchar *uri;
  gchar       *basename;
  gchar       *retval;

  gimage = GIMP_IMAGE (viewable);

  uri = gimp_image_get_uri (GIMP_IMAGE (gimage));

  basename = file_utils_uri_to_utf8_basename (uri);

  if (tooltip)
    *tooltip = file_utils_uri_to_utf8_filename (uri);

  retval = g_strdup_printf ("%s-%d", basename, gimp_image_get_ID (gimage));

  g_free (basename);

  return retval;
}

static void 
gimp_image_real_colormap_changed (GimpImage *gimage,
				  gint       ncol)
{
  if (gimp_image_base_type (gimage) == GIMP_INDEXED)
    {
      /* A colormap alteration affects the whole image */
      gimp_image_update (gimage, 0, 0, gimage->width, gimage->height);

      gimp_image_color_hash_invalidate (gimage, ncol);
    }
}

static void
gimp_image_get_active_components (const GimpImage    *gimage,
                                  const GimpDrawable *drawable,
                                  gboolean           *active)
{
  /* If the drawable is a channel make sure that the alpha channel is
   * not valid.
   */
  if (GIMP_IS_CHANNEL (drawable))
    {
      active[GRAY_PIX]    = TRUE;
      active[ALPHA_G_PIX] = FALSE;
    }
  /*  otherwise, check whether preserve transparency is
   *  enabled in the layer and if the layer has alpha
   */
  else if (GIMP_IS_LAYER (drawable))
    {
      GimpLayer *layer = GIMP_LAYER (drawable);
      gint       i;
      
      /*  first copy the gimage active channels  */
      for (i = 0; i < MAX_CHANNELS; i++)
        active[i] = gimage->active[i];

      if (gimp_drawable_has_alpha (drawable) && layer->preserve_trans)
        active[gimp_drawable_bytes (drawable) - 1] = FALSE;
    }
}


/*  public functions  */

GimpImage *
gimp_image_new (Gimp              *gimp,
		gint               width,
		gint               height,
		GimpImageBaseType  base_type)
{
  GimpImage *gimage;
  gint       i;

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);

  gimage = GIMP_IMAGE (g_object_new (GIMP_TYPE_IMAGE, NULL));

  gimage->gimp        = gimp;
  gimage->ID          = gimp->next_image_ID++;

  g_hash_table_insert (gimp->image_table,
		       GINT_TO_POINTER (gimage->ID),
		       (gpointer) gimage);

  gimage->width       = width;
  gimage->height      = height;
  gimage->base_type   = base_type;

  gimage->xresolution = gimp->config->default_xresolution;
  gimage->yresolution = gimp->config->default_yresolution;
  gimage->unit        = gimp->config->default_unit;

  switch (base_type)
    {
    case GIMP_RGB:
    case GIMP_GRAY:
      break;
    case GIMP_INDEXED:
      /* always allocate 256 colors for the colormap */
      gimage->num_cols = 0;
      gimage->cmap     = g_new0 (guchar, GIMP_IMAGE_COLORMAP_SIZE);
      break;
    default:
      break;
    }

  /*  set all color channels visible and active  */
  for (i = 0; i < MAX_CHANNELS; i++)
    {
      gimage->visible[i] = TRUE;
      gimage->active[i]  = TRUE;
    }

  /* create the selection mask */
  gimage->selection_mask = gimp_channel_new_mask (gimage,
						  gimage->width,
						  gimage->height);

  g_signal_connect_object (gimp->config, "notify::transparency-type",
                           G_CALLBACK (gimp_image_invalidate_layer_previews),
                           gimage, G_CONNECT_SWAPPED);
  g_signal_connect_object (gimp->config, "notify::transparency-size",
                           G_CALLBACK (gimp_image_invalidate_layer_previews),
                           gimage, G_CONNECT_SWAPPED);
  g_signal_connect_object (gimp->config, "notify::layer-previews",
                           G_CALLBACK (gimp_viewable_size_changed),
                           gimage, G_CONNECT_SWAPPED);

  return gimage;
}

GimpImageBaseType
gimp_image_base_type (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);

  return gimage->base_type;
}

GimpImageType
gimp_image_base_type_with_alpha (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);

  switch (gimage->base_type)
    {
    case GIMP_RGB:
      return GIMP_RGBA_IMAGE;
    case GIMP_GRAY:
      return GIMP_GRAYA_IMAGE;
    case GIMP_INDEXED:
      return GIMP_INDEXEDA_IMAGE;
    }

  return GIMP_RGB_IMAGE;
}

CombinationMode
gimp_image_get_combination_mode (GimpImageType dest_type,
                                 gint          src_bytes)
{
  return valid_combinations[dest_type][src_bytes];
}

gint
gimp_image_get_ID (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);

  return gimage->ID;
}

GimpImage *
gimp_image_get_by_ID (Gimp *gimp,
		      gint  image_id)
{
  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);

  if (gimp->image_table == NULL)
    return NULL;

  return (GimpImage *) g_hash_table_lookup (gimp->image_table, 
					    GINT_TO_POINTER (image_id));
}

void
gimp_image_set_uri (GimpImage   *gimage,
                    const gchar *uri)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  gimp_object_set_name (GIMP_OBJECT (gimage), uri);
}

const gchar *
gimp_image_get_uri (const GimpImage *gimage)
{
  const gchar *uri;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  uri = gimp_object_get_name (GIMP_OBJECT (gimage));

  return uri ? uri : _("Untitled");
}

void
gimp_image_set_filename (GimpImage   *gimage,
                         const gchar *filename)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  if (filename && strlen (filename))
    {
      gchar *uri;

      uri = file_utils_filename_to_uri (gimage->gimp->load_procs, filename,
                                        NULL);

      gimp_image_set_uri (gimage, uri);

      g_free (uri);
    }
  else
    {
      gimp_image_set_uri (gimage, NULL);
    }
}

gchar *
gimp_image_get_filename (const GimpImage *gimage)
{
  const gchar *uri;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  uri = gimp_object_get_name (GIMP_OBJECT (gimage));

  if (! uri)
    return NULL;

  return g_filename_from_uri (uri, NULL, NULL);
}

void
gimp_image_set_save_proc (GimpImage     *gimage, 
			  PlugInProcDef *proc)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  gimage->save_proc = proc;
}

PlugInProcDef *
gimp_image_get_save_proc (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->save_proc;
}

void
gimp_image_set_resolution (GimpImage *gimage,
			   gdouble    xresolution,
			   gdouble    yresolution)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  /* don't allow to set the resolution out of bounds */
  if (xresolution < GIMP_MIN_RESOLUTION || xresolution > GIMP_MAX_RESOLUTION ||
      yresolution < GIMP_MIN_RESOLUTION || yresolution > GIMP_MAX_RESOLUTION)
    return;

  if ((ABS (gimage->xresolution - xresolution) >= 1e-5) ||
      (ABS (gimage->yresolution - yresolution) >= 1e-5))
    {
      gimp_image_undo_push_image_resolution (gimage,
                                             _("Change Image Resolution"));

      gimage->xresolution = xresolution;
      gimage->yresolution = yresolution;

      gimp_image_resolution_changed (gimage);
      gimp_viewable_size_changed (GIMP_VIEWABLE (gimage));
    }
}

void
gimp_image_get_resolution (const GimpImage *gimage,
			   gdouble         *xresolution,
			   gdouble         *yresolution)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (xresolution && yresolution);

  *xresolution = gimage->xresolution;
  *yresolution = gimage->yresolution;
}

void
gimp_image_resolution_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[RESOLUTION_CHANGED], 0);
}

void
gimp_image_set_unit (GimpImage *gimage,
		     GimpUnit   unit)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  if (gimage->unit != unit)
    {
      gimp_image_undo_push_image_resolution (gimage,
                                             _("Change Image Unit"));

      gimage->unit = unit;

      gimp_image_unit_changed (gimage);
    }
}

GimpUnit
gimp_image_get_unit (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), GIMP_UNIT_INCH);

  return gimage->unit;
}

void
gimp_image_unit_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[UNIT_CHANGED], 0);
}

gint
gimp_image_get_width (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), 0);

  return gimage->width;
}

gint
gimp_image_get_height (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), 0);

  return gimage->height;
}

gboolean
gimp_image_has_alpha (const GimpImage *gimage)
{
  GimpLayer *layer;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), TRUE);

  layer = (GimpLayer *) gimp_container_get_child_by_index (gimage->layers, 0);

  return ((gimp_container_num_children (gimage->layers) > 1) ||
          (layer && gimp_drawable_has_alpha (GIMP_DRAWABLE (layer))));
}

gboolean
gimp_image_is_empty (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), TRUE);

  return (gimp_container_num_children (gimage->layers) == 0);
}

GimpLayer *
gimp_image_floating_sel (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  if (gimage->floating_sel == NULL)
    return NULL;
  else
    return gimage->floating_sel;
}

void
gimp_image_floating_selection_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[FLOATING_SELECTION_CHANGED], 0);
}

GimpChannel *
gimp_image_get_mask (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->selection_mask;
}

void
gimp_image_mask_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[MASK_CHANGED], 0);
}

gint
gimp_image_get_component_index (const GimpImage *gimage,
                                GimpChannelType  channel)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);

  switch (channel)
    {
    case GIMP_RED_CHANNEL:     return RED_PIX;
    case GIMP_GREEN_CHANNEL:   return GREEN_PIX;
    case GIMP_BLUE_CHANNEL:    return BLUE_PIX;
    case GIMP_GRAY_CHANNEL:    return GRAY_PIX;
    case GIMP_INDEXED_CHANNEL: return INDEXED_PIX;
    case GIMP_ALPHA_CHANNEL:
      switch (gimp_image_base_type (gimage))
	{
	case GIMP_RGB:     return ALPHA_PIX;
	case GIMP_GRAY:    return ALPHA_G_PIX;
	case GIMP_INDEXED: return ALPHA_I_PIX;
	}
    }

  return -1;
}

void
gimp_image_set_component_active (GimpImage       *gimage, 
				 GimpChannelType  channel,
				 gboolean         active)
{
  gint index = -1;

  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  index = gimp_image_get_component_index (gimage, channel);

  if (index != -1 && active != gimage->active[index])
    {
      gimage->active[index] = active ? TRUE : FALSE;

      /*  If there is an active channel and we mess with the components,
       *  the active channel gets unset...
       */
      gimp_image_unset_active_channel (gimage);

      g_signal_emit (gimage,
		     gimp_image_signals[COMPONENT_ACTIVE_CHANGED], 0,
		     channel);
    }
}

gboolean
gimp_image_get_component_active (const GimpImage *gimage, 
				 GimpChannelType  channel)
{
  gint index = -1;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  index = gimp_image_get_component_index (gimage, channel);

  if (index != -1)
    return gimage->active[index];

  return FALSE;
}

void
gimp_image_set_component_visible (GimpImage       *gimage, 
				  GimpChannelType  channel,
				  gboolean         visible)
{
  gint index = -1;

  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  index = gimp_image_get_component_index (gimage, channel);

  if (index != -1 && visible != gimage->visible[index])
    {
      gimage->visible[index] = visible ? TRUE : FALSE;

      g_signal_emit (gimage,
		     gimp_image_signals[COMPONENT_VISIBILITY_CHANGED], 0,
		     channel);

      gimp_image_update (gimage, 0, 0, gimage->width, gimage->height);
    }
}

gboolean
gimp_image_get_component_visible (const GimpImage *gimage, 
				  GimpChannelType  channel)
{
  gint index = -1;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  index = gimp_image_get_component_index (gimage, channel);

  if (index != -1)
    return gimage->visible[index];

  return FALSE;
}

void
gimp_image_mode_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[MODE_CHANGED], 0);
}

void
gimp_image_alpha_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[ALPHA_CHANGED], 0);
}

void
gimp_image_update (GimpImage *gimage,
		   gint       x,
		   gint       y,
		   gint       width,
		   gint       height)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[UPDATE], 0,
		 x, y, width, height);
}

void
gimp_image_update_guide (GimpImage *gimage,
                         GimpGuide *guide)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (guide != NULL);

  g_signal_emit (gimage, gimp_image_signals[UPDATE_GUIDE], 0, guide);
}

void
gimp_image_colormap_changed (GimpImage *gimage, 
			     gint       col)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (col >= -1 && col < gimage->num_cols);

  g_signal_emit (gimage, gimp_image_signals[COLORMAP_CHANGED], 0,
		 col);
}

void
gimp_image_selection_control (GimpImage            *gimage,
                              GimpSelectionControl  control)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[SELECTION_CONTROL], 0, control);
}

void
gimp_image_qmask_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[QMASK_CHANGED], 0);
}


/*  undo  */

gboolean
gimp_image_undo_is_enabled (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  return gimage->undo_on;
}

gboolean
gimp_image_undo_enable (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  /*  Free all undo steps as they are now invalidated  */
  gimp_image_undo_free (gimage);

  return gimp_image_undo_thaw (gimage);
}

gboolean
gimp_image_undo_disable (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  return gimp_image_undo_freeze (gimage);
}

gboolean
gimp_image_undo_freeze (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  gimage->undo_on = FALSE;

  return TRUE;
}

gboolean
gimp_image_undo_thaw (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  gimage->undo_on = TRUE;

  return TRUE;
}

void
gimp_image_undo_start (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[UNDO_START], 0);
}

void
gimp_image_undo_event (GimpImage     *gimage,
		       GimpUndoEvent  event,
                       GimpUndo      *undo)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (event == GIMP_UNDO_EVENT_UNDO_FREE || GIMP_IS_UNDO (undo));

  g_signal_emit (gimage, gimp_image_signals[UNDO_EVENT], 0, event, undo);
}


/* NOTE about the gimage->dirty counter:
 *   If 0, then the image is clean (ie, copy on disk is the same as the one 
 *      in memory).
 *   If positive, then that's the number of dirtying operations done
 *       on the image since the last save.
 *   If negative, then user has hit undo and gone back in time prior
 *       to the saved copy.  Hitting redo will eventually come back to
 *       the saved copy.
 *
 *   The image is dirty (ie, needs saving) if counter is non-zero.
 *
 *   If the counter is around 10000, this is due to undo-ing back
 *   before a saved version, then mutating the image (thus destroying
 *   the redo stack).  Once this has happened, it's impossible to get
 *   the image back to the state on disk, since the redo info has been
 *   freed.  See undo.c for the gorey details.
 */


/*
 * NEVER CALL gimp_image_dirty() directly!
 *
 * If your code has just dirtied the image, push an undo instead.
 * Failing that, push the trivial undo which tells the user the
 * command is not undoable: undo_push_cantundo() (But really, it would
 * be best to push a proper undo).  If you just dirty the image
 * without pushing an undo then the dirty count is increased, but
 * popping that many undo actions won't lead to a clean image.
 */

gint
gimp_image_dirty (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  gimage->dirty++;

  g_signal_emit (gimage, gimp_image_signals[DIRTY], 0);

  TRC (("dirty %d -> %d\n", gimage->dirty-1, gimage->dirty));

  return gimage->dirty;
}

gint
gimp_image_clean (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  gimage->dirty--;

  g_signal_emit (gimage, gimp_image_signals[CLEAN], 0);
  
  TRC (("clean %d -> %d\n", gimage->dirty+1, gimage->dirty));
  
  return gimage->dirty;
}

void
gimp_image_clean_all (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  gimage->dirty = 0;

  g_signal_emit (gimage, gimp_image_signals[CLEAN], 0);
}


/*  flush this image's displays  */

void
gimp_image_flush (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[FLUSH], 0);
}


/*  color transforms / utilities  */


/* Get rid of these! A "foreground" is an UI concept.. */

void
gimp_image_get_foreground (const GimpImage    *gimage, 
			   const GimpDrawable *drawable, 
			   guchar             *fg)
{
  GimpRGB  color;
  guchar   pfg[3];

  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (! drawable || GIMP_IS_DRAWABLE (drawable));
  g_return_if_fail (fg != NULL);

  gimp_context_get_foreground (gimp_get_current_context (gimage->gimp), &color);

  gimp_rgb_get_uchar (&color, &pfg[0], &pfg[1], &pfg[2]);

  gimp_image_transform_color (gimage, drawable, pfg, fg, GIMP_RGB);
}

void
gimp_image_get_background (const GimpImage    *gimage, 
			   const GimpDrawable *drawable, 
			   guchar             *bg)
{
  GimpRGB  color;
  guchar   pbg[3];

  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (! drawable || GIMP_IS_DRAWABLE (drawable));
  g_return_if_fail (bg != NULL);

  gimp_context_get_background (gimp_get_current_context (gimage->gimp), &color);

  gimp_rgb_get_uchar (&color, &pbg[0], &pbg[1], &pbg[2]);

  gimp_image_transform_color (gimage, drawable, pbg, bg, GIMP_RGB);
}

void
gimp_image_get_color (const GimpImage *gimage, 
		      GimpImageType    d_type,
		      guchar          *rgb, 
		      guchar          *src)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  switch (d_type)
    {
    case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE:
      map_to_color (0, NULL, src, rgb);
      break;
    case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE:
      map_to_color (1, NULL, src, rgb);
      break;
    case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE:
      map_to_color (2, gimage->cmap, src, rgb);
      break;
    }
}

void
gimp_image_transform_color (const GimpImage    *gimage, 
			    const GimpDrawable *drawable,
			    guchar             *src, 
			    guchar             *dest, 
			    GimpImageBaseType   type)
{
  GimpImageType drawable_type;

  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  drawable_type = (drawable ?
                   gimp_drawable_type (drawable) :
                   gimp_image_base_type_with_alpha (gimage));

  switch (type)
    {
    case GIMP_RGB:
      switch (drawable_type)
	{
	case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE:
	  /*  Straight copy  */
	  *dest++ = *src++;
	  *dest++ = *src++;
	  *dest++ = *src++;
	  break;
	case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE:
	  /*  NTSC conversion  */
	  *dest = INTENSITY (src[RED_PIX],
			     src[GREEN_PIX],
			     src[BLUE_PIX]);
	  break;
	case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE:
	  /*  Least squares method  */
	  *dest = gimp_image_color_hash_rgb_to_indexed (gimage,
							src[RED_PIX],
							src[GREEN_PIX],
							src[BLUE_PIX]);
	  break;
	}
      break;

    case GIMP_GRAY:
      switch (drawable_type)
	{
	case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE:
	  /*  Gray to RG&B */
	  *dest++ = *src;
	  *dest++ = *src;
	  *dest++ = *src;
	  break;
	case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE:
	  /*  Straight copy  */
	  *dest = *src;
	  break;
	case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE:
	  /*  Least squares method  */
	  *dest = gimp_image_color_hash_rgb_to_indexed (gimage,
							src[GRAY_PIX],
							src[GRAY_PIX],
							src[GRAY_PIX]);
	  break;
	}
      break;

    default:
      break;
    }
}


/*  shadow tiles  */

TileManager *
gimp_image_shadow (GimpImage *gimage, 
		   gint       width, 
		   gint       height, 
		   gint       bpp)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  if (gimage->shadow)
    {
      if ((width != tile_manager_width (gimage->shadow)) ||
          (height != tile_manager_height (gimage->shadow)) ||
          (bpp != tile_manager_bpp (gimage->shadow)))
        {
          gimp_image_free_shadow (gimage);
        }
      else
        {
          return gimage->shadow;
        }
    }

  gimage->shadow = tile_manager_new (width, height, bpp);

  return gimage->shadow;
}

void
gimp_image_free_shadow (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  if (gimage->shadow)
    {
      tile_manager_unref (gimage->shadow);
      gimage->shadow = NULL;
    }
}


/*  combine functions  */

void
gimp_image_apply_image (GimpImage	     *gimage,
			GimpDrawable	     *drawable,
			PixelRegion	     *src2PR,
			gboolean              push_undo,
                        const gchar          *undo_desc,
			gdouble               opacity,
			GimpLayerModeEffects  mode,
			/*  alternative to using drawable tiles as src1: */
			TileManager	     *src1_tiles,
			gint                  x,
			gint                  y)
{
  GimpItem        *item;
  GimpChannel     *mask;
  gint             x1, y1, x2, y2;
  gint             offset_x, offset_y;
  PixelRegion      src1PR, destPR, maskPR;
  CombinationMode  operation;
  gboolean         active_components[MAX_CHANNELS];

  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (GIMP_IS_DRAWABLE (drawable));

  item = GIMP_ITEM (drawable);

  /*  get the selection mask if one exists  */
  mask = (gimp_image_mask_is_empty (gimage) ? 
          NULL : gimp_image_get_mask (gimage));

  /*  configure the active channel array  */
  gimp_image_get_active_components (gimage, drawable, active_components);

  /*  determine what sort of operation is being attempted and
   *  if it's actually legal...
   */
  operation = gimp_image_get_combination_mode (gimp_drawable_type (drawable),
                                               src2PR->bytes);
  if (operation == -1)
    {
      g_warning ("%s: illegal parameters.", G_GNUC_PRETTY_FUNCTION);
      return;
    }

  /*  get the layer offsets  */
  gimp_item_offsets (item, &offset_x, &offset_y);

  /*  make sure the image application coordinates are within gimage bounds  */
  x1 = CLAMP (x, 0, gimp_item_width  (item));
  y1 = CLAMP (y, 0, gimp_item_height (item));
  x2 = CLAMP (x + src2PR->w, 0, gimp_item_width  (item));
  y2 = CLAMP (y + src2PR->h, 0, gimp_item_height (item));

  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, gimp_item_width  (GIMP_ITEM (mask))-offset_x);
      y1 = CLAMP (y1, -offset_y, gimp_item_height (GIMP_ITEM (mask))-offset_y);
      x2 = CLAMP (x2, -offset_x, gimp_item_width  (GIMP_ITEM (mask))-offset_x);
      y2 = CLAMP (y2, -offset_y, gimp_item_height (GIMP_ITEM (mask))-offset_y);
    }

  /*  If the calling procedure specified an undo step...  */
  if (push_undo)
    gimp_drawable_push_undo (drawable, undo_desc, 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, gimp_drawable_data (drawable), 
		       x1, y1, (x2 - x1), (y2 - y1), FALSE);
  pixel_region_init (&destPR, gimp_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)
    {
      gint 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, 
			 gimp_drawable_data (GIMP_DRAWABLE (mask)), 
			 mx, my, 
			 (x2 - x1), (y2 - y1), 
			 FALSE);

      combine_regions (&src1PR, src2PR, &destPR, &maskPR, NULL,
		       opacity * 255.999,
                       mode,
                       active_components,
                       operation);
    }
  else
    {
      combine_regions (&src1PR, src2PR, &destPR, NULL, NULL,
		       opacity * 255.999,
                       mode,
                       active_components,
                       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,
			  gboolean      push_undo, 
                          const gchar  *undo_desc,
			  gdouble       opacity,
			  PixelRegion  *maskPR,
			  gint          x, 
			  gint          y)
{
  GimpItem        *item;
  GimpChannel     *mask;
  gint             x1, y1, x2, y2;
  gint             offset_x, offset_y;
  PixelRegion      src1PR, destPR;
  PixelRegion      mask2PR, tempPR;
  guchar          *temp_data;
  CombinationMode  operation;
  gboolean         active_components[MAX_CHANNELS];

  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (GIMP_IS_DRAWABLE (drawable));

  item = GIMP_ITEM (drawable);

  /*  get the selection mask if one exists  */
  mask = (gimp_image_mask_is_empty (gimage)) ? NULL : gimp_image_get_mask (gimage);

  /*  configure the active channel array  */
  gimp_image_get_active_components (gimage, drawable, active_components);

  /*  determine what sort of operation is being attempted and
   *  if it's actually legal...
   */
  operation = gimp_image_get_combination_mode (gimp_drawable_type (drawable),
                                               src2PR->bytes);
  if (operation == -1)
    {
      g_warning ("%s: illegal parameters.", G_GNUC_PRETTY_FUNCTION);
      return;
    }

  /*  get the layer offsets  */
  gimp_item_offsets (item, &offset_x, &offset_y);

  /*  make sure the image application coordinates are within gimage bounds  */
  x1 = CLAMP (x, 0, gimp_item_width (item));
  y1 = CLAMP (y, 0, gimp_item_height (item));
  x2 = CLAMP (x + src2PR->w, 0, gimp_item_width (item));
  y2 = CLAMP (y + src2PR->h, 0, gimp_item_height (item));

  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, gimp_item_width (GIMP_ITEM (mask))-offset_x);
      y1 = CLAMP (y1, -offset_y, gimp_item_height(GIMP_ITEM (mask))-offset_y);
      x2 = CLAMP (x2, -offset_x, gimp_item_width (GIMP_ITEM (mask))-offset_x);
      y2 = CLAMP (y2, -offset_y, gimp_item_height(GIMP_ITEM (mask))-offset_y);
    }

  /*  If the calling procedure specified an undo step...  */
  if (push_undo)
    gimp_drawable_push_undo (drawable, undo_desc, 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, gimp_drawable_data (drawable),
		     x1, y1, (x2 - x1), (y2 - y1), FALSE);
  pixel_region_init (&destPR, gimp_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, 
			 gimp_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 = tempPR.w * tempPR.bytes;
      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 * 255.999,
                               active_components,
                               operation);

      g_free (temp_data);
    }
  else
    {
      combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL,
			       opacity * 255.999,
                               active_components,
                               operation);
    }
}


/*  parasites  */

GimpParasite *
gimp_image_parasite_find (const GimpImage *gimage, 
			  const gchar     *name)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimp_parasite_list_find (gimage->parasites, name);
}

static void
list_func (gchar          *key, 
	   GimpParasite   *p, 
	   gchar        ***cur)
{
  *(*cur)++ = (gchar *) g_strdup (key);
}

gchar **
gimp_image_parasite_list (const GimpImage *gimage, 
			  gint            *count)
{
  gchar **list;
  gchar **cur;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  *count = gimp_parasite_list_length (gimage->parasites);
  cur = list = g_new (gchar*, *count);

  gimp_parasite_list_foreach (gimage->parasites, (GHFunc) list_func, &cur);
  
  return list;
}

void
gimp_image_parasite_attach (GimpImage    *gimage, 
			    GimpParasite *parasite)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage) && parasite != NULL);

  /* only set the dirty bit manually if we can be saved and the new
     parasite differs from the current one and we aren't undoable */
  if (gimp_parasite_is_undoable (parasite))
    gimp_image_undo_push_image_parasite (gimage,
                                         _("Attach Paraite to Image"),
                                         parasite);

  /*  We used to push an cantundo on te stack here. This made the undo stack
      unusable (NULL on the stack) and prevented people from undoing after a 
      save (since most save plug-ins attach an undoable comment parasite).
      Now we simply attach the parasite without pushing an undo. That way it's
      undoable but does not block the undo system.   --Sven
   */

  gimp_parasite_list_add (gimage->parasites, parasite);

  if (gimp_parasite_has_flag (parasite, GIMP_PARASITE_ATTACH_PARENT))
    {
      gimp_parasite_shift_parent (parasite);
      gimp_parasite_attach (gimage->gimp, parasite);
    }
}

void
gimp_image_parasite_detach (GimpImage   *gimage, 
			    const gchar *parasite)
{
  GimpParasite *p;

  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (parasite != NULL);

  if (!(p = gimp_parasite_list_find (gimage->parasites, parasite)))
    return;

  if (gimp_parasite_is_undoable (p))
    gimp_image_undo_push_image_parasite_remove (gimage,
                                                _("Remove Parasite from Image"),
                                                gimp_parasite_name (p));

  gimp_parasite_list_remove (gimage->parasites, parasite);
}


/*  tattoos  */

GimpTattoo
gimp_image_get_new_tattoo (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), 0);

  gimage->tattoo_state++;

  if (gimage->tattoo_state <= 0)
    g_warning ("%s: Tattoo state corrupted "
	       "(integer overflow).", G_GNUC_PRETTY_FUNCTION);

  return gimage->tattoo_state;
}

GimpTattoo
gimp_image_get_tattoo_state (GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), 0);

  return gimage->tattoo_state;
}

gboolean
gimp_image_set_tattoo_state (GimpImage  *gimage, 
			     GimpTattoo  val)
{
  GList       *list;
  gboolean     retval = TRUE;
  GimpChannel *channel;
  GimpTattoo   maxval = 0;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);

  /* Check that the layer tatoos don't overlap with channel or vector ones */
  for (list = GIMP_LIST (gimage->layers)->list;
       list;
       list = g_list_next (list))
    {
      GimpTattoo ltattoo;

      ltattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
      if (ltattoo > maxval)
	maxval = ltattoo;

      if (gimp_image_get_channel_by_tattoo (gimage, ltattoo))
        retval = FALSE; /* Oopps duplicated tattoo in channel */

      if (gimp_image_get_vectors_by_tattoo (gimage, ltattoo))
        retval = FALSE; /* Oopps duplicated tattoo in vectors */
    }

  /* Now check that the channel and vectors tattoos don't overlap */
  for (list = GIMP_LIST (gimage->channels)->list;
       list;
       list = g_list_next (list))
    {
      GimpTattoo ctattoo;

      channel = (GimpChannel *) list->data;

      ctattoo = gimp_item_get_tattoo (GIMP_ITEM (channel));
      if (ctattoo > maxval)
	maxval = ctattoo;

      if (gimp_image_get_vectors_by_tattoo (gimage, ctattoo))
        retval = FALSE; /* Oopps duplicated tattoo in vectors */
    }

  /* Find the max tatto value in the vectors */
  for (list = GIMP_LIST (gimage->vectors)->list;
       list;
       list = g_list_next (list))
    {
      GimpTattoo vtattoo;

      vtattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
      if (vtattoo > maxval)
	maxval = vtattoo;
    }

  if (val < maxval)
    retval = FALSE;

  /* Must check if the state is valid */
  if (retval == TRUE)
    gimage->tattoo_state = val;

  return retval;
}


/*  layers / channels / vectors  */

GimpContainer *
gimp_image_get_layers (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->layers;
}

GimpContainer *
gimp_image_get_channels (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->channels;
}

GimpContainer *
gimp_image_get_vectors (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->vectors;
}

GimpDrawable *
gimp_image_active_drawable (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  /*  If there is an active channel (a saved selection, etc.),
   *  we ignore the active layer
   */
  if (gimage->active_channel)
    {
      return GIMP_DRAWABLE (gimage->active_channel);
    }
  else if (gimage->active_layer)
    {
      GimpLayer *layer;

      layer = gimage->active_layer;

      if (layer->mask && layer->mask->edit_mask)
	return GIMP_DRAWABLE (layer->mask);
      else
	return GIMP_DRAWABLE (layer);
    }

  return NULL;
}

GimpLayer *
gimp_image_get_active_layer (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->active_layer;
}

GimpChannel *
gimp_image_get_active_channel (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->active_channel;
}

GimpVectors *
gimp_image_get_active_vectors (const GimpImage *gimage)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  return gimage->active_vectors;
}

GimpLayer *
gimp_image_set_active_layer (GimpImage *gimage, 
			     GimpLayer *layer)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);

  /*  First, find the layer in the gimage
   *  If it isn't valid, find the first layer that is
   */
  if (! gimp_container_have (gimage->layers, GIMP_OBJECT (layer)))
    layer = (GimpLayer *) gimp_container_get_child_by_index (gimage->layers, 0);

  if (layer)
    {
      /*  Configure the layer stack to reflect this change  */
      gimage->layer_stack = g_slist_remove (gimage->layer_stack, layer);
      gimage->layer_stack = g_slist_prepend (gimage->layer_stack, layer);

      /*  invalidate the selection boundary because of a layer modification  */
      gimp_layer_invalidate_boundary (layer);
    }

  if (layer != gimage->active_layer)
    {
      gimage->active_layer = layer;

      g_signal_emit (gimage, gimp_image_signals[ACTIVE_LAYER_CHANGED], 0);

      if (gimage->active_channel)
	{
	  gimage->active_channel = NULL;

	  g_signal_emit (gimage, gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0);
	}
    }

  /*  return the layer  */
  return layer;
}

GimpChannel *
gimp_image_set_active_channel (GimpImage   *gimage, 
			       GimpChannel *channel)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
  g_return_val_if_fail (GIMP_IS_CHANNEL (channel), NULL);

  /*  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 (! gimp_container_have (gimage->channels, GIMP_OBJECT (channel)))
    channel = (GimpChannel *) gimp_container_get_child_by_index (gimage->channels, 0);

  if (channel != gimage->active_channel)
    {
      gimage->active_channel = channel;

      g_signal_emit (gimage, gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0);

      if (gimage->active_layer)
	{
	  gimage->active_layer = NULL;

	  g_signal_emit (gimage, gimp_image_signals[ACTIVE_LAYER_CHANGED], 0);
	}
    }

  /*  return the channel  */
  return channel;
}

GimpChannel *
gimp_image_unset_active_channel (GimpImage *gimage)
{
  GimpChannel *channel;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  channel = gimp_image_get_active_channel (gimage);

  if (channel)
    {
      gimage->active_channel = NULL;

      g_signal_emit (gimage, gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0);

      if (gimage->layer_stack)
	{
	  GimpLayer *layer;

	  layer = (GimpLayer *) gimage->layer_stack->data;

	  gimp_image_set_active_layer (gimage, layer);
	}
    }

  return channel;
}

GimpVectors *
gimp_image_set_active_vectors (GimpImage   *gimage, 
			       GimpVectors *vectors)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
  g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);

  /*  First, find the vectors
   *  If it doesn't exist, find the first vectors that does
   */
  if (! gimp_container_have (gimage->vectors, GIMP_OBJECT (vectors)))
    vectors = (GimpVectors *)
      gimp_container_get_child_by_index (gimage->vectors, 0);

  if (vectors != gimage->active_vectors)
    {
      gimage->active_vectors = vectors;

      g_signal_emit (gimage, gimp_image_signals[ACTIVE_VECTORS_CHANGED], 0);
    }

  return vectors;
}

void
gimp_image_active_layer_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[ACTIVE_LAYER_CHANGED], 0);
}

void
gimp_image_active_channel_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0);
}

void
gimp_image_active_vectors_changed (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  g_signal_emit (gimage, gimp_image_signals[ACTIVE_VECTORS_CHANGED], 0);
}

gint            
gimp_image_get_layer_index (const GimpImage   *gimage,
			    const GimpLayer   *layer)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), -1);

  return gimp_container_get_child_index (gimage->layers, 
					 GIMP_OBJECT (layer));
}

gint            
gimp_image_get_channel_index (const GimpImage   *gimage,
			      const GimpChannel *channel)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);
  g_return_val_if_fail (GIMP_IS_CHANNEL (channel), -1);

  return gimp_container_get_child_index (gimage->channels, 
					 GIMP_OBJECT (channel));
}

gint            
gimp_image_get_vectors_index (const GimpImage   *gimage,
			      const GimpVectors *vectors)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);
  g_return_val_if_fail (GIMP_IS_VECTORS (vectors), -1);

  return gimp_container_get_child_index (gimage->vectors,
					 GIMP_OBJECT (vectors));
}

GimpLayer *
gimp_image_get_layer_by_tattoo (const GimpImage *gimage,
				GimpTattoo       tattoo)
{
  GimpLayer *layer;
  GList     *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->layers)->list; 
       list; 
       list = g_list_next (list))
    {
      layer = (GimpLayer *) list->data;

      if (gimp_item_get_tattoo (GIMP_ITEM (layer)) == tattoo)
	return layer;
    }

  return NULL;
}

GimpChannel *
gimp_image_get_channel_by_tattoo (const GimpImage *gimage, 
				  GimpTattoo       tattoo)
{
  GimpChannel *channel;
  GList       *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->channels)->list; 
       list; 
       list = g_list_next (list))
    {
      channel = (GimpChannel *) list->data;

      if (gimp_item_get_tattoo (GIMP_ITEM (channel)) == tattoo)
	return channel;
    }

  return NULL;
}

GimpVectors *
gimp_image_get_vectors_by_tattoo (const GimpImage *gimage, 
				  GimpTattoo       tattoo)
{
  GimpVectors *vectors;
  GList       *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->vectors)->list;
       list;
       list = g_list_next (list))
    {
      vectors = (GimpVectors *) list->data;

      if (gimp_item_get_tattoo (GIMP_ITEM (vectors)) == tattoo)
	return vectors;
    }

  return NULL;
}

GimpLayer *
gimp_image_get_layer_by_name (const GimpImage *gimage,
                              const gchar     *name)
{
  GimpLayer *layer;
  GList     *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->layers)->list;
       list;
       list = g_list_next (list))
    {
      layer = (GimpLayer *) list->data;

      if (! strcmp (gimp_object_get_name (GIMP_OBJECT (layer)), name))
        return layer;
    }

  return NULL;
}

GimpChannel *
gimp_image_get_channel_by_name (const GimpImage *gimage,
				const gchar     *name)
{
  GimpChannel *channel;
  GList       *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->channels)->list;
       list;
       list = g_list_next (list))
    {
      channel = (GimpChannel *) list->data;

      if (! strcmp (gimp_object_get_name (GIMP_OBJECT (channel)), name))
        return channel;
    }

  return NULL;
}

GimpVectors *
gimp_image_get_vectors_by_name (const GimpImage *gimage,
				const gchar     *name)
{
  GimpVectors *vectors;
  GList       *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->vectors)->list;
       list;
       list = g_list_next (list))
    {
      vectors = (GimpVectors *) list->data;

      if (! strcmp (gimp_object_get_name (GIMP_OBJECT (vectors)), name))
        return vectors;
    }

  return NULL;
}

gboolean
gimp_image_add_layer (GimpImage *gimage, 
		      GimpLayer *layer, 
		      gint       position)
{
  gboolean alpha_changed = FALSE;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);

  if (GIMP_ITEM (layer)->gimage != NULL && 
      GIMP_ITEM (layer)->gimage != gimage) 
    {
      g_warning ("%s: attempting to add layer to wrong image.",
		 G_GNUC_PRETTY_FUNCTION);
      return FALSE;
    }

  if (gimp_container_have (gimage->layers, GIMP_OBJECT (layer)))
    {
      g_warning ("%s: trying to add layer to image twice.",
		 G_GNUC_PRETTY_FUNCTION);
      return FALSE;
    }

  gimp_image_undo_push_layer_add (gimage, _("Add Layer"),
                                  layer, 0,
                                  gimp_image_get_active_layer (gimage));

  /*  If the layer is a floating selection, set the ID  */
  if (gimp_layer_is_floating_sel (layer))
    gimage->floating_sel = layer;

  /*  let the layer know about the gimage  */
  gimp_item_set_image (GIMP_ITEM (layer), gimage);

  /*  If the layer has a mask, set the mask's gimage  */
  if (layer->mask)
    gimp_item_set_image (GIMP_ITEM (layer->mask), gimage);

  /*  add the layer to the list at the specified position  */
  if (position == -1)
    {
      GimpLayer *active_layer;

      active_layer = gimp_image_get_active_layer (gimage);

      if (active_layer)
	{
	  position = gimp_container_get_child_index (gimage->layers, 
						     GIMP_OBJECT (active_layer));
	}
      else
	{
	  position = 0;
	}
    }

  /*  If there is a floating selection (and this isn't it!),
   *  make sure the insert position is greater than 0
   */
  if (position == 0                    &&
      gimp_image_floating_sel (gimage) &&
      (gimage->floating_sel != layer))
    {
      position = 1;
    }

  if (gimp_container_num_children (gimage->layers) == 1 &&
      ! gimp_drawable_has_alpha (GIMP_LIST (gimage->layers)->list->data))
    {
      alpha_changed = TRUE;
    }

  /*  Don't add at a non-existing index  */
  if (position > gimp_container_num_children (gimage->layers))
    position = gimp_container_num_children (gimage->layers);

  gimp_container_insert (gimage->layers, GIMP_OBJECT (layer), position);
  g_object_unref (layer);

  /*  notify the layers dialog of the currently active layer  */
  gimp_image_set_active_layer (gimage, layer);

  /*  update the new layer's area  */
  gimp_drawable_update (GIMP_DRAWABLE (layer),
			0, 0,
			gimp_item_width  (GIMP_ITEM (layer)),
			gimp_item_height (GIMP_ITEM (layer)));

  if (alpha_changed)
    gimp_image_alpha_changed (gimage);

  return TRUE;
}

void
gimp_image_remove_layer (GimpImage *gimage, 
			 GimpLayer *layer)
{
  gint x, y, w, h;

  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (GIMP_IS_LAYER (layer));

  g_return_if_fail (gimp_container_have (gimage->layers, GIMP_OBJECT (layer)));

  gimp_image_undo_push_layer_remove (gimage, _("Remove Layer"),
                                     layer,
                                     gimp_container_get_child_index (gimage->layers, 
                                                                     GIMP_OBJECT (layer)),
                                     gimp_image_get_active_layer (gimage));

  g_object_ref (layer);

  gimp_container_remove (gimage->layers, GIMP_OBJECT (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 (layer == gimp_image_get_active_layer (gimage))
    {
      if (gimage->layer_stack)
	{
	  gimp_image_set_active_layer (gimage, gimage->layer_stack->data);
	}
      else
	{
	  gimage->active_layer = NULL;
	  g_signal_emit (gimage, gimp_image_signals[ACTIVE_LAYER_CHANGED], 0);
	}
    }

  /* Send out REMOVED signal from layer */
  gimp_item_removed (GIMP_ITEM (layer));

  gimp_item_offsets (GIMP_ITEM (layer), &x, &y);
  w = gimp_item_width  (GIMP_ITEM (layer));
  h = gimp_item_height (GIMP_ITEM (layer));

  g_object_unref (layer);

  gimp_image_update (gimage, x, y, w, h);

  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage));

  if (gimp_container_num_children (gimage->layers) == 1 &&
      ! gimp_drawable_has_alpha (GIMP_LIST (gimage->layers)->list->data))
    {
      gimp_image_alpha_changed (gimage);
    }
}

gboolean
gimp_image_raise_layer (GimpImage *gimage, 
			GimpLayer *layer)
{
  gint curpos;
  
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);

  curpos = gimp_container_get_child_index (gimage->layers, 
					   GIMP_OBJECT (layer));

  /* is this the top layer already? */
  if (curpos == 0)
    {
      g_message (_("Layer cannot be raised higher."));
      return FALSE;
    }
  
  return gimp_image_position_layer (gimage, layer, curpos - 1,
                                    TRUE, _("Raise Layer"));
}

gboolean
gimp_image_lower_layer (GimpImage *gimage, 
			GimpLayer *layer)
{
  gint curpos;
  gint length;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);

  curpos = gimp_container_get_child_index (gimage->layers, 
					   GIMP_OBJECT (layer));
  
  /* is this the bottom layer already? */
  length = gimp_container_num_children (gimage->layers);
  if (curpos >= length - 1)
    {
      g_message (_("Layer cannot be lowered more."));
      return FALSE;
    }
  
  return gimp_image_position_layer (gimage, layer, curpos + 1,
                                    TRUE, _("Lower Layer"));
}

gboolean
gimp_image_raise_layer_to_top (GimpImage *gimage, 
			       GimpLayer *layer)
{
  gint curpos;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);

  curpos = gimp_container_get_child_index (gimage->layers, 
					   GIMP_OBJECT (layer));
  
  if (curpos == 0)
    {
      g_message (_("Layer is already on top."));
      return FALSE;
    }
  
  if (! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
    {
      g_message (_("Cannot raise a layer without alpha."));
      return FALSE;
    }
  
  return gimp_image_position_layer (gimage, layer, 0,
                                    TRUE, _("Raise Layer to Top"));
}

gboolean
gimp_image_lower_layer_to_bottom (GimpImage *gimage, 
				  GimpLayer *layer)
{
  gint curpos;
  gint length;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);

  curpos = gimp_container_get_child_index (gimage->layers, 
					   GIMP_OBJECT (layer));
 
  length = gimp_container_num_children (gimage->layers);

  if (curpos >= length - 1)
    {
      g_message (_("Layer is already on the bottom."));
      return FALSE;
    }
  
  return gimp_image_position_layer (gimage, layer, length - 1,
                                    TRUE, _("Lower Layer to Bottom"));
}

gboolean
gimp_image_position_layer (GimpImage   *gimage, 
			   GimpLayer   *layer,
			   gint         new_index,
			   gboolean     push_undo,
                           const gchar *undo_desc)
{
  gint off_x, off_y;
  gint index;
  gint num_layers;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);

  index = gimp_container_get_child_index (gimage->layers, 
					  GIMP_OBJECT (layer));
  if (index < 0)
    return FALSE;

  num_layers = gimp_container_num_children (gimage->layers);

  if (new_index < 0)
    new_index = 0;

  if (new_index >= num_layers)
    new_index = num_layers - 1;

  if (new_index == index)
    return TRUE;

  /* check if we want to move it below a bottom layer without alpha */
  if (new_index == num_layers - 1)
    {
      GimpLayer *tmp;
      
      tmp = (GimpLayer *) gimp_container_get_child_by_index (gimage->layers, 
							     num_layers - 1);

      if (new_index == num_layers - 1 &&
	  ! gimp_drawable_has_alpha (GIMP_DRAWABLE (tmp)))
	{
	  g_message (_("Layer \"%s\" has no alpha.\nLayer was placed above it."),
		     GIMP_OBJECT (tmp)->name);
	  new_index--;
	}
    }

  if (push_undo)
    gimp_image_undo_push_layer_reposition (gimage, undo_desc, layer);

  gimp_container_reorder (gimage->layers, GIMP_OBJECT (layer), new_index);

  gimp_item_offsets (GIMP_ITEM (layer), &off_x, &off_y);

  gimp_image_update (gimage,
		     off_x, off_y,
		     gimp_item_width  (GIMP_ITEM (layer)),
		     gimp_item_height (GIMP_ITEM (layer)));

  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage));

  return TRUE;
}

gboolean
gimp_image_add_channel (GimpImage   *gimage, 
			GimpChannel *channel, 
			gint         position)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);

  if (GIMP_ITEM (channel)->gimage != NULL &&
      GIMP_ITEM (channel)->gimage != gimage)
    {
      g_warning ("%s: attempting to add channel to wrong image.",
		 G_GNUC_PRETTY_FUNCTION);
      return FALSE;
    }

  if (gimp_container_have (gimage->channels, GIMP_OBJECT (channel)))
    {
      g_warning ("%s: trying to add channel to image twice.",
		 G_GNUC_PRETTY_FUNCTION);
      return FALSE;
    }

  gimp_image_undo_push_channel_add (gimage, _("Add Channel"),
                                    channel, 0,
                                    gimp_image_get_active_channel (gimage));

  /*  add the layer to the list at the specified position  */
  if (position == -1)
    {
      GimpChannel *active_channel;

      active_channel = gimp_image_get_active_channel (gimage);

      if (active_channel)
	{
	  position = gimp_container_get_child_index (gimage->channels,
						     GIMP_OBJECT (active_channel));
	}
      else
	{
	  position = 0;
	}
    }

  /*  Don't add at a non-existing index  */
  if (position > gimp_container_num_children (gimage->channels))
    position = gimp_container_num_children (gimage->channels);

  gimp_container_insert (gimage->channels, GIMP_OBJECT (channel), position);
  g_object_unref (channel);

  /*  notify this gimage of the currently active channel  */
  gimp_image_set_active_channel (gimage, channel);

  /*  if channel is visible, update the image  */
  if (gimp_drawable_get_visible (GIMP_DRAWABLE (channel)))
    gimp_drawable_update (GIMP_DRAWABLE (channel), 
			  0, 0, 
			  gimp_item_width  (GIMP_ITEM (channel)), 
			  gimp_item_height (GIMP_ITEM (channel)));

  return TRUE;
}

void
gimp_image_remove_channel (GimpImage   *gimage, 
			   GimpChannel *channel)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (GIMP_IS_CHANNEL (channel));

  g_return_if_fail (gimp_container_have (gimage->channels, 
					 GIMP_OBJECT (channel)));

  gimp_image_undo_push_channel_remove (gimage, _("Remove Channel"),
                                       channel,
                                       gimp_container_get_child_index (gimage->channels,
                                                                       GIMP_OBJECT (channel)),
                                       gimp_image_get_active_channel (gimage));

  g_object_ref (channel);

  gimp_container_remove (gimage->channels, GIMP_OBJECT (channel));

  /* Send out REMOVED signal from channel */
  gimp_item_removed (GIMP_ITEM (channel));

  if (channel == gimp_image_get_active_channel (gimage))
    {
      if (gimp_container_num_children (gimage->channels) > 0)
	{
	  gimp_image_set_active_channel
	    (gimage,
	     GIMP_CHANNEL (gimp_container_get_child_by_index (gimage->channels,
							      0)));
	}
      else
	{
	  gimp_image_unset_active_channel (gimage);
	}
    }

  g_object_unref (channel);

  gimp_image_update (gimage, 0, 0, gimage->width, gimage->height);

  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage));
}

gboolean
gimp_image_raise_channel (GimpImage   *gimage, 
			  GimpChannel *channel)
{
  gint index;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);  

  index = gimp_container_get_child_index (gimage->channels, 
					  GIMP_OBJECT (channel));
  if (index == 0)
    {
      g_message (_("Channel cannot be raised higher."));
      return FALSE;
    }

  return gimp_image_position_channel (gimage, channel, index - 1,
                                      TRUE, _("Raise Channel"));
}

gboolean
gimp_image_lower_channel (GimpImage   *gimage, 
			  GimpChannel *channel)
{
  gint index;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);  
  
  index = gimp_container_get_child_index (gimage->channels, 
					  GIMP_OBJECT (channel));
  if (index == gimp_container_num_children (gimage->channels) - 1)
    {
      g_message (_("Channel cannot be lowered more."));
      return FALSE;
    }

  return gimp_image_position_channel (gimage, channel, index + 1,
                                      TRUE, _("Lower Channel"));
}

gboolean
gimp_image_position_channel (GimpImage   *gimage, 
			     GimpChannel *channel,
			     gint         new_index,
                             gboolean     push_undo,
                             const gchar *undo_desc)
{
  gint index;
  gint num_channels;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);  

  index = gimp_container_get_child_index (gimage->channels, 
					  GIMP_OBJECT (channel));
  if (index < 0)
    return FALSE;

  num_channels = gimp_container_num_children (gimage->channels);

  new_index = CLAMP (new_index, 0, num_channels - 1);

  if (new_index == index)
    return TRUE;

  if (push_undo)
    gimp_image_undo_push_channel_reposition (gimage, undo_desc, channel);

  gimp_container_reorder (gimage->channels, 
			  GIMP_OBJECT (channel), new_index);

  gimp_drawable_update (GIMP_DRAWABLE (channel),
			0, 0,
			gimp_item_width  (GIMP_ITEM (channel)),
			gimp_item_height (GIMP_ITEM (channel)));

  return TRUE;
}

gboolean
gimp_image_add_vectors (GimpImage   *gimage, 
			GimpVectors *vectors,
			gint         position)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);

  if (GIMP_ITEM (vectors)->gimage != NULL &&
      GIMP_ITEM (vectors)->gimage != gimage)
    {
      g_warning ("%s: attempting to add vectors to wrong image.",
		 G_GNUC_PRETTY_FUNCTION);
      return FALSE;
    }

  if (gimp_container_have (gimage->vectors, GIMP_OBJECT (vectors)))
    {
      g_warning ("%s: trying to add vectors to image twice.",
		 G_GNUC_PRETTY_FUNCTION);
      return FALSE;
    }

  gimp_image_undo_push_vectors_add (gimage, _("Add Path"),
                                    vectors, 0,
                                    gimp_image_get_active_vectors (gimage));

  gimp_item_set_image (GIMP_ITEM (vectors), gimage);

  /*  add the layer to the list at the specified position  */
  if (position == -1)
    {
      GimpVectors *active_vectors;

      active_vectors = gimp_image_get_active_vectors (gimage);

      if (active_vectors)
	{
	  position = gimp_container_get_child_index (gimage->vectors,
						     GIMP_OBJECT (active_vectors));
	}
      else
	{
	  position = 0;
	}
    }

  /*  Don't add at a non-existing index  */
  if (position > gimp_container_num_children (gimage->vectors))
    position = gimp_container_num_children (gimage->vectors);

  gimp_container_insert (gimage->vectors, GIMP_OBJECT (vectors), position);
  g_object_unref (vectors);

  /*  notify this gimage of the currently active vectors  */
  gimp_image_set_active_vectors (gimage, vectors);

  return TRUE;
}

void
gimp_image_remove_vectors (GimpImage   *gimage, 
			   GimpVectors *vectors)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));
  g_return_if_fail (GIMP_IS_VECTORS (vectors));

  g_return_if_fail (gimp_container_have (gimage->vectors,
					 GIMP_OBJECT (vectors)));

  gimp_image_undo_push_vectors_remove (gimage, _("Remove Path"),
                                       vectors,
                                       gimp_container_get_child_index (gimage->vectors,
                                                                       GIMP_OBJECT (vectors)),
                                       gimp_image_get_active_vectors (gimage));

  g_object_ref (vectors);

  gimp_container_remove (gimage->vectors, GIMP_OBJECT (vectors));

  gimp_item_set_image (GIMP_ITEM (vectors), NULL);

  /* Send out REMOVED signal from vectors */
  gimp_item_removed (GIMP_ITEM (vectors));

  if (vectors == gimp_image_get_active_vectors (gimage))
    {
      if (gimp_container_num_children (gimage->vectors) > 0)
	{
	  gimp_image_set_active_vectors
	    (gimage,
	     GIMP_VECTORS (gimp_container_get_child_by_index (gimage->vectors,
							      0)));
	}
      else
        {
          gimage->active_vectors = NULL;
          g_signal_emit (gimage, gimp_image_signals[ACTIVE_VECTORS_CHANGED], 0);
        }
    }

  g_object_unref (vectors);
}

gboolean
gimp_image_raise_vectors (GimpImage   *gimage, 
			  GimpVectors *vectors)
{
  gint index;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);  

  index = gimp_container_get_child_index (gimage->vectors,
					  GIMP_OBJECT (vectors));
  if (index == 0)
    {
      g_message (_("Path cannot be raised higher."));
      return FALSE;
    }

  return gimp_image_position_vectors (gimage, vectors, index - 1,
                                      TRUE, _("Raise Path"));
}

gboolean
gimp_image_lower_vectors (GimpImage   *gimage, 
			  GimpVectors *vectors)
{
  gint index;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);  

  index = gimp_container_get_child_index (gimage->vectors,
					  GIMP_OBJECT (vectors));
  if (index == gimp_container_num_children (gimage->vectors) - 1)
    {
      g_message (_("Path cannot be lowered more."));
      return FALSE;
    }

  return gimp_image_position_vectors (gimage, vectors, index + 1,
                                      TRUE, _("Lower Path"));
}

gboolean
gimp_image_position_vectors (GimpImage   *gimage, 
			     GimpVectors *vectors,
			     gint         new_index,
                             gboolean     push_undo,
                             const gchar *undo_desc)
{
  gint index;
  gint num_vectors;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);  

  index = gimp_container_get_child_index (gimage->vectors,
					  GIMP_OBJECT (vectors));
  if (index < 0)
    return FALSE;

  num_vectors = gimp_container_num_children (gimage->vectors);

  new_index = CLAMP (new_index, 0, num_vectors - 1);

  if (new_index == index)
    return TRUE;

  if (push_undo)
    gimp_image_undo_push_vectors_reposition (gimage, undo_desc, vectors);

  gimp_container_reorder (gimage->vectors,
			  GIMP_OBJECT (vectors), new_index);

  return TRUE;
}

gboolean
gimp_image_layer_boundary (const GimpImage  *gimage, 
			   BoundSeg        **segs, 
			   gint             *n_segs)
{
  GimpLayer *layer;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
  g_return_val_if_fail (segs != NULL, FALSE);
  g_return_val_if_fail (n_segs != NULL, FALSE);

  /*  The second boundary corresponds to the active layer's
   *  perimeter...
   */
  layer = gimp_image_get_active_layer (gimage);

  if (layer)
    {
      *segs = gimp_layer_boundary (layer, n_segs);
      return TRUE;
    }
  else
    {
      *segs = NULL;
      *n_segs = 0;
      return FALSE;
    }
}

GimpLayer *
gimp_image_pick_correlate_layer (const GimpImage *gimage, 
				 gint             x, 
				 gint             y)
{
  GimpLayer *layer;
  GList     *list;

  g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);

  for (list = GIMP_LIST (gimage->layers)->list; 
       list; 
       list = g_list_next (list))
    {
      layer = (GimpLayer *) list->data;

      if (gimp_layer_pick_correlate (layer, x, y))
	return layer;
    }

  return NULL;
}

void
gimp_image_invalidate_layer_previews (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  gimp_container_foreach (gimage->layers, 
			  (GFunc) gimp_viewable_invalidate_preview, 
			  NULL);
}

void
gimp_image_invalidate_channel_previews (GimpImage *gimage)
{
  g_return_if_fail (GIMP_IS_IMAGE (gimage));

  gimp_container_foreach (gimage->channels, 
			  (GFunc) gimp_viewable_invalidate_preview, 
			  NULL);
}
