app, libgimp, pdb: "edit-copy" multi-layer aware.

When several layers are selected, select their render, similar to how
"edit-copy-visible" would have copied an image with only these layers
made visible.
Also apply the same logics to PDB function gimp_edit_copy() which can
now be used on several drawables at once.
This commit is contained in:
Jehan 2020-05-06 15:46:51 +02:00
parent 0534e077b7
commit 98603c69c9
12 changed files with 373 additions and 62 deletions

View File

@ -98,6 +98,12 @@ void action_message (GimpDisplay *display,
if (! drawable) \
return
#define return_if_no_drawables(image,drawables,data) \
return_if_no_image (image,data); \
drawables = gimp_image_get_selected_drawables (image); \
if (! drawables) \
return
#define return_if_no_layer(image,layer,data) \
return_if_no_image (image,data); \
layer = gimp_image_get_active_layer (image); \

View File

@ -288,6 +288,7 @@ edit_actions_update (GimpActionGroup *group,
GimpImage *image = action_data_get_image (data);
GimpDisplay *display = action_data_get_display (data);
GimpDrawable *drawable = NULL;
GList *drawables = NULL;
gchar *undo_name = NULL;
gchar *redo_name = NULL;
gboolean writable = FALSE;
@ -296,6 +297,7 @@ edit_actions_update (GimpActionGroup *group,
if (image)
{
drawables = gimp_image_get_selected_drawables (image);
drawable = gimp_image_get_active_drawable (image);
if (drawable)
@ -356,7 +358,7 @@ edit_actions_update (GimpActionGroup *group,
g_free (redo_name);
SET_SENSITIVE ("edit-cut", writable && !children);
SET_SENSITIVE ("edit-copy", drawable);
SET_SENSITIVE ("edit-copy", drawables);
SET_SENSITIVE ("edit-copy-visible", image);
/* "edit-paste" is always active */
SET_SENSITIVE ("edit-paste-in-place", image);
@ -377,6 +379,8 @@ edit_actions_update (GimpActionGroup *group,
#undef SET_LABEL
#undef SET_SENSITIVE
g_list_free (drawables);
}

View File

@ -256,12 +256,12 @@ edit_copy_cmd_callback (GimpAction *action,
gpointer data)
{
GimpImage *image;
GimpDrawable *drawable;
GList *drawables;
GimpObject *copy;
GError *error = NULL;
return_if_no_drawable (image, drawable, data);
return_if_no_drawables (image, drawables, data);
copy = gimp_edit_copy (image, drawable, action_data_get_context (data),
copy = gimp_edit_copy (image, drawables, action_data_get_context (data),
&error);
if (copy)
@ -285,6 +285,8 @@ edit_copy_cmd_callback (GimpAction *action,
error->message);
g_clear_error (&error);
}
g_list_free (drawables);
}
void

View File

@ -115,25 +115,44 @@ gimp_edit_cut (GimpImage *image,
GimpObject *
gimp_edit_copy (GimpImage *image,
GimpDrawable *drawable,
GList *drawables,
GimpContext *context,
GError **error)
{
GList *iter;
gboolean drawables_are_layers = TRUE;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
g_return_val_if_fail (drawables != NULL, NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (GIMP_IS_LAYER (drawable) &&
gimp_channel_is_empty (gimp_image_get_mask (image)))
for (iter = drawables; iter; iter = iter->next)
{
g_return_val_if_fail (GIMP_IS_DRAWABLE (iter->data), NULL);
g_return_val_if_fail (gimp_item_is_attached (iter->data), NULL);
if (! GIMP_IS_LAYER (iter->data))
drawables_are_layers = FALSE;
}
/* Only accept multiple drawables for layers. */
g_return_val_if_fail (g_list_length (drawables) == 1 || drawables_are_layers, NULL);
if (drawables_are_layers &&
gimp_channel_is_empty (gimp_image_get_mask (image)) &&
g_list_length (drawables) == 1)
{
/* Special-casing the 1 layer with no selection case.
* It allows us to save the whole layer with all pixels as stored,
* not the rendered version of it.
*/
GimpImage *clip_image;
gint off_x, off_y;
gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
gimp_item_get_offset (GIMP_ITEM (drawables->data), &off_x, &off_y);
clip_image = gimp_image_new_from_drawable (image->gimp, drawable);
clip_image = gimp_image_new_from_drawable (image->gimp, drawables->data);
g_object_set_data (G_OBJECT (clip_image), "offset-x",
GINT_TO_POINTER (off_x));
g_object_set_data (G_OBJECT (clip_image), "offset-y",
@ -144,11 +163,27 @@ gimp_edit_copy (GimpImage *image,
return GIMP_OBJECT (gimp_get_clipboard_image (image->gimp));
}
else if (drawables_are_layers)
{
/* Copying multiple layers or specific selection, we copy the
* composited pixels as rendered.
*/
GimpImage *clip_image;
GimpBuffer *buffer;
clip_image = gimp_image_new_from_drawables (image->gimp, drawables, TRUE);
gimp_container_remove (image->gimp->images, GIMP_OBJECT (clip_image));
buffer = gimp_edit_copy_visible (clip_image, context, error);
g_object_unref (clip_image);
return buffer ? GIMP_OBJECT (buffer) : NULL;
}
else
{
GimpBuffer *buffer;
buffer = gimp_edit_extract (image, GIMP_PICKABLE (drawable),
buffer = gimp_edit_extract (image, GIMP_PICKABLE (drawables->data),
context, FALSE, error);
if (buffer)

View File

@ -24,7 +24,7 @@ GimpObject * gimp_edit_cut (GimpImage *image,
GimpContext *context,
GError **error);
GimpObject * gimp_edit_copy (GimpImage *image,
GimpDrawable *drawable,
GList *drawables,
GimpContext *context,
GError **error);
GimpBuffer * gimp_edit_copy_visible (GimpImage *image,

View File

@ -30,6 +30,7 @@
#include "config/gimpcoreconfig.h"
#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-utils.h"
#include "gimp.h"
#include "gimpbuffer.h"
@ -43,6 +44,7 @@
#include "gimpimage-undo.h"
#include "gimplayer.h"
#include "gimplayer-new.h"
#include "gimplist.h"
#include "gimptemplate.h"
#include "gimp-intl.h"
@ -232,6 +234,198 @@ gimp_image_new_from_drawable (Gimp *gimp,
return new_image;
}
/**
* gimp_image_new_copy_drawables:
* @image:
* @drawables: the drawables to insert into @image.
* @parent:
* @new_parent:
*
* This recursive function will create copies of all @drawables
* belonging to the same @image, and will insert them into @new_image
* with the same layer order and hierarchy (adding layer groups when
* needed).
* If a single drawable is selected, it will be copied visible, with
* full opacity and default layer mode. Otherwise, visibility, opacity
* and layer mode will be copied as-is, allowing proper compositing.
*
* The @parent and @new_parent arguments are only used internally for
* recursive calls and must be set to NULL for the initial call.
*/
static void
gimp_image_new_copy_drawables (GimpImage *image,
GList *drawables,
GimpImage *new_image,
GimpLayer *parent,
GimpLayer *new_parent)
{
GList *layers;
GList *iter;
gint n_drawables;
gint index;
n_drawables = g_list_length (drawables);
if (parent == NULL)
{
if (n_drawables == 1)
{
layers = drawables;
}
else
{
/* Root layers. */
layers = gimp_image_get_layer_iter (image);
/* Add any item parent. */
drawables = g_list_copy (drawables);
for (iter = drawables; iter; iter = iter->next)
{
GimpItem *item = iter->data;
while ((item = gimp_item_get_parent (item)))
{
if (! g_list_find (drawables, item))
drawables = g_list_prepend (drawables, item);
}
}
}
}
else
{
GimpContainer *container;
container = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
layers = GIMP_LIST (container)->queue->head;
}
index = 0;
for (iter = layers; iter; iter = iter->next)
{
if (g_list_find (drawables, iter->data))
{
GimpLayer *new_layer;
GType new_type;
if (GIMP_IS_LAYER (iter->data))
new_type = G_TYPE_FROM_INSTANCE (iter->data);
else
new_type = GIMP_TYPE_LAYER;
new_layer = GIMP_LAYER (gimp_item_convert (GIMP_ITEM (iter->data),
new_image, new_type));
gimp_object_set_name (GIMP_OBJECT (new_layer),
gimp_object_get_name (iter->data));
gimp_item_set_linked (GIMP_ITEM (new_layer),
gimp_item_get_linked (iter->data), FALSE);
/* Visibility, mode and opacity mimick the source image if
* multiple items are copied. Otherwise we just set them to
* defaults.
*/
gimp_item_set_visible (GIMP_ITEM (new_layer),
n_drawables > 1 ?
gimp_item_get_visible (iter->data) : TRUE,
FALSE);
gimp_layer_set_mode (new_layer,
n_drawables > 1 && GIMP_IS_LAYER (iter->data) ?
gimp_layer_get_mode (iter->data) :
gimp_image_get_default_new_layer_mode (new_image),
FALSE);
gimp_layer_set_opacity (new_layer,
n_drawables > 1 && GIMP_IS_LAYER (iter->data) ?
gimp_layer_get_opacity (iter->data) : GIMP_OPACITY_OPAQUE, FALSE);
if (gimp_layer_can_lock_alpha (new_layer))
gimp_layer_set_lock_alpha (new_layer, FALSE, FALSE);
gimp_image_add_layer (new_image, new_layer, new_parent, index++, TRUE);
/* If a group, loop through children. */
if (n_drawables > 1 && gimp_viewable_get_children (iter->data))
gimp_image_new_copy_drawables (image, drawables, new_image, iter->data, new_layer);
}
}
if (parent == NULL && n_drawables != 1)
g_list_free (drawables);
}
GimpImage *
gimp_image_new_from_drawables (Gimp *gimp,
GList *drawables,
gboolean copy_selection)
{
GimpImage *image = NULL;
GimpImage *new_image;
GList *iter;
GimpImageBaseType type;
GimpPrecision precision;
gdouble xres;
gdouble yres;
GimpColorProfile *profile = NULL;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
g_return_val_if_fail (drawables != NULL, NULL);
for (iter = drawables; iter; iter = iter->next)
{
g_return_val_if_fail (GIMP_IS_DRAWABLE (iter->data), NULL);
if (iter == drawables)
image = gimp_item_get_image (iter->data);
else
/* We only accept list of drawables for a same origin image. */
g_return_val_if_fail (gimp_item_get_image (iter->data) == image, NULL);
}
type = gimp_drawable_get_base_type (drawables->data);
precision = gimp_drawable_get_precision (drawables->data);
profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (drawables->data));
new_image = gimp_create_image (gimp,
gimp_image_get_width (image),
gimp_image_get_height (image),
type, precision,
TRUE);
gimp_image_undo_disable (new_image);
if (type == GIMP_INDEXED)
gimp_image_set_colormap (new_image,
gimp_image_get_colormap (image),
gimp_image_get_colormap_size (image),
FALSE);
gimp_image_get_resolution (image, &xres, &yres);
gimp_image_set_resolution (new_image, xres, yres);
gimp_image_set_unit (new_image, gimp_image_get_unit (image));
if (profile)
gimp_image_set_color_profile (new_image, profile, NULL);
if (copy_selection)
{
GimpChannel *selection;
selection = gimp_image_get_mask (image);
if (! gimp_channel_is_empty (selection))
{
GimpChannel *new_selection;
GeglBuffer *buffer;
new_selection = gimp_image_get_mask (new_image);
buffer = gimp_gegl_buffer_dup (gimp_drawable_get_buffer (GIMP_DRAWABLE (selection)));
gimp_drawable_set_buffer (GIMP_DRAWABLE (new_selection),
FALSE, NULL, buffer);
g_object_unref (buffer);
}
}
gimp_image_new_copy_drawables (image, drawables, new_image, NULL, NULL);
gimp_image_undo_enable (new_image);
return new_image;
}
GimpImage *
gimp_image_new_from_component (Gimp *gimp,
GimpImage *image,

View File

@ -29,6 +29,9 @@ GimpImage * gimp_image_new_from_template (Gimp *gimp,
GimpContext *context);
GimpImage * gimp_image_new_from_drawable (Gimp *gimp,
GimpDrawable *drawable);
GimpImage * gimp_image_new_from_drawables (Gimp *gimp,
GList *drawables,
gboolean copy_selection);
GimpImage * gimp_image_new_from_component (Gimp *gimp,
GimpImage *image,
GimpChannelType component);

View File

@ -105,19 +105,43 @@ edit_copy_invoker (GimpProcedure *procedure,
{
gboolean success = TRUE;
GimpValueArray *return_vals;
GimpDrawable *drawable;
gint num_drawables;
const GimpItem **drawables;
gboolean non_empty = FALSE;
drawable = g_value_get_object (gimp_value_array_index (args, 0));
num_drawables = g_value_get_int (gimp_value_array_index (args, 0));
drawables = (const GimpItem **) gimp_value_get_object_array (gimp_value_array_index (args, 1));
if (success)
{
if (gimp_pdb_item_is_attached (GIMP_ITEM (drawable), NULL, 0, error))
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
GError *my_error = NULL;
GimpImage *image = NULL;
GList *drawables_list = NULL;
gint i;
non_empty = gimp_edit_copy (image, drawable, context, &my_error) != NULL;
for (i = 0; i < num_drawables; i++)
{
if (! gimp_pdb_item_is_attached (GIMP_ITEM (drawables[i]), NULL, 0, error))
{
success = FALSE;
break;
}
if (image == NULL)
{
image = gimp_item_get_image (GIMP_ITEM (drawables[i]));
}
else if (image != gimp_item_get_image (GIMP_ITEM (drawables[i])))
{
success = FALSE;
break;
}
drawables_list = g_list_prepend (drawables_list, (gpointer) drawables[i]);
}
if (success && num_drawables > 0)
{
GError *my_error = NULL;
non_empty = gimp_edit_copy (image, drawables_list, context, &my_error) != NULL;
if (! non_empty)
{
@ -129,6 +153,8 @@ edit_copy_invoker (GimpProcedure *procedure,
}
else
success = FALSE;
g_list_free (drawables_list);
}
return_vals = gimp_procedure_get_return_values (procedure, success,
@ -541,19 +567,25 @@ register_edit_procs (GimpPDB *pdb)
gimp_object_set_static_name (GIMP_OBJECT (procedure),
"gimp-edit-copy");
gimp_procedure_set_static_help (procedure,
"Copy from the specified drawable.",
"If there is a selection in the image, then the area specified by the selection is copied from the specified drawable and placed in an internal GIMP edit buffer. It can subsequently be retrieved using the 'gimp-edit-paste' command. If there is no selection, then the specified drawable's contents will be stored in the internal GIMP edit buffer. This procedure will fail if the selected area lies completely outside the bounds of the current drawable and there is nothing to copy from.",
"Copy from the specified drawables.",
"If there is a selection in the image, then the area specified by the selection is copied from the specified drawables and placed in an internal GIMP edit buffer. It can subsequently be retrieved using the 'gimp-edit-paste' command. If there is no selection, then the specified drawables' contents will be stored in the internal GIMP edit buffer. This procedure will fail if the selected area lies completely outside the bounds of the current drawables and there is nothing to copy from. All the drawables must belong to the same image.",
NULL);
gimp_procedure_set_static_attribution (procedure,
"Spencer Kimball & Peter Mattis",
"Spencer Kimball & Peter Mattis",
"1995-1996");
gimp_procedure_add_argument (procedure,
gimp_param_spec_drawable ("drawable",
"drawable",
"The drawable to copy from",
FALSE,
GIMP_PARAM_READWRITE));
g_param_spec_int ("num-drawables",
"num drawables",
"The number of drawables to save",
1, G_MAXINT32, 1,
GIMP_PARAM_READWRITE));
gimp_procedure_add_argument (procedure,
gimp_param_spec_object_array ("drawables",
"drawables",
"Drawables to copy from",
GIMP_TYPE_ITEM,
GIMP_PARAM_READWRITE | GIMP_PARAM_NO_VALIDATE));
gimp_procedure_add_return_value (procedure,
g_param_spec_boolean ("non-empty",
"non empty",

View File

@ -77,31 +77,36 @@ gimp_edit_cut (GimpDrawable *drawable)
/**
* gimp_edit_copy:
* @drawable: The drawable to copy from.
* @num_drawables: The number of drawables to save.
* @drawables: (array length=num_drawables) (element-type GimpItem): Drawables to copy from.
*
* Copy from the specified drawable.
* Copy from the specified drawables.
*
* If there is a selection in the image, then the area specified by the
* selection is copied from the specified drawable and placed in an
* selection is copied from the specified drawables and placed in an
* internal GIMP edit buffer. It can subsequently be retrieved using
* the gimp_edit_paste() command. If there is no selection, then the
* specified drawable's contents will be stored in the internal GIMP
* specified drawables' contents will be stored in the internal GIMP
* edit buffer. This procedure will fail if the selected area lies
* completely outside the bounds of the current drawable and there is
* nothing to copy from.
* completely outside the bounds of the current drawables and there is
* nothing to copy from. All the drawables must belong to the same
* image.
*
* Returns: TRUE if the cut was successful, FALSE if there was nothing to copy from.
**/
gboolean
gimp_edit_copy (GimpDrawable *drawable)
gimp_edit_copy (gint num_drawables,
const GimpItem **drawables)
{
GimpValueArray *args;
GimpValueArray *return_vals;
gboolean non_empty = FALSE;
args = gimp_value_array_new_from_types (NULL,
GIMP_TYPE_DRAWABLE, drawable,
G_TYPE_INT, num_drawables,
GIMP_TYPE_OBJECT_ARRAY, NULL,
G_TYPE_NONE);
gimp_value_set_object_array (gimp_value_array_index (args, 1), GIMP_TYPE_ITEM, (GObject **) drawables, num_drawables);
return_vals = gimp_pdb_run_procedure_array (gimp_get_pdb (),
"gimp-edit-copy",

View File

@ -32,22 +32,23 @@ G_BEGIN_DECLS
/* For information look into the C source or the html documentation */
gboolean gimp_edit_cut (GimpDrawable *drawable);
gboolean gimp_edit_copy (GimpDrawable *drawable);
gboolean gimp_edit_copy_visible (GimpImage *image);
GimpLayer* gimp_edit_paste (GimpDrawable *drawable,
gboolean paste_into);
gboolean gimp_edit_cut (GimpDrawable *drawable);
gboolean gimp_edit_copy (gint num_drawables,
const GimpItem **drawables);
gboolean gimp_edit_copy_visible (GimpImage *image);
GimpLayer* gimp_edit_paste (GimpDrawable *drawable,
gboolean paste_into);
GimpImage* gimp_edit_paste_as_new_image (void);
gchar* gimp_edit_named_cut (GimpDrawable *drawable,
const gchar *buffer_name);
gchar* gimp_edit_named_copy (GimpDrawable *drawable,
const gchar *buffer_name);
gchar* gimp_edit_named_copy_visible (GimpImage *image,
const gchar *buffer_name);
GimpLayer* gimp_edit_named_paste (GimpDrawable *drawable,
const gchar *buffer_name,
gboolean paste_into);
GimpImage* gimp_edit_named_paste_as_new_image (const gchar *buffer_name);
gchar* gimp_edit_named_cut (GimpDrawable *drawable,
const gchar *buffer_name);
gchar* gimp_edit_named_copy (GimpDrawable *drawable,
const gchar *buffer_name);
gchar* gimp_edit_named_copy_visible (GimpImage *image,
const gchar *buffer_name);
GimpLayer* gimp_edit_named_paste (GimpDrawable *drawable,
const gchar *buffer_name,
gboolean paste_into);
GimpImage* gimp_edit_named_paste_as_new_image (const gchar *buffer_name);
G_END_DECLS

View File

@ -71,24 +71,29 @@ CODE
}
sub edit_copy {
$blurb = 'Copy from the specified drawable.';
$blurb = 'Copy from the specified drawables.';
$help = <<'HELP';
If there is a selection in the image, then the area specified by the
selection is copied from the specified drawable and placed in an
selection is copied from the specified drawables and placed in an
internal GIMP edit buffer. It can subsequently be retrieved using the
gimp_edit_paste() command. If there is no selection, then the
specified drawable's contents will be stored in the internal GIMP edit
specified drawables' contents will be stored in the internal GIMP edit
buffer. This procedure will fail if the selected area lies completely
outside the bounds of the current drawable and there is nothing to
outside the bounds of the current drawables and there is nothing to
copy from.
All the drawables must belong to the same image.
HELP
&std_pdb_misc;
@inargs = (
{ name => 'drawable', type => 'drawable',
desc => 'The drawable to copy from' }
{ name => 'drawables', type => 'itemarray',
desc => 'Drawables to copy from',
no_validate => 1,
array => { name => 'num_drawables',
type => '1 <= int32',
desc => "The number of drawables to save" } },
);
@outargs = (
@ -100,12 +105,34 @@ HELP
%invoke = (
code => <<CODE
{
if (gimp_pdb_item_is_attached (GIMP_ITEM (drawable), NULL, 0, error))
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
GError *my_error = NULL;
GimpImage *image = NULL;
GList *drawables_list = NULL;
gint i;
non_empty = gimp_edit_copy (image, drawable, context, &my_error) != NULL;
for (i = 0; i < num_drawables; i++)
{
if (! gimp_pdb_item_is_attached (GIMP_ITEM (drawables[i]), NULL, 0, error))
{
success = FALSE;
break;
}
if (image == NULL)
{
image = gimp_item_get_image (GIMP_ITEM (drawables[i]));
}
else if (image != gimp_item_get_image (GIMP_ITEM (drawables[i])))
{
success = FALSE;
break;
}
drawables_list = g_list_prepend (drawables_list, (gpointer) drawables[i]);
}
if (success && num_drawables > 0)
{
GError *my_error = NULL;
non_empty = gimp_edit_copy (image, drawables_list, context, &my_error) != NULL;
if (! non_empty)
{
@ -117,6 +144,8 @@ HELP
}
else
success = FALSE;
g_list_free (drawables_list);
}
CODE
);

View File

@ -620,7 +620,7 @@ p_if_selection_float_it (GimpImage *image,
{
/* selection is TRUE, make a layer (floating selection) from
the selection */
if (gimp_edit_copy (GIMP_DRAWABLE (layer)))
if (gimp_edit_copy (1, (const GimpItem **) &layer))
{
layer = gimp_edit_paste (GIMP_DRAWABLE (layer), FALSE);
}