/* 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 #include #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 "base/tile.h" #include "paint-funcs/paint-funcs.h" #include "gimp.h" #include "gimpcontext.h" #include "gimpcoreconfig.h" #include "gimpimage.h" #include "gimpimage-colorhash.h" #include "gimpimage-mask.h" #include "gimpimage-undo.h" #include "gimplayer.h" #include "gimplayermask.h" #include "gimplist.h" #include "gimpmarshal.h" #include "gimpparasite.h" #include "gimpparasitelist.h" #include "gimpundostack.h" #include "floating_sel.h" #include "path.h" #include "undo.h" #include "libgimp/gimpintl.h" #ifdef DEBUG #define TRC(x) printf x #else #define TRC(x) #endif #define GUIDE_EPSILON 5 /* Local function declarations */ 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 void gimp_image_invalidate_preview (GimpViewable *viewable); static void gimp_image_size_changed (GimpViewable *viewable); static void gimp_image_real_colormap_changed (GimpImage *gimage, gint ncol); static TempBuf *gimp_image_get_preview (GimpViewable *gimage, gint width, gint height); static TempBuf *gimp_image_get_new_preview (GimpViewable *viewable, gint width, gint height); static void gimp_image_free_projection (GimpImage *gimage); static void gimp_image_allocate_shadow (GimpImage *gimage, gint width, gint height, gint bpp); static void gimp_image_allocate_projection (GimpImage *gimage); static void gimp_image_construct_layers (GimpImage *gimage, gint x, gint y, gint w, gint h); static void gimp_image_construct_channels (GimpImage *gimage, gint x, gint y, gint w, gint h); static void gimp_image_initialize_projection (GimpImage *gimage, gint x, gint y, gint w, gint h); static void gimp_image_get_active_channels (GimpImage *gimage, GimpDrawable *drawable, gint *active); static void gimp_image_construct (GimpImage *gimage, gint x, gint y, gint w, gint h); /* projection functions */ static void project_intensity (GimpImage *gimage, GimpLayer *layer, PixelRegion *src, PixelRegion *dest, PixelRegion *mask); static void project_intensity_alpha (GimpImage *gimage, GimpLayer *layer, PixelRegion *src, PixelRegion *dest, PixelRegion *mask); static void project_indexed (GimpImage *gimage, GimpLayer *layer, PixelRegion *src, PixelRegion *dest); static void project_indexed_alpha (GimpImage *gimage, GimpLayer *layer, PixelRegion *src, PixelRegion *dest, PixelRegion *mask); static void project_channel (GimpImage *gimage, GimpChannel *channel, PixelRegion *src, PixelRegion *src2); /* * Global variables */ static gint 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 */ enum { MODE_CHANGED, ALPHA_CHANGED, FLOATING_SELECTION_CHANGED, ACTIVE_LAYER_CHANGED, ACTIVE_CHANNEL_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_EVENT, LAST_SIGNAL }; 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; } 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[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__INT, G_TYPE_NONE, 1, G_TYPE_INT); 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__INT, G_TYPE_NONE, 1, G_TYPE_INT); 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__INT, G_TYPE_NONE, 1, G_TYPE_INT); 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_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__INT, G_TYPE_NONE, 1, G_TYPE_INT); object_class->dispose = gimp_image_dispose; object_class->finalize = gimp_image_finalize; gimp_object_class->name_changed = gimp_image_name_changed; viewable_class->invalidate_preview = gimp_image_invalidate_preview; viewable_class->size_changed = gimp_image_size_changed; viewable_class->get_preview = gimp_image_get_preview; viewable_class->get_new_preview = gimp_image_get_new_preview; klass->mode_changed = NULL; klass->alpha_changed = NULL; klass->floating_selection_changed = NULL; klass->active_layer_changed = NULL; klass->active_channel_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_event = NULL; klass->undo = gimp_image_undo; klass->redo = gimp_image_redo; gimp_image_color_hash_init (); } /* static functions */ 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 = 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 = RGBA_GIMAGE; 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->layer_stack = NULL; gimage->active_layer = NULL; gimage->active_channel = NULL; gimage->floating_sel = NULL; gimage->selection_mask = NULL; gimage->parasites = gimp_parasite_list_new (); gimage->paths = NULL; gimage->qmask_state = 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 = NULL; gimage->redo_stack = NULL; gimage->undo_bytes = 0; gimage->undo_levels = 0; gimage->group_count = 0; gimage->pushing_undo_group = UNDO_NULL; gimage->new_undo_stack = gimp_undo_stack_new (gimage); gimage->new_redo_stack = gimp_undo_stack_new (gimage); gimage->comp_preview = NULL; gimage->comp_preview_valid = FALSE; } static void gimp_image_dispose (GObject *object) { GimpImage *gimage; gimage = GIMP_IMAGE (object); 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_free_projection (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 (G_OBJECT (gimage->layers)); gimage->layers = NULL; } if (gimage->channels) { g_object_unref (G_OBJECT (gimage->channels)); gimage->channels = NULL; } if (gimage->layer_stack) { g_slist_free (gimage->layer_stack); gimage->layer_stack = NULL; } if (gimage->selection_mask) { g_object_unref (G_OBJECT (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 (G_OBJECT (gimage->parasites)); gimage->parasites = NULL; } if (gimage->guides) { g_list_foreach (gimage->guides, (GFunc) g_free, NULL); g_list_free (gimage->guides); gimage->guides = NULL; } if (gimage->new_undo_stack) { g_object_unref (G_OBJECT (gimage->new_undo_stack)); gimage->new_undo_stack = NULL; } if (gimage->new_redo_stack) { g_object_unref (G_OBJECT (gimage->new_redo_stack)); gimage->new_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 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; } static void gimp_image_size_changed (GimpViewable *viewable) { GimpImage *gimage; if (GIMP_VIEWABLE_CLASS (parent_class)->size_changed) GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable); gimage = GIMP_IMAGE (viewable); gimp_image_invalidate_layer_previews (gimage); gimp_image_invalidate_channel_previews (gimage); } static void gimp_image_real_colormap_changed (GimpImage *gimage, gint ncol) { if (gimp_image_base_type (gimage) == INDEXED) gimp_image_color_hash_invalidate (gimage, ncol); } 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_assert_not_reached (); } /* allocate the new projection */ gimage->projection = tile_manager_new (gimage->width, gimage->height, gimage->proj_bytes); tile_manager_set_user_data (gimage->projection, (void *) gimage); tile_manager_set_validate_proc (gimage->projection, gimp_image_validate); } static void gimp_image_free_projection (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); if (gimage->projection) { tile_manager_destroy (gimage->projection); gimage->projection = NULL; } } static void gimp_image_allocate_shadow (GimpImage *gimage, gint width, gint height, gint bpp) { /* allocate the new projection */ gimage->shadow = tile_manager_new (width, height, bpp); } /* function definitions */ 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_units; switch (base_type) { case RGB: case GRAY: break; case INDEXED: /* always allocate 256 colors for the colormap */ gimage->num_cols = 0; gimage->cmap = (guchar *) g_malloc0 (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); return gimage; } gint gimp_image_get_ID (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_filename (GimpImage *gimage, const gchar *filename) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); gimp_object_set_name (GIMP_OBJECT (gimage), filename); } 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)) { undo_push_resolution (gimage); 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_set_unit (GimpImage *gimage, GimpUnit unit) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); if (gimage->unit != unit) { undo_push_resolution (gimage); 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_set_qmask_state (GimpImage *gimage, gboolean qmask_state) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); if (qmask_state != gimage->qmask_state) { gimage->qmask_state = qmask_state ? TRUE : FALSE; gimp_image_qmask_changed (gimage); } } gboolean gimp_image_get_qmask_state (const GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); return gimage->qmask_state; } 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; } 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; } void gimp_image_resize (GimpImage *gimage, gint new_width, gint new_height, gint offset_x, gint offset_y) { GimpChannel *channel; GimpLayer *layer; GimpLayer *floating_layer; GList *list; GList *guide_list; g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_return_if_fail (new_width > 0 && new_height > 0); gimp_set_busy (gimage->gimp); /* Get the floating layer if one exists */ floating_layer = gimp_image_floating_sel (gimage); undo_push_group_start (gimage, IMAGE_RESIZE_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 */ for (list = GIMP_LIST (gimage->channels)->list; list; list = g_list_next (list)) { channel = (GimpChannel *) list->data; gimp_channel_resize (channel, new_width, new_height, offset_x, offset_y); } /* Reposition or remove any guides */ guide_list = gimage->guides; while (guide_list) { GimpGuide *guide; guide = (GimpGuide *) guide_list->data; guide_list = g_list_next (guide_list); switch (guide->orientation) { case ORIENTATION_HORIZONTAL: undo_push_guide (gimage, guide); guide->position += offset_y; if (guide->position < 0 || guide->position > new_height) gimp_image_delete_guide (gimage, guide); break; case ORIENTATION_VERTICAL: undo_push_guide (gimage, guide); guide->position += offset_x; if (guide->position < 0 || guide->position > new_width) gimp_image_delete_guide (gimage, guide); break; default: g_error ("Unknown guide orientation\n"); } } /* Don't forget the selection mask! */ gimp_channel_resize (gimage->selection_mask, new_width, new_height, offset_x, offset_y); gimage_mask_invalidate (gimage); /* Reposition all layers */ for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; gimp_layer_translate (layer, offset_x, offset_y); } /* 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); undo_push_group_end (gimage); gimp_viewable_size_changed (GIMP_VIEWABLE (gimage)); gimp_unset_busy (gimage->gimp); } void gimp_image_scale (GimpImage *gimage, gint new_width, gint new_height, GimpProgressFunc progress_func, gpointer progress_data) { GimpChannel *channel; GimpLayer *layer; GimpLayer *floating_layer; GList *list; GSList *remove = NULL; GSList *slist; GimpGuide *guide; gint old_width; gint old_height; gdouble img_scale_w = 1.0; gdouble img_scale_h = 1.0; gint num_channels; gint num_layers; gint progress_current = 1; g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_return_if_fail (new_width > 0 && new_height > 0); gimp_set_busy (gimage->gimp); num_channels = gimage->channels->num_children; num_layers = gimage->layers->num_children; /* Get the floating layer if one exists */ floating_layer = gimp_image_floating_sel (gimage); undo_push_group_start (gimage, IMAGE_SCALE_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; img_scale_w = (gdouble) new_width / (gdouble) old_width; img_scale_h = (gdouble) new_height / (gdouble) old_height; /* Scale all channels */ for (list = GIMP_LIST (gimage->channels)->list; list; list = g_list_next (list)) { channel = (GimpChannel *) list->data; gimp_channel_scale (channel, new_width, new_height); if (progress_func) { (* progress_func) (0, num_channels + num_layers, progress_current++, progress_data); } } /* Don't forget the selection mask! */ /* if (channel_is_empty(gimage->selection_mask)) gimp_channel_resize(gimage->selection_mask, new_width, new_height, 0, 0) else */ gimp_channel_scale (gimage->selection_mask, new_width, new_height); gimage_mask_invalidate (gimage); /* Scale all layers */ for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; if (! gimp_layer_scale_by_factors (layer, img_scale_w, img_scale_h)) { /* Since 0 < img_scale_w, img_scale_h, failure due to one or more * vanishing scaled layer dimensions. Implicit delete implemented * here. Upstream warning implemented in resize_check_layer_scaling() * [resize.c line 1295], which offers the user the chance to bail out. */ remove = g_slist_append (remove, layer); } if (progress_func) { (* progress_func) (0, num_channels + num_layers, progress_current++, progress_data); } } /* We defer removing layers lost to scaling until now so as not to mix * the operations of iterating over and removal from gimage->layers. */ for (slist = remove; slist; slist = g_slist_next (slist)) { layer = slist->data; gimp_image_remove_layer (gimage, layer); } g_slist_free (remove); /* Scale any Guides */ for (list = gimage->guides; list; list = g_list_next (list)) { guide = (GimpGuide *) list->data; switch (guide->orientation) { case ORIENTATION_HORIZONTAL: undo_push_guide (gimage, guide); guide->position = (guide->position * new_height) / old_height; break; case ORIENTATION_VERTICAL: undo_push_guide (gimage, guide); guide->position = (guide->position * new_width) / old_width; break; default: g_error("Unknown guide orientation II.\n"); } } /* 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); undo_push_group_end (gimage); gimp_viewable_size_changed (GIMP_VIEWABLE (gimage)); gimp_unset_busy (gimage->gimp); } /** * gimp_image_check_scaling: * @gimage: A #GimpImage. * @new_width: The new width. * @new_height: The new height. * * Inventory the layer list in gimage and return #TRUE if, after * scaling, they all retain positive x and y pixel dimensions. * * Return value: #TRUE if scaling the image will shrink none of it's * layers completely away. **/ gboolean gimp_image_check_scaling (const GimpImage *gimage, gint new_width, gint new_height) { GList *list; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { GimpLayer *layer; layer = (GimpLayer *) list->data; if (! gimp_layer_check_scaling (layer, new_width, new_height)) return FALSE; } return TRUE; } 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 && ((width != tile_manager_width (gimage->shadow)) || (height != tile_manager_height (gimage->shadow)) || (bpp != tile_manager_bpp (gimage->shadow)))) 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) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); if (gimage->shadow) { tile_manager_destroy (gimage->shadow); gimage->shadow = NULL; } } void gimp_image_apply_image (GimpImage *gimage, GimpDrawable *drawable, PixelRegion *src2PR, gboolean undo, gint opacity, LayerModeEffects mode, /* alternative to using drawable tiles as src1: */ TileManager *src1_tiles, gint x, gint y) { GimpChannel *mask; gint x1, y1, x2, y2; gint offset_x, offset_y; PixelRegion src1PR, destPR, maskPR; gint operation; gint active [MAX_CHANNELS]; g_return_if_fail (GIMP_IS_IMAGE (gimage)); /* 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[gimp_drawable_type (drawable)][src2PR->bytes]; if (operation == -1) { g_warning ("%s: illegal parameters.", G_GNUC_PRETTY_FUNCTION); return; } /* get the layer offsets */ gimp_drawable_offsets (drawable, &offset_x, &offset_y); /* make sure the image application coordinates are within gimage bounds */ x1 = CLAMP (x, 0, gimp_drawable_width (drawable)); y1 = CLAMP (y, 0, gimp_drawable_height (drawable)); x2 = CLAMP (x + src2PR->w, 0, gimp_drawable_width (drawable)); y2 = CLAMP (y + src2PR->h, 0, gimp_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, gimp_drawable_width (GIMP_DRAWABLE (mask))-offset_x); y1 = CLAMP (y1, -offset_y, gimp_drawable_height(GIMP_DRAWABLE (mask))-offset_y); x2 = CLAMP (x2, -offset_x, gimp_drawable_width (GIMP_DRAWABLE (mask))-offset_x); y2 = CLAMP (y2, -offset_y, gimp_drawable_height(GIMP_DRAWABLE (mask))-offset_y); } /* If the calling procedure specified an undo step... */ if (undo) undo_push_image (gimp_drawable_gimage (drawable), drawable, x1, y1, x2, y2); /* 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, 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, gboolean undo, gint opacity, PixelRegion *maskPR, gint x, gint y) { GimpChannel *mask; gint x1, y1, x2, y2; gint offset_x, offset_y; PixelRegion src1PR, destPR; PixelRegion mask2PR, tempPR; guchar *temp_data; gint operation; gint active [MAX_CHANNELS]; g_return_if_fail (GIMP_IS_IMAGE (gimage)); /* 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 [gimp_drawable_type (drawable)][src2PR->bytes]; if (operation == -1) { g_warning ("%s: illegal parameters.", G_GNUC_PRETTY_FUNCTION); return; } /* get the layer offsets */ gimp_drawable_offsets (drawable, &offset_x, &offset_y); /* make sure the image application coordinates are within gimage bounds */ x1 = CLAMP (x, 0, gimp_drawable_width (drawable)); y1 = CLAMP (y, 0, gimp_drawable_height (drawable)); x2 = CLAMP (x + src2PR->w, 0, gimp_drawable_width (drawable)); y2 = CLAMP (y + src2PR->h, 0, gimp_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, gimp_drawable_width (GIMP_DRAWABLE (mask))-offset_x); y1 = CLAMP (y1, -offset_y, gimp_drawable_height(GIMP_DRAWABLE (mask))-offset_y); x2 = CLAMP (x2, -offset_x, gimp_drawable_width (GIMP_DRAWABLE (mask))-offset_x); y2 = CLAMP (y2, -offset_y, gimp_drawable_height(GIMP_DRAWABLE (mask))-offset_y); } /* If the calling procedure specified an undo step... */ if (undo) gimp_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, 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, 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 (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, 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, RGB); } guchar * gimp_image_get_color_at (GimpImage *gimage, gint x, gint y) { Tile *tile; guchar *src; guchar *dest; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); if (x < 0 || y < 0 || x >= gimage->width || y >= gimage->height) return NULL; dest = g_new (guchar, 5); tile = tile_manager_get_tile (gimp_image_composite (gimage), x, y, TRUE, FALSE); src = tile_data_pointer (tile, x % TILE_WIDTH, y % TILE_HEIGHT); gimp_image_get_color (gimage, gimp_image_composite_type (gimage), dest, src); if (GIMP_IMAGE_TYPE_HAS_ALPHA (gimp_image_composite_type (gimage))) dest[3] = src[gimp_image_composite_bytes (gimage) - 1]; else dest[3] = 255; dest[4] = 0; tile_release (tile, FALSE); return dest; } 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 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 (const GimpImage *gimage, const GimpDrawable *drawable, guchar *src, guchar *dest, GimpImageBaseType type) { GimpImageType d_type; g_return_if_fail (GIMP_IS_IMAGE (gimage)); d_type = (drawable != NULL) ? gimp_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 = gimp_image_color_hash_rgb_to_indexed (gimage, 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 = gimp_image_color_hash_rgb_to_indexed (gimage, src[GRAY_PIX], src[GRAY_PIX], src[GRAY_PIX]); break; } break; default: break; } } GimpGuide * gimp_image_add_hguide (GimpImage *gimage) { GimpGuide *guide; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); guide = g_new (GimpGuide, 1); guide->ref_count = 0; guide->position = -1; guide->guide_ID = gimage->gimp->next_guide_ID++; guide->orientation = ORIENTATION_HORIZONTAL; gimage->guides = g_list_prepend (gimage->guides, guide); return guide; } GimpGuide * gimp_image_add_vguide (GimpImage *gimage) { GimpGuide *guide; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); guide = g_new (GimpGuide, 1); guide->ref_count = 0; guide->position = -1; guide->guide_ID = gimage->gimp->next_guide_ID++; guide->orientation = ORIENTATION_VERTICAL; gimage->guides = g_list_prepend (gimage->guides, guide); return guide; } void gimp_image_add_guide (GimpImage *gimage, GimpGuide *guide) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); gimage->guides = g_list_prepend (gimage->guides, guide); } void gimp_image_remove_guide (GimpImage *gimage, GimpGuide *guide) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); gimage->guides = g_list_remove (gimage->guides, guide); } void gimp_image_delete_guide (GimpImage *gimage, GimpGuide *guide) { guide->position = -1; g_return_if_fail (GIMP_IS_IMAGE (gimage)); if (guide->ref_count <= 0) { gimage->guides = g_list_remove (gimage->guides, guide); g_free (guide); } } GimpGuide * gimp_image_find_guide (GimpImage *gimage, gint x, gint y) { GList *list; GimpGuide *guide; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); if (x < 0 || x >= gimage->width || y < 0 || y >= gimage->height) { return NULL; } for (list = gimage->guides; list; list = g_list_next (list)) { guide = (GimpGuide *) list->data; if (guide->position < 0) continue; switch (guide->orientation) { case ORIENTATION_HORIZONTAL: if (ABS (guide->position - y) < GUIDE_EPSILON) return guide; break; case ORIENTATION_VERTICAL: if (ABS (guide->position - x) < GUIDE_EPSILON) return guide; break; default: break; } } return NULL; } gboolean gimp_image_snap_point (GimpImage *gimage, gint x, gint y, gint *tx, gint *ty) { GList *list; GimpGuide *guide; gint minxdist, minydist; gint dist; gboolean snapped = FALSE; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); g_return_val_if_fail (tx != NULL, FALSE); g_return_val_if_fail (ty != NULL, FALSE); *tx = x; *ty = y; if (x < 0 || x >= gimage->width || y < 0 || y >= gimage->height) { return FALSE; } minxdist = G_MAXINT; minydist = G_MAXINT; for (list = gimage->guides; list; list = g_list_next (list)) { guide = (GimpGuide *) list->data; switch (guide->orientation) { case ORIENTATION_HORIZONTAL: dist = ABS (guide->position - y); if (dist < MIN (GUIDE_EPSILON, minydist)) { minydist = dist; *ty = guide->position; snapped = TRUE; } break; case ORIENTATION_VERTICAL: dist = ABS (guide->position - x); if (dist < MIN (GUIDE_EPSILON, minxdist)) { minxdist = dist; *tx = guide->position; snapped = TRUE; } break; default: break; } } return snapped; } gboolean gimp_image_snap_rectangle (GimpImage *gimage, gint x1, gint y1, gint x2, gint y2, gint *tx1, gint *ty1) { gint nx1, ny1; gint nx2, ny2; gboolean snap1, snap2; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); g_return_val_if_fail (tx1 != NULL, FALSE); g_return_val_if_fail (ty1 != NULL, FALSE); *tx1 = x1; *ty1 = y1; snap1 = gimp_image_snap_point (gimage, x1, y1, &nx1, &ny1); snap2 = gimp_image_snap_point (gimage, x2, y2, &nx2, &ny2); if (snap1 || snap2) { if (x1 != nx1) *tx1 = nx1; else if (x2 != nx2) *tx1 = x1 + (nx2 - x2); if (y1 != ny1) *ty1 = ny1; else if (y2 != ny2) *ty1 = y1 + (ny2 - y2); } return snap1 || snap2; } 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)) undo_push_image_parasite (gimage, 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)) undo_push_image_parasite_remove (gimage, gimp_parasite_name (p)); gimp_parasite_list_remove (gimage->parasites, parasite); } 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; Path *pptr = NULL; PathList *plist; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { GimpTattoo ltattoo; ltattoo = gimp_drawable_get_tattoo (GIMP_DRAWABLE (list->data)); if (ltattoo > maxval) maxval = ltattoo; if (gimp_image_get_channel_by_tattoo (gimage, ltattoo) != NULL) { retval = FALSE; /* Oopps duplicated tattoo in channel */ } /* Now check path an't got this tattoo */ if (path_get_path_by_tattoo (gimage, ltattoo) != NULL) { retval = FALSE; /* Oopps duplicated tattoo in layer */ } } /* Now check that the paths channel 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_drawable_get_tattoo (GIMP_DRAWABLE (channel)); if (ctattoo > maxval) maxval = ctattoo; /* Now check path an't got this tattoo */ if (path_get_path_by_tattoo (gimage, ctattoo) != NULL) { retval = FALSE; /* Oopps duplicated tattoo in layer */ } } /* Find the max tatto value in the paths */ plist = gimage->paths; if (plist && plist->bz_paths) { GimpTattoo ptattoo; GSList *pl; for (pl = plist->bz_paths; pl; pl = g_slist_next (pl)) { pptr = pl->data; ptattoo = path_get_tattoo (pptr); if (ptattoo > maxval) maxval = ptattoo; } } if (val < maxval) retval = FALSE; /* Must check the state is valid */ if (retval == TRUE) gimage->tattoo_state = val; return retval; } void gimp_image_set_paths (GimpImage *gimage, PathList *paths) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); gimage->paths = paths; } PathList * gimp_image_get_paths (const GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); return gimage->paths; } void gimp_image_colormap_changed (GimpImage *gimage, gint col) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_return_if_fail (col < gimage->num_cols); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[COLORMAP_CHANGED], 0, col); } void gimp_image_mode_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[MODE_CHANGED], 0); } void gimp_image_mask_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[MASK_CHANGED], 0); } void gimp_image_resolution_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[RESOLUTION_CHANGED], 0); } void gimp_image_unit_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[UNIT_CHANGED], 0); } void gimp_image_qmask_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[QMASK_CHANGED], 0); } void gimp_image_alpha_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[ALPHA_CHANGED], 0); } void gimp_image_floating_selection_changed (GimpImage *gimage) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[FLOATING_SELECTION_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 (G_OBJECT (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 (G_OBJECT (gimage), gimp_image_signals[UPDATE_GUIDE], 0, guide); } void gimp_image_selection_control (GimpImage *gimage, GimpSelectionControl control) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); g_signal_emit (G_OBJECT (gimage), gimp_image_signals[SELECTION_CONTROL], 0, control); } /************************************************************/ /* Projection functions */ /************************************************************/ static void project_intensity (GimpImage *gimage, GimpLayer *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, GimpLayer *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, GimpLayer *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_warning ("%s: unable to project indexed image.", G_GNUC_PRETTY_FUNCTION); } static void project_indexed_alpha (GimpImage *gimage, GimpLayer *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, GimpChannel *channel, PixelRegion *src, PixelRegion *src2) { guchar col[3]; guchar opacity; gint type; gimp_rgba_get_uchar (&channel->color, &col[0], &col[1], &col[2], &opacity); if (! gimage->construct_flag) { type = (channel->show_masked) ? INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION; initial_region (src2, src, NULL, col, opacity, NORMAL_MODE, NULL, type); } else { type = (channel->show_masked) ? COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION; combine_regions (src, src2, src, NULL, col, opacity, NORMAL_MODE, NULL, type); } } /************************************************************/ /* Layer/Channel functions */ /************************************************************/ static void gimp_image_construct_layers (GimpImage *gimage, gint x, gint y, gint w, gint h) { GimpLayer *layer; gint x1, y1, x2, y2; PixelRegion src1PR, src2PR, maskPR; PixelRegion * mask; GList *list; GList *reverse_list; gint off_x; gint 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); reverse_list = NULL; for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; /* only add layers that are visible and not floating selections * to the list */ if (! gimp_layer_is_floating_sel (layer) && gimp_drawable_get_visible (GIMP_DRAWABLE (layer))) { reverse_list = g_list_prepend (reverse_list, layer); } } for (list = reverse_list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; gimp_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 + gimp_drawable_width (GIMP_DRAWABLE (layer)), x, x + w); y2 = CLAMP (off_y + gimp_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->mask->show_mask) { pixel_region_init (&src2PR, gimp_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, gimp_drawable_data (GIMP_DRAWABLE (layer)), (x1 - off_x), (y1 - off_y), (x2 - x1), (y2 - y1), FALSE); if (layer->mask && layer->mask->apply_mask) { pixel_region_init (&maskPR, gimp_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 (gimp_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 = TRUE; /* something was projected */ } g_list_free (reverse_list); } static void gimp_image_construct_channels (GimpImage *gimage, gint x, gint y, gint w, gint h) { GimpChannel *channel; PixelRegion src1PR; PixelRegion src2PR; GList *list; GList *reverse_list = NULL; /* reverse the channel list */ for (list = GIMP_LIST (gimage->channels)->list; list; list = g_list_next (list)) { reverse_list = g_list_prepend (reverse_list, list->data); } for (list = reverse_list; list; list = g_list_next (list)) { channel = (GimpChannel *) list->data; if (gimp_drawable_get_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, gimp_drawable_data (GIMP_DRAWABLE (channel)), x, y, w, h, FALSE); project_channel (gimage, channel, &src1PR, &src2PR); gimage->construct_flag = TRUE; } } g_list_free (reverse_list); } static void gimp_image_initialize_projection (GimpImage *gimage, gint x, gint y, gint w, gint h) { GList *list; GimpLayer *layer; gint coverage = 0; PixelRegion PR; guchar 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 */ for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { gint off_x, off_y; layer = (GimpLayer *) list->data; gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); if (gimp_drawable_get_visible (GIMP_DRAWABLE (layer)) && ! gimp_layer_has_alpha (layer) && (off_x <= x) && (off_y <= y) && (off_x + gimp_drawable_width (GIMP_DRAWABLE (layer)) >= x + w) && (off_y + gimp_drawable_height (GIMP_DRAWABLE (layer)) >= y + h)) { coverage = 1; break; } } 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, gint *active) { GimpLayer *layer; gint 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 (GIMP_IS_CHANNEL (drawable)) 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 (GIMP_IS_LAYER (drawable)) { layer = GIMP_LAYER (drawable); if (gimp_layer_has_alpha (layer) && layer->preserve_trans) active[gimp_drawable_bytes (drawable) - 1] = 0; } } } static void gimp_image_construct (GimpImage *gimage, gint x, gint y, gint w, gint h) { g_return_if_fail (GIMP_IS_IMAGE (gimage)); #if 0 gint xoff; gint yoff; /* set the construct flag, used to determine if anything * has been written to the gimage raw image yet. */ gimage->construct_flag = FALSE; if (gimage->layers) { gimp_drawable_offsets (GIMP_DRAWABLE ((GimpLayer*) gimage->layers->data), &xoff, &yoff); } if ((gimage->layers) && /* There's a layer. */ (! g_slist_next (gimage->layers)) && /* It's the only layer. */ (gimp_layer_has_alpha ((GimpLayer *) (gimage->layers->data))) && /* It's !flat. */ (gimp_drawable_get_visible (GIMP_DRAWABLE (gimage->layers->data))) && /* It's visible. */ (gimp_drawable_width (GIMP_DRAWABLE (gimage->layers->data)) == gimage->width) && (gimp_drawable_height (GIMP_DRAWABLE (gimage->layers->data)) == gimage->height) && /* Covers all. */ (!gimp_drawable_is_indexed (GIMP_DRAWABLE (gimage->layers->data))) && /* Not indexed. */ (((GimpLayer *)(gimage->layers->data))->opacity == OPAQUE_OPACITY) /* Opaque */ ) { gint xoff; gint yoff; gimp_drawable_offsets (GIMP_DRAWABLE (gimage->layers->data), &xoff, &yoff); if ((xoff==0) && (yoff==0)) /* Starts at 0,0 */ { PixelRegion srcPR, destPR; gpointer pr; g_warning("Can use cow-projection hack. Yay!"); pixel_region_init (&srcPR, gimp_drawable_data (GIMP_DRAWABLE (gimage->layers->data)), x, y, w,h, FALSE); pixel_region_init (&destPR, gimp_image_projection (gimage), x, y, w,h, TRUE); for (pr = pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr)) { tile_manager_map_over_tile (destPR.tiles, destPR.curtile, srcPR.curtile); } gimage->construct_flag = TRUE; gimp_image_construct_channels (gimage, x, y, w, h); return; } } #else gimage->construct_flag = FALSE; #endif /* 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_without_render (GimpImage *gimage, gint x, gint y, gint w, gint h, gint x1, gint y1, gint x2, gint y2) { Tile *tile; TileManager *tm; gint i, j; g_return_if_fail (GIMP_IS_IMAGE (gimage)); tm = gimp_image_projection (gimage); /* 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, FALSE, FALSE); /* check if the tile is outside the bounds */ if ((MIN ((j + tile_ewidth(tile)), x2) - MAX (j, x1)) <= 0) { tile_invalidate_tile (&tile, tm, j, i); } else if (MIN ((i + tile_eheight(tile)), y2) - MAX (i, y1) <= 0) { tile_invalidate_tile (&tile, tm, j, i); } } } void gimp_image_invalidate (GimpImage *gimage, gint x, gint y, gint w, gint h, gint x1, gint y1, gint x2, gint y2) { Tile *tile; TileManager *tm; gint i, j; gint startx, starty; gint endx, endy; gint tilex, tiley; g_return_if_fail (GIMP_IS_IMAGE (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, FALSE, FALSE); /* check if the tile is outside the bounds */ if ((MIN ((j + tile_ewidth(tile)), x2) - MAX (j, x1)) <= 0) { tile_invalidate_tile (&tile, tm, j, i); if (j < x1) startx = MAX (startx, (j + tile_ewidth(tile))); else endx = MIN (endx, j); } else if (MIN ((i + tile_eheight(tile)), y2) - MAX (i, y1) <= 0) { tile_invalidate_tile (&tile, tm, j, i); if (i < y1) starty = MAX (starty, (i + tile_eheight(tile))); 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_is_valid (tile) == FALSE) { tilex = j - (j % TILE_WIDTH); tiley = i - (i % TILE_HEIGHT); startx = MIN (startx, tilex); endx = MAX (endx, tilex + tile_ewidth (tile)); starty = MIN (starty, tiley); endy = MAX (endy, tiley + tile_eheight (tile)); tile_mark_valid (tile); /* hmmmmmmm..... */ } } } if ((endx - startx) > 0 && (endy - starty) > 0) gimp_image_construct (gimage, startx, starty, (endx - startx), (endy - starty)); } void gimp_image_validate (TileManager *tm, Tile *tile) { GimpImage *gimage; gint x, y; gint w, h; /* Get the gimage from the tilemanager */ gimage = (GimpImage *) tile_manager_get_user_data (tm); gimp_set_busy_until_idle (gimage->gimp); /* Find the coordinates of this tile */ tile_manager_get_tile_coordinates (tm, tile, &x, &y); w = tile_ewidth (tile); h = tile_eheight (tile); gimp_image_construct (gimage, x, y, w, h); } 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); } 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; } 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)); } 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; } 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_drawable_get_tattoo (GIMP_DRAWABLE (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_drawable_get_tattoo (GIMP_DRAWABLE (channel)) == tattoo) return channel; } 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; } 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_set_component_active (GimpImage *gimage, ChannelType type, gboolean active) { gint pixel = -1; g_return_if_fail (GIMP_IS_IMAGE (gimage)); switch (type) { case RED_CHANNEL: pixel = RED_PIX; break; case GREEN_CHANNEL: pixel = GREEN_PIX; break; case BLUE_CHANNEL: pixel = BLUE_PIX; break; case GRAY_CHANNEL: pixel = GRAY_PIX; break; case INDEXED_CHANNEL: pixel = INDEXED_PIX; break; case ALPHA_CHANNEL: switch (gimp_image_base_type (gimage)) { case RGB: pixel = ALPHA_PIX; break; case GRAY: pixel = ALPHA_G_PIX; break; case INDEXED: pixel = ALPHA_I_PIX; break; } break; default: break; } if (pixel != -1 && active != gimage->active[pixel]) { gimage->active[pixel] = 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 (G_OBJECT (gimage), gimp_image_signals[COMPONENT_ACTIVE_CHANGED], 0, type); } } gboolean gimp_image_get_component_active (const GimpImage *gimage, ChannelType type) { /* No sanity checking here... */ switch (type) { case RED_CHANNEL: return gimage->active[RED_PIX]; break; case GREEN_CHANNEL: return gimage->active[GREEN_PIX]; break; case BLUE_CHANNEL: return gimage->active[BLUE_PIX]; break; case GRAY_CHANNEL: return gimage->active[GRAY_PIX]; break; case INDEXED_CHANNEL: return gimage->active[INDEXED_PIX]; break; case ALPHA_CHANNEL: switch (gimp_image_base_type (gimage)) { case RGB: return gimage->active[ALPHA_PIX]; break; case GRAY: return gimage->active[ALPHA_G_PIX]; break; case INDEXED: return gimage->active[ALPHA_I_PIX]; break; } break; default: break; } return FALSE; } void gimp_image_set_component_visible (GimpImage *gimage, ChannelType type, gboolean visible) { gint pixel = -1; g_return_if_fail (GIMP_IS_IMAGE (gimage)); switch (type) { case RED_CHANNEL: pixel = RED_PIX; break; case GREEN_CHANNEL: pixel = GREEN_PIX; break; case BLUE_CHANNEL: pixel = BLUE_PIX; break; case GRAY_CHANNEL: pixel = GRAY_PIX; break; case INDEXED_CHANNEL: pixel = INDEXED_PIX; break; case ALPHA_CHANNEL: switch (gimp_image_base_type (gimage)) { case RGB: pixel = ALPHA_PIX; break; case GRAY: pixel = ALPHA_G_PIX; break; case INDEXED: pixel = ALPHA_I_PIX; break; } break; default: break; } if (pixel != -1 && visible != gimage->visible[pixel]) { gimage->visible[pixel] = visible ? TRUE : FALSE; g_signal_emit (G_OBJECT (gimage), gimp_image_signals[COMPONENT_VISIBILITY_CHANGED], 0, type); gimp_image_update (gimage, 0, 0, gimage->width, gimage->height); } } gboolean gimp_image_get_component_visible (const GimpImage *gimage, ChannelType type) { /* No sanity checking here... */ switch (type) { case RED_CHANNEL: return gimage->visible[RED_PIX]; break; case GREEN_CHANNEL: return gimage->visible[GREEN_PIX]; break; case BLUE_CHANNEL: return gimage->visible[BLUE_PIX]; break; case GRAY_CHANNEL: return gimage->visible[GRAY_PIX]; break; case INDEXED_CHANNEL: return gimage->visible[INDEXED_PIX]; break; case ALPHA_CHANNEL: switch (gimp_image_base_type (gimage)) { case RGB: return gimage->visible[ALPHA_PIX]; break; case GRAY: return gimage->visible[ALPHA_G_PIX]; break; case INDEXED: return gimage->visible[ALPHA_I_PIX]; break; } break; default: break; } return FALSE; } 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_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 (G_OBJECT (gimage), gimp_image_signals[ACTIVE_LAYER_CHANGED], 0); if (gimage->active_channel) { gimage->active_channel = NULL; g_signal_emit (G_OBJECT (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); /* 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 (G_OBJECT (gimage), gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0); if (gimage->active_layer) { gimage->active_layer = NULL; g_signal_emit (G_OBJECT (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 (G_OBJECT (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; } 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; } 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); } 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); } 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_layer_has_alpha (layer)) { g_message (_("Cannot raise a layer without alpha.")); return FALSE; } return gimp_image_position_layer (gimage, layer, 0, TRUE); } 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); } gboolean gimp_image_position_layer (GimpImage *gimage, GimpLayer *layer, gint new_index, gboolean push_undo) { 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_layer_has_alpha (tmp)) { g_message (_("Layer \"%s\" has no alpha.\nLayer was placed above it."), GIMP_OBJECT (tmp)->name); new_index--; } } if (push_undo) undo_push_layer_reposition (gimage, layer); gimp_container_reorder (gimage->layers, GIMP_OBJECT (layer), new_index); gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); gimp_image_update (gimage, off_x, off_y, gimp_drawable_width (GIMP_DRAWABLE (layer)), gimp_drawable_height (GIMP_DRAWABLE (layer))); gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage)); return TRUE; } GimpLayer * gimp_image_merge_visible_layers (GimpImage *gimage, MergeType merge_type) { GList *list; GSList *merge_list = NULL; gboolean had_floating_sel = FALSE; GimpLayer *layer = NULL; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); /* if there's a floating selection, anchor it */ if (gimp_image_floating_sel (gimage)) { floating_sel_anchor (gimage->floating_sel); had_floating_sel = TRUE; } for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; if (gimp_drawable_get_visible (GIMP_DRAWABLE (layer))) merge_list = g_slist_append (merge_list, layer); } if (merge_list && merge_list->next) { gimp_set_busy (gimage->gimp); layer = gimp_image_merge_layers (gimage, merge_list, merge_type); g_slist_free (merge_list); gimp_unset_busy (gimage->gimp); return layer; } else { g_slist_free (merge_list); /* If there was a floating selection, we have done something. No need to warn the user. Return the active layer instead */ if (had_floating_sel) return layer; else g_message (_("Not enough visible layers for a merge.\n" "There must be at least two.")); return NULL; } } GimpLayer * gimp_image_flatten (GimpImage *gimage) { GList *list; GSList *merge_list = NULL; GimpLayer *layer; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); gimp_set_busy (gimage->gimp); /* if there's a floating selection, anchor it */ if (gimp_image_floating_sel (gimage)) floating_sel_anchor (gimage->floating_sel); for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; if (gimp_drawable_get_visible (GIMP_DRAWABLE (layer))) merge_list = g_slist_append (merge_list, layer); } layer = gimp_image_merge_layers (gimage, merge_list, FLATTEN_IMAGE); g_slist_free (merge_list); gimp_image_alpha_changed (gimage); gimp_unset_busy (gimage->gimp); return layer; } GimpLayer * gimp_image_merge_down (GimpImage *gimage, GimpLayer *current_layer, MergeType merge_type) { GimpLayer *layer; GList *list; GList *layer_list; GSList *merge_list; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); for (list = GIMP_LIST (gimage->layers)->list, layer_list = NULL; list && !layer_list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; if (layer == current_layer) break; } for (layer_list = g_list_next (list), merge_list = NULL; layer_list && !merge_list; layer_list = g_list_next (layer_list)) { layer = (GimpLayer *) layer_list->data; if (gimp_drawable_get_visible (GIMP_DRAWABLE (layer))) merge_list = g_slist_append (NULL, layer); } if (merge_list) { merge_list = g_slist_prepend (merge_list, current_layer); gimp_set_busy (gimage->gimp); layer = gimp_image_merge_layers (gimage, merge_list, merge_type); g_slist_free (merge_list); gimp_unset_busy (gimage->gimp); return layer; } else { g_message (_("There are not enough visible layers for a merge down.")); return NULL; } } GimpLayer * gimp_image_merge_layers (GimpImage *gimage, GSList *merge_list, MergeType merge_type) { GList *list; GSList *reverse_list = NULL; PixelRegion src1PR, src2PR, maskPR; PixelRegion *mask; GimpLayer *merge_layer; GimpLayer *layer; GimpLayer *bottom_layer; LayerModeEffects bottom_mode; guchar bg[4] = {0, 0, 0, 0}; GimpImageType type; gint count; gint x1, y1, x2, y2; gint x3, y3, x4, y4; gint operation; gint position; gint active[MAX_CHANNELS] = {1, 1, 1, 1}; gint off_x, off_y; gchar *name; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); layer = NULL; type = RGBA_GIMAGE; x1 = y1 = 0; x2 = y2 = 0; bottom_layer = NULL; bottom_mode = NORMAL_MODE; /* Get the layer extents */ count = 0; while (merge_list) { layer = (GimpLayer *) merge_list->data; gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); switch (merge_type) { case EXPAND_AS_NECESSARY: case CLIP_TO_IMAGE: if (!count) { x1 = off_x; y1 = off_y; x2 = off_x + gimp_drawable_width (GIMP_DRAWABLE (layer)); y2 = off_y + gimp_drawable_height (GIMP_DRAWABLE (layer)); } else { if (off_x < x1) x1 = off_x; if (off_y < y1) y1 = off_y; if ((off_x + gimp_drawable_width (GIMP_DRAWABLE (layer))) > x2) x2 = (off_x + gimp_drawable_width (GIMP_DRAWABLE (layer))); if ((off_y + gimp_drawable_height (GIMP_DRAWABLE (layer))) > y2) y2 = (off_y + gimp_drawable_height (GIMP_DRAWABLE (layer))); } if (merge_type == CLIP_TO_IMAGE) { 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 CLIP_TO_BOTTOM_LAYER: if (merge_list->next == NULL) { x1 = off_x; y1 = off_y; x2 = off_x + gimp_drawable_width (GIMP_DRAWABLE (layer)); y2 = off_y + gimp_drawable_height (GIMP_DRAWABLE (layer)); } break; case FLATTEN_IMAGE: 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); name = g_strdup (gimp_object_get_name (GIMP_OBJECT (layer))); if (merge_type == FLATTEN_IMAGE || gimp_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 = gimp_layer_new (gimage, (x2 - x1), (y2 - y1), type, gimp_object_get_name (GIMP_OBJECT (layer)), OPAQUE_OPACITY, NORMAL_MODE); if (!merge_layer) { g_warning ("%s: could not allocate merge layer.", G_GNUC_PRETTY_FUNCTION); 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, gimp_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 name of the bottom most layer * and the resulting layer has an alpha channel * whether or not the original did * Opacity is set to 100% and the MODE is set to normal */ merge_layer = gimp_layer_new (gimage, (x2 - x1), (y2 - y1), gimp_drawable_type_with_alpha (GIMP_DRAWABLE (layer)), "merged layer", OPAQUE_OPACITY, NORMAL_MODE); if (!merge_layer) { g_warning ("%s: could not allocate merge layer", G_GNUC_PRETTY_FUNCTION); 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, gimp_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 = (GimpLayer *) reverse_list->data; position = gimp_container_num_children (gimage->layers) - gimp_container_get_child_index (gimage->layers, GIMP_OBJECT (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. */ bottom_layer = layer; bottom_mode = bottom_layer->mode; /* DISSOLVE_MODE is special since it is the only mode that does not * work on the projection with the lower layer, but only locally on * the layers alpha channel. */ if (bottom_layer->mode != DISSOLVE_MODE) gimp_layer_set_mode (bottom_layer, NORMAL_MODE); } /* Copy the tattoo and parasites of the bottom layer to the new layer */ gimp_drawable_set_tattoo (GIMP_DRAWABLE (merge_layer), gimp_drawable_get_tattoo (GIMP_DRAWABLE (layer))); GIMP_DRAWABLE (merge_layer)->parasites = gimp_parasite_list_copy (GIMP_DRAWABLE (layer)->parasites); while (reverse_list) { layer = (GimpLayer *) reverse_list->data; /* determine what sort of operation is being attempted and * if it's actually legal... */ operation = valid_combinations[gimp_drawable_type (GIMP_DRAWABLE (merge_layer))][gimp_drawable_bytes (GIMP_DRAWABLE (layer))]; if (operation == -1) { g_warning ("%s: attempting to merge incompatible layers.", G_GNUC_PRETTY_FUNCTION); return NULL; } gimp_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 + gimp_drawable_width (GIMP_DRAWABLE (layer)), x1, x2); y4 = CLAMP (off_y + gimp_drawable_height (GIMP_DRAWABLE (layer)), y1, y2); /* configure the pixel regions */ pixel_region_init (&src1PR, gimp_drawable_data (GIMP_DRAWABLE (merge_layer)), (x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3), TRUE); pixel_region_init (&src2PR, gimp_drawable_data (GIMP_DRAWABLE (layer)), (x3 - off_x), (y3 - off_y), (x4 - x3), (y4 - y3), FALSE); if (layer->mask && layer->mask->apply_mask) { pixel_region_init (&maskPR, gimp_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_layer) gimp_layer_set_mode (bottom_layer, bottom_mode); g_slist_free (reverse_list); /* if the type is flatten, remove all the remaining layers */ if (merge_type == FLATTEN_IMAGE) { list = GIMP_LIST (gimage->layers)->list; while (list) { layer = (GimpLayer *) list->data; list = g_list_next (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, gimp_container_num_children (gimage->layers) - position + 1); } /* set the name after the original layers have been removed so we * don't end up with #2 appended to the name */ gimp_object_set_name (GIMP_OBJECT (merge_layer), name); g_free (name); /* End the merge undo group */ undo_push_group_end (gimage); gimp_drawable_set_visible (GIMP_DRAWABLE (merge_layer), TRUE); gimp_drawable_update (GIMP_DRAWABLE (merge_layer), 0, 0, gimp_drawable_width (GIMP_DRAWABLE (merge_layer)), gimp_drawable_height (GIMP_DRAWABLE (merge_layer))); return merge_layer; } gboolean gimp_image_add_layer (GimpImage *gimage, GimpLayer *layer, gint position) { LayerUndo *lu; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE); if (GIMP_DRAWABLE (layer)->gimage != NULL && GIMP_DRAWABLE (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; } /* Prepare a layer undo and push it */ lu = g_new (LayerUndo, 1); lu->layer = layer; lu->prev_position = 0; lu->prev_layer = gimp_image_get_active_layer (gimage); undo_push_layer (gimage, LAYER_ADD_UNDO, lu); /* 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_drawable_set_gimage (GIMP_DRAWABLE (layer), gimage); /* If the layer has a mask, set the mask's gimage */ if (layer->mask) { gimp_drawable_set_gimage (GIMP_DRAWABLE (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; } gimp_container_insert (gimage->layers, GIMP_OBJECT (layer), position); g_object_unref (G_OBJECT (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_drawable_width (GIMP_DRAWABLE (layer)), gimp_drawable_height (GIMP_DRAWABLE (layer))); return TRUE; } void gimp_image_remove_layer (GimpImage *gimage, GimpLayer *layer) { LayerUndo *lu; 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))); /* Push a layer undo */ lu = g_new (LayerUndo, 1); lu->layer = layer; lu->prev_position = gimp_container_get_child_index (gimage->layers, GIMP_OBJECT (layer)); lu->prev_layer = layer; undo_push_layer (gimage, LAYER_REMOVE_UNDO, lu); g_object_ref (G_OBJECT (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 (G_OBJECT (gimage), gimp_image_signals[ACTIVE_LAYER_CHANGED], 0); } } /* Send out REMOVED signal from layer */ gimp_drawable_removed (GIMP_DRAWABLE (layer)); gimp_drawable_offsets (GIMP_DRAWABLE (layer), &x, &y); w = gimp_drawable_width (GIMP_DRAWABLE (layer)); h = gimp_drawable_height (GIMP_DRAWABLE (layer)); g_object_unref (G_OBJECT (layer)); gimp_image_update (gimage, x, y, w, h); 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); } 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); } gboolean gimp_image_position_channel (GimpImage *gimage, GimpChannel *channel, gint new_index, gboolean push_undo /* FIXME unused */) { 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; gimp_container_reorder (gimage->channels, GIMP_OBJECT (channel), new_index); gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, gimp_drawable_width (GIMP_DRAWABLE (channel)), gimp_drawable_height (GIMP_DRAWABLE (channel))); return TRUE; } gboolean gimp_image_add_channel (GimpImage *gimage, GimpChannel *channel, gint position) { ChannelUndo *cu; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE); if (GIMP_DRAWABLE (channel)->gimage != NULL && GIMP_DRAWABLE (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; } /* Push a channel undo */ cu = g_new (ChannelUndo, 1); cu->channel = channel; cu->prev_position = 0; cu->prev_channel = gimp_image_get_active_channel (gimage); undo_push_channel (gimage, CHANNEL_ADD_UNDO, cu); /* add the channel to the list */ gimp_container_add (gimage->channels, GIMP_OBJECT (channel)); g_object_unref (G_OBJECT (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_drawable_width (GIMP_DRAWABLE (channel)), gimp_drawable_height (GIMP_DRAWABLE (channel))); return TRUE; } void gimp_image_remove_channel (GimpImage *gimage, GimpChannel *channel) { ChannelUndo *cu; 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))); /* Prepare a channel undo--push it below */ cu = g_new (ChannelUndo, 1); cu->channel = channel; cu->prev_position = gimp_container_get_child_index (gimage->channels, GIMP_OBJECT (channel)); cu->prev_channel = gimp_image_get_active_channel (gimage); undo_push_channel (gimage, CHANNEL_REMOVE_UNDO, cu); g_object_ref (G_OBJECT (channel)); gimp_container_remove (gimage->channels, GIMP_OBJECT (channel)); /* Send out REMOVED signal from channel */ gimp_drawable_removed (GIMP_DRAWABLE (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 (G_OBJECT (channel)); gimp_image_update (gimage, 0, 0, gimage->width, gimage->height); gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage)); } /************************************************************/ /* Access functions */ /************************************************************/ 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); } 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; } 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 RGB: return RGBA_GIMAGE; case GRAY: return GRAYA_GIMAGE; case INDEXED: return INDEXEDA_GIMAGE; } return RGB_GIMAGE; } const gchar * gimp_image_filename (const GimpImage *gimage) { const gchar *filename; g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); filename = gimp_object_get_name (GIMP_OBJECT (gimage)); return filename ? filename : _("Untitled"); } 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_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; } 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_enable (GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE); /* Free all undo steps as they are now invalidated */ undo_free (gimage); return gimp_image_undo_thaw (gimage); } void gimp_image_undo_event (GimpImage *gimage, gint event) { g_signal_emit (G_OBJECT (gimage), gimp_image_signals[UNDO_EVENT], 0, event); } /* 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 (G_OBJECT (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 (G_OBJECT (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 (G_OBJECT (gimage), gimp_image_signals[CLEAN], 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; } guchar * gimp_image_cmap (const GimpImage *gimage) { return gimp_drawable_cmap (gimp_image_active_drawable (gimage)); } /************************************************************/ /* Projection access functions */ /************************************************************/ TileManager * gimp_image_projection (GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL); if ((gimage->projection == NULL) || (tile_manager_width (gimage->projection) != gimage->width) || (tile_manager_height (gimage->projection) != gimage->height)) { gimp_image_allocate_projection (gimage); } return gimage->projection; } GimpImageType gimp_image_projection_type (const GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1); return gimage->proj_type; } gint gimp_image_projection_bytes (const GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1); return gimage->proj_bytes; } gint gimp_image_projection_opacity (const GimpImage *gimage) { g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1); return OPAQUE_OPACITY; } void gimp_image_projection_realloc (GimpImage *gimage) { gimp_image_allocate_projection (gimage); } /************************************************************/ /* Composition access functions */ /************************************************************/ TileManager * gimp_image_composite (GimpImage *gimage) { return gimp_image_projection (gimage); } GimpImageType gimp_image_composite_type (const GimpImage *gimage) { return gimp_image_projection_type (gimage); } gint gimp_image_composite_bytes (const GimpImage *gimage) { return gimp_image_projection_bytes (gimage); } gboolean gimp_image_preview_valid (const GimpImage *gimage) { return gimage->comp_preview_valid; } static TempBuf * gimp_image_get_preview (GimpViewable *viewable, gint width, gint height) { GimpImage *gimage; gimage = GIMP_IMAGE (viewable); if (gimage->comp_preview_valid && gimage->comp_preview->width == width && gimage->comp_preview->height == height) { /* The easy way */ return gimage->comp_preview; } else { /* The hard way */ 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_get_new_preview (viewable, width, height); gimage->comp_preview_valid = TRUE; return gimage->comp_preview; } } static TempBuf * gimp_image_get_new_preview (GimpViewable *viewable, gint width, gint height) { GimpImage *gimage; GimpLayer *layer; GimpLayer *floating_sel; PixelRegion src1PR, src2PR, maskPR; PixelRegion *mask; TempBuf *comp; TempBuf *layer_buf; TempBuf *mask_buf; GList *list; GSList *reverse_list = NULL; gdouble ratio; gint x, y, w, h; gint x1, y1, x2, y2; gint bytes; gboolean construct_flag; gint visible[MAX_CHANNELS] = { 1, 1, 1, 1 }; gint off_x, off_y; gimage = GIMP_IMAGE (viewable); ratio = (gdouble) width / (gdouble) 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); temp_buf_data_clear (comp); floating_sel = NULL; for (list = GIMP_LIST (gimage->layers)->list; list; list = g_list_next (list)) { layer = (GimpLayer *) list->data; /* only add layers that are visible to the list */ if (gimp_drawable_get_visible (GIMP_DRAWABLE (layer))) { /* floating selections are added right above the layer * they are attached to */ if (gimp_layer_is_floating_sel (layer)) { floating_sel = layer; } else { if (floating_sel && floating_sel->fs.drawable == GIMP_DRAWABLE (layer)) { reverse_list = g_slist_prepend (reverse_list, floating_sel); } reverse_list = g_slist_prepend (reverse_list, layer); } } } construct_flag = FALSE; for (; reverse_list; reverse_list = g_slist_next (reverse_list)) { layer = (GimpLayer *) reverse_list->data; gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); x = (gint) RINT (ratio * off_x); y = (gint) RINT (ratio * off_y); w = (gint) RINT (ratio * gimp_drawable_width (GIMP_DRAWABLE (layer))); h = (gint) RINT (ratio * gimp_drawable_height (GIMP_DRAWABLE (layer))); if (w < 1 || h < 1) continue; 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 = gimp_viewable_get_preview (GIMP_VIEWABLE (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->mask->apply_mask) { mask_buf = gimp_viewable_get_preview (GIMP_VIEWABLE (layer->mask), 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 (gimp_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 = TRUE; } g_slist_free (reverse_list); return comp; }