Files
gimp/app/core/gimpimage-merge.c
Barak Itkin 5930b13084 app: add context to all undo descriptions
Description of undo actions should be marked as action descriptions,
and not as commands. This is required for translation for some
language (like Hebrew) that require a different grammatical tense for
describing actions
2010-06-09 18:50:23 +02:00

704 lines
21 KiB
C

/* GIMP - The GNU 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"
#include "core-types.h"
#include "base/pixel-region.h"
#include "base/temp-buf.h"
#include "base/tile-manager.h"
#include "paint-funcs/paint-funcs.h"
#include "vectors/gimpvectors.h"
#include "gimp.h"
#include "gimpcontext.h"
#include "gimperror.h"
#include "gimpgrouplayer.h"
#include "gimpimage.h"
#include "gimpimage-colorhash.h"
#include "gimpimage-merge.h"
#include "gimpimage-undo.h"
#include "gimpitemstack.h"
#include "gimplayer-floating-sel.h"
#include "gimplayermask.h"
#include "gimpmarshal.h"
#include "gimpparasitelist.h"
#include "gimpundostack.h"
#include "gimp-intl.h"
static GimpLayer * gimp_image_merge_layers (GimpImage *image,
GimpContainer *container,
GSList *merge_list,
GimpContext *context,
GimpMergeType merge_type,
const gchar *undo_desc);
/* public functions */
GimpLayer *
gimp_image_merge_visible_layers (GimpImage *image,
GimpContext *context,
GimpMergeType merge_type,
gboolean discard_invisible)
{
GimpLayer *active_layer;
GimpContainer *container;
GList *list;
GSList *merge_list = NULL;
GSList *invisible_list = NULL;
GimpLayer *layer = NULL;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
active_layer = gimp_image_get_active_layer (image);
if (active_layer)
container = gimp_item_get_container (GIMP_ITEM (active_layer));
else
container = gimp_image_get_layers (image);
/* if there's a floating selection, anchor it */
if (gimp_image_get_floating_selection (image))
floating_sel_anchor (gimp_image_get_floating_selection (image));
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (container));
list;
list = g_list_next (list))
{
layer = list->data;
if (gimp_item_get_visible (GIMP_ITEM (layer)))
{
merge_list = g_slist_append (merge_list, layer);
}
else if (discard_invisible)
{
invisible_list = g_slist_append (invisible_list, layer);
}
}
if (merge_list)
{
const gchar *undo_desc = C_("undo-type", "Merge Visible Layers");
gimp_set_busy (image->gimp);
if (invisible_list)
{
gimp_image_undo_group_start (image,
GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
undo_desc);
}
layer = gimp_image_merge_layers (image,
container,
merge_list, context, merge_type,
C_("undo-type", "Merge Visible Layers"));
g_slist_free (merge_list);
if (invisible_list)
{
GSList *list;
for (list = invisible_list; list; list = g_slist_next (list))
gimp_image_remove_layer (image, list->data, TRUE, NULL);
gimp_image_undo_group_end (image);
g_slist_free (invisible_list);
}
gimp_unset_busy (image->gimp);
return layer;
}
return gimp_image_get_active_layer (image);
}
GimpLayer *
gimp_image_flatten (GimpImage *image,
GimpContext *context)
{
GList *list;
GSList *merge_list = NULL;
GimpLayer *layer;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
gimp_set_busy (image->gimp);
/* if there's a floating selection, anchor it */
if (gimp_image_get_floating_selection (image))
floating_sel_anchor (gimp_image_get_floating_selection (image));
for (list = gimp_image_get_layer_iter (image);
list;
list = g_list_next (list))
{
layer = list->data;
if (gimp_item_get_visible (GIMP_ITEM (layer)))
merge_list = g_slist_append (merge_list, layer);
}
layer = gimp_image_merge_layers (image,
gimp_image_get_layers (image),
merge_list, context,
GIMP_FLATTEN_IMAGE, C_("undo-type", "Flatten Image"));
g_slist_free (merge_list);
gimp_image_alpha_changed (image);
gimp_unset_busy (image->gimp);
return layer;
}
GimpLayer *
gimp_image_merge_down (GimpImage *image,
GimpLayer *current_layer,
GimpContext *context,
GimpMergeType merge_type,
GError **error)
{
GimpLayer *layer;
GList *list;
GList *layer_list = NULL;
GSList *merge_list = NULL;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_LAYER (current_layer), NULL);
g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (current_layer)), NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
for (list = gimp_item_get_container_iter (GIMP_ITEM (current_layer));
list;
list = g_list_next (list))
{
layer = list->data;
if (layer == current_layer)
break;
}
for (layer_list = g_list_next (list);
layer_list;
layer_list = g_list_next (layer_list))
{
layer = layer_list->data;
if (gimp_item_get_visible (GIMP_ITEM (layer)))
{
if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
{
g_set_error_literal (error, 0, 0,
_("Cannot merge down to a layer group."));
return NULL;
}
if (gimp_item_is_content_locked (GIMP_ITEM (layer)))
{
g_set_error_literal (error, 0, 0,
_("The layer to merge down to is locked."));
return NULL;
}
merge_list = g_slist_append (NULL, layer);
break;
}
}
if (! merge_list)
{
g_set_error_literal (error, 0, 0,
_("There is no visible layer to merge down to."));
return NULL;
}
merge_list = g_slist_prepend (merge_list, current_layer);
gimp_set_busy (image->gimp);
layer = gimp_image_merge_layers (image,
gimp_item_get_container (GIMP_ITEM (current_layer)),
merge_list, context, merge_type,
C_("undo-type", "Merge Down"));
g_slist_free (merge_list);
gimp_unset_busy (image->gimp);
return layer;
}
GimpLayer *
gimp_image_merge_group_layer (GimpImage *image,
GimpGroupLayer *group)
{
GimpLayer *parent;
GimpLayer *layer;
gint index;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
g_return_val_if_fail (gimp_item_get_image (GIMP_ITEM (group)) == image, NULL);
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
C_("undo-type", "Merge Layer Group"));
parent = gimp_layer_get_parent (GIMP_LAYER (group));
index = gimp_item_get_index (GIMP_ITEM (group));
layer = GIMP_LAYER (gimp_item_duplicate (GIMP_ITEM (group),
GIMP_TYPE_LAYER));
gimp_object_set_name (GIMP_OBJECT (layer), gimp_object_get_name (group));
gimp_image_remove_layer (image, GIMP_LAYER (group), TRUE, NULL);
gimp_image_add_layer (image, layer, parent, index, TRUE);
gimp_image_undo_group_end (image);
return layer;
}
/* merging vectors */
GimpVectors *
gimp_image_merge_visible_vectors (GimpImage *image,
GError **error)
{
GList *list;
GList *merge_list = NULL;
GimpVectors *vectors;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
for (list = gimp_image_get_vectors_iter (image);
list;
list = g_list_next (list))
{
vectors = list->data;
if (gimp_item_get_visible (GIMP_ITEM (vectors)))
merge_list = g_list_prepend (merge_list, vectors);
}
merge_list = g_list_reverse (merge_list);
if (merge_list && merge_list->next)
{
GimpVectors *target_vectors;
gchar *name;
gint pos;
gimp_set_busy (image->gimp);
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE,
C_("undo-type", "Merge Visible Paths"));
vectors = GIMP_VECTORS (merge_list->data);
name = g_strdup (gimp_object_get_name (vectors));
pos = gimp_item_get_index (GIMP_ITEM (vectors));
target_vectors = GIMP_VECTORS (gimp_item_duplicate (GIMP_ITEM (vectors),
GIMP_TYPE_VECTORS));
gimp_image_remove_vectors (image, vectors, TRUE, NULL);
for (list = g_list_next (merge_list);
list;
list = g_list_next (list))
{
vectors = list->data;
gimp_vectors_add_strokes (vectors, target_vectors);
gimp_image_remove_vectors (image, vectors, TRUE, NULL);
}
gimp_object_take_name (GIMP_OBJECT (target_vectors), name);
g_list_free (merge_list);
/* FIXME tree */
gimp_image_add_vectors (image, target_vectors, NULL, pos, TRUE);
gimp_unset_busy (image->gimp);
gimp_image_undo_group_end (image);
return target_vectors;
}
else
{
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
_("Not enough visible paths for a merge. "
"There must be at least two."));
return NULL;
}
}
/* private functions */
static CombinationMode
gimp_image_merge_layers_get_operation (GimpLayer *dest,
GimpLayer *src)
{
GimpImageType type = gimp_drawable_type (GIMP_DRAWABLE (dest));
gint bytes = gimp_drawable_bytes (GIMP_DRAWABLE (src));
return gimp_image_get_combination_mode (type, bytes);
}
static GimpLayer *
gimp_image_merge_layers (GimpImage *image,
GimpContainer *container,
GSList *merge_list,
GimpContext *context,
GimpMergeType merge_type,
const gchar *undo_desc)
{
GList *list;
GSList *reverse_list = NULL;
PixelRegion src1PR, src2PR, maskPR;
PixelRegion *mask;
GimpLayer *merge_layer;
GimpLayer *layer;
GimpLayer *bottom_layer;
gint count;
gint x1, y1, x2, y2;
gint off_x, off_y;
gint position;
gchar *name;
GimpLayer *parent;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
layer = NULL;
x1 = y1 = 0;
x2 = y2 = 0;
bottom_layer = NULL;
parent = gimp_layer_get_parent (merge_list->data);
/* Get the layer extents */
count = 0;
while (merge_list)
{
layer = merge_list->data;
gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
switch (merge_type)
{
case GIMP_EXPAND_AS_NECESSARY:
case GIMP_CLIP_TO_IMAGE:
if (! count)
{
x1 = off_x;
y1 = off_y;
x2 = off_x + gimp_item_get_width (GIMP_ITEM (layer));
y2 = off_y + gimp_item_get_height (GIMP_ITEM (layer));
}
else
{
if (off_x < x1)
x1 = off_x;
if (off_y < y1)
y1 = off_y;
if ((off_x + gimp_item_get_width (GIMP_ITEM (layer))) > x2)
x2 = (off_x + gimp_item_get_width (GIMP_ITEM (layer)));
if ((off_y + gimp_item_get_height (GIMP_ITEM (layer))) > y2)
y2 = (off_y + gimp_item_get_height (GIMP_ITEM (layer)));
}
if (merge_type == GIMP_CLIP_TO_IMAGE)
{
x1 = CLAMP (x1, 0, gimp_image_get_width (image));
y1 = CLAMP (y1, 0, gimp_image_get_height (image));
x2 = CLAMP (x2, 0, gimp_image_get_width (image));
y2 = CLAMP (y2, 0, gimp_image_get_height (image));
}
break;
case GIMP_CLIP_TO_BOTTOM_LAYER:
if (merge_list->next == NULL)
{
x1 = off_x;
y1 = off_y;
x2 = off_x + gimp_item_get_width (GIMP_ITEM (layer));
y2 = off_y + gimp_item_get_height (GIMP_ITEM (layer));
}
break;
case GIMP_FLATTEN_IMAGE:
if (merge_list->next == NULL)
{
x1 = 0;
y1 = 0;
x2 = gimp_image_get_width (image);
y2 = gimp_image_get_height (image);
}
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. */
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
undo_desc);
name = g_strdup (gimp_object_get_name (layer));
if (merge_type == GIMP_FLATTEN_IMAGE ||
gimp_drawable_type (GIMP_DRAWABLE (layer)) == GIMP_INDEXED_IMAGE)
{
GimpImageType type;
guchar bg[4] = { 0, 0, 0, 0 };
type = GIMP_IMAGE_TYPE_FROM_BASE_TYPE (gimp_image_base_type (image));
merge_layer = gimp_layer_new (image, (x2 - x1), (y2 - y1),
type,
gimp_object_get_name (layer),
GIMP_OPACITY_OPAQUE, GIMP_NORMAL_MODE);
if (! merge_layer)
{
g_warning ("%s: could not allocate merge layer.", G_STRFUNC);
return NULL;
}
gimp_item_set_offset (GIMP_ITEM (merge_layer), x1, y1);
/* get the background for compositing */
gimp_image_get_background (image, context,
gimp_drawable_type (GIMP_DRAWABLE (merge_layer)),
bg);
/* init the pixel region */
pixel_region_init (&src1PR,
gimp_drawable_get_tiles (GIMP_DRAWABLE (merge_layer)),
0, 0, (x2 - x1), (y2 - y1),
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 (image, (x2 - x1), (y2 - y1),
gimp_drawable_type_with_alpha (GIMP_DRAWABLE (layer)),
"merged layer",
GIMP_OPACITY_OPAQUE, GIMP_NORMAL_MODE);
if (!merge_layer)
{
g_warning ("%s: could not allocate merge layer", G_STRFUNC);
return NULL;
}
gimp_item_set_offset (GIMP_ITEM (merge_layer), x1, y1);
/* clear the layer */
pixel_region_init (&src1PR,
gimp_drawable_get_tiles (GIMP_DRAWABLE (merge_layer)),
0, 0,
(x2 - x1), (y2 - y1),
TRUE);
clear_region (&src1PR);
/* 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 = reverse_list->data;
position =
gimp_container_get_n_children (container) -
gimp_container_get_child_index (container, GIMP_OBJECT (layer));
}
bottom_layer = layer;
/* Copy the tattoo and parasites of the bottom layer to the new layer */
gimp_item_set_tattoo (GIMP_ITEM (merge_layer),
gimp_item_get_tattoo (GIMP_ITEM (bottom_layer)));
g_object_unref (GIMP_ITEM (merge_layer)->parasites);
GIMP_ITEM (merge_layer)->parasites =
gimp_parasite_list_copy (GIMP_ITEM (bottom_layer)->parasites);
while (reverse_list)
{
CombinationMode operation;
GimpLayerModeEffects mode;
gint x3, y3, x4, y4;
gboolean active[MAX_CHANNELS] = { TRUE, TRUE, TRUE, TRUE };
layer = reverse_list->data;
/* determine what sort of operation is being attempted and
* if it's actually legal...
*/
operation = gimp_image_merge_layers_get_operation (merge_layer, layer);
if (operation == -1)
{
gimp_layer_add_alpha (layer);
/* try again ... */
operation = gimp_image_merge_layers_get_operation (merge_layer,
layer);
}
if (operation == -1)
{
g_warning ("%s: attempting to merge incompatible layers.", G_STRFUNC);
return NULL;
}
gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
x3 = CLAMP (off_x, x1, x2);
y3 = CLAMP (off_y, y1, y2);
x4 = CLAMP (off_x + gimp_item_get_width (GIMP_ITEM (layer)), x1, x2);
y4 = CLAMP (off_y + gimp_item_get_height (GIMP_ITEM (layer)), y1, y2);
/* configure the pixel regions */
pixel_region_init (&src1PR,
gimp_drawable_get_tiles (GIMP_DRAWABLE (merge_layer)),
(x3 - x1), (y3 - y1), (x4 - x3), (y4 - y3),
TRUE);
pixel_region_init (&src2PR,
gimp_drawable_get_tiles (GIMP_DRAWABLE (layer)),
(x3 - off_x), (y3 - off_y),
(x4 - x3), (y4 - y3),
FALSE);
if (gimp_layer_get_mask (layer) &&
gimp_layer_mask_get_apply (layer->mask))
{
TileManager *tiles;
tiles = gimp_drawable_get_tiles (GIMP_DRAWABLE (layer->mask));
pixel_region_init (&maskPR, tiles,
(x3 - off_x), (y3 - off_y), (x4 - x3), (y4 - y3),
FALSE);
mask = &maskPR;
}
else
{
mask = NULL;
}
/* 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.
*/
mode = gimp_layer_get_mode (layer);
if (layer == bottom_layer && mode != GIMP_DISSOLVE_MODE)
mode = GIMP_NORMAL_MODE;
combine_regions (&src1PR, &src2PR, &src1PR, mask, NULL,
gimp_layer_get_opacity (layer) * 255.999,
mode,
active,
operation);
gimp_image_remove_layer (image, layer, TRUE, NULL);
reverse_list = g_slist_next (reverse_list);
}
g_slist_free (reverse_list);
/* if the type is flatten, remove all the remaining layers */
if (merge_type == GIMP_FLATTEN_IMAGE)
{
list = gimp_image_get_layer_iter (image);
while (list)
{
layer = list->data;
list = g_list_next (list);
gimp_image_remove_layer (image, layer, TRUE, NULL);
}
gimp_image_add_layer (image, merge_layer, parent,
position, TRUE);
}
else
{
/* Add the layer to the image */
gimp_image_add_layer (image, merge_layer, parent,
gimp_container_get_n_children (container) -
position + 1,
TRUE);
}
/* set the name after the original layers have been removed so we
* don't end up with #2 appended to the name
*/
gimp_object_take_name (GIMP_OBJECT (merge_layer), name);
gimp_item_set_visible (GIMP_ITEM (merge_layer), TRUE, TRUE);
/* End the merge undo group */
gimp_image_undo_group_end (image);
gimp_drawable_update (GIMP_DRAWABLE (merge_layer),
0, 0,
gimp_item_get_width (GIMP_ITEM (merge_layer)),
gimp_item_get_height (GIMP_ITEM (merge_layer)));
return merge_layer;
}