app: merging a pass-through group should merge all visible layers below it.

Existing logic to merge a pass-through group layer was wrong because it
was changing the global rendering of the image as though the group was
in fact in Normal mode.

Instead a pass-through group can be kinda considered as a Normal group
which would contain not only its children, but also all visible sister
layers below it. Therefore the new pass-through group merging will be
taking the real pass-through group render into the new layer (we don't
change the original group's mode before copying the render anymore), set
to Normal mode, then we remove not only the pass-through group but all
its big sister layers below it on same level.

Organizational-wise, it may seem unexpected because "merging" this group
leaks outside it (getting rid of not only the children but also the big
sisters and cousins). Nevertheless this is exactly how this group mode
works after all. So let's go full-in.

After discussing on IRC with Wormnest and NikcDC, we decided that it was
worth doing this specific merge the technically proper way, and we would
just educate people through the docs on why this group mode is very
particular this way.
After all, if someone absolutely wants the old-style merge, they can
always manually change the group to Normal mode first before merging.
But if they let to "Pass-Through", we should assume this is the render
they want.
This commit is contained in:
Jehan 2025-03-11 12:11:10 +01:00
parent db8be0c28c
commit f3eb02b2f0
3 changed files with 112 additions and 32 deletions

View file

@ -943,11 +943,47 @@ layers_merge_group_cmd_callback (GimpAction *action,
for (iter2 = layers; iter2; iter2 = iter2->next)
{
if (iter->data == iter2->data)
continue;
/* Do not merge a layer when we already merge one of its
* ancestors.
*/
if (gimp_viewable_is_ancestor (iter2->data, iter->data))
break;
/* Do not merge a layer which has a little sister (same
* parent and smaller index) or a little cousin (one of
* its ancestors is a little sister) of a pass-through
* group layer.
* These will be rendered and merged through the
* pass-through by definition.
*/
if (gimp_viewable_get_children (GIMP_VIEWABLE (iter2->data)) &&
gimp_layer_get_mode (iter2->data) == GIMP_LAYER_MODE_PASS_THROUGH)
{
GimpLayer *pass_through_parent = gimp_layer_get_parent (iter2->data);
GimpLayer *cousin = iter->data;
gboolean ignore = FALSE;
do
{
GimpLayer *cousin_parent = gimp_layer_get_parent (cousin);
if (pass_through_parent == cousin_parent &&
gimp_item_get_index (GIMP_ITEM (iter2->data)) < gimp_item_get_index (GIMP_ITEM (cousin)))
{
ignore = TRUE;
break;
}
cousin = cousin_parent;
}
while (cousin != NULL);
if (ignore)
break;
}
}
if (iter2 == NULL)

View file

@ -412,6 +412,7 @@ gimp_image_merge_group_layer (GimpImage *image,
{
GimpLayer *parent;
GimpLayer *layer;
gboolean is_pass_through = FALSE;
gint index;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
@ -425,28 +426,8 @@ gimp_image_merge_group_layer (GimpImage *image,
parent = gimp_layer_get_parent (GIMP_LAYER (group));
index = gimp_item_get_index (GIMP_ITEM (group));
/* if this is a pass-through group, change its mode to NORMAL *before*
* duplicating it, since PASS_THROUGH mode is invalid for regular layers.
* see bug #793714.
*/
if (gimp_layer_get_mode (GIMP_LAYER (group)) == GIMP_LAYER_MODE_PASS_THROUGH)
{
GimpLayerColorSpace blend_space;
GimpLayerColorSpace composite_space;
GimpLayerCompositeMode composite_mode;
/* keep the group's current blend space, composite space, and composite
* mode.
*/
blend_space = gimp_layer_get_blend_space (GIMP_LAYER (group));
composite_space = gimp_layer_get_composite_space (GIMP_LAYER (group));
composite_mode = gimp_layer_get_composite_mode (GIMP_LAYER (group));
gimp_layer_set_mode (GIMP_LAYER (group), GIMP_LAYER_MODE_NORMAL, TRUE);
gimp_layer_set_blend_space (GIMP_LAYER (group), blend_space, TRUE);
gimp_layer_set_composite_space (GIMP_LAYER (group), composite_space, TRUE);
gimp_layer_set_composite_mode (GIMP_LAYER (group), composite_mode, TRUE);
}
is_pass_through = (gimp_layer_get_mode (GIMP_LAYER (group)) == GIMP_LAYER_MODE_PASS_THROUGH &&
gimp_item_get_visible (GIMP_ITEM (group)));
/* Merge down filter effects */
gimp_drawable_merge_filters (GIMP_DRAWABLE (group));
@ -460,6 +441,39 @@ gimp_image_merge_group_layer (GimpImage *image,
gimp_image_remove_layer (image, GIMP_LAYER (group), TRUE, NULL);
gimp_image_add_layer (image, layer, parent, index, TRUE);
/* For pass-through group layers, we must remove all "big sister"
* layers, i.e. all layers on the same level below the newly merged
* layer, because their render is already integrated in the merged
* layer. Therefore keeping them would change the whole image's
* rendering.
*/
if (is_pass_through)
{
GimpContainer *stack;
GList *iter;
GList *new_selected = g_list_prepend (NULL, layer);
GList *to_remove = NULL;
gboolean remove = FALSE;
if (parent)
stack = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
else
stack = gimp_image_get_layers (image);
for (iter = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (stack)); iter; iter = iter->next)
{
if (iter->data == layer)
remove = TRUE;
else if (remove && gimp_item_get_visible (iter->data))
to_remove = g_list_prepend (to_remove, iter->data);
}
for (iter = to_remove; iter; iter = iter->next)
gimp_image_remove_layer (image, GIMP_LAYER (iter->data), TRUE, new_selected);
g_list_free (new_selected);
}
gimp_image_undo_group_end (image);
return layer;

View file

@ -937,16 +937,46 @@ gimp_layer_duplicate (GimpItem *item,
GimpLayer *layer = GIMP_LAYER (item);
GimpLayer *new_layer = GIMP_LAYER (new_item);
gimp_layer_set_mode (new_layer,
gimp_layer_get_mode (layer), FALSE);
gimp_layer_set_blend_space (new_layer,
gimp_layer_get_blend_space (layer), FALSE);
gimp_layer_set_composite_space (new_layer,
gimp_layer_get_composite_space (layer), FALSE);
gimp_layer_set_composite_mode (new_layer,
gimp_layer_get_composite_mode (layer), FALSE);
gimp_layer_set_opacity (new_layer,
gimp_layer_get_opacity (layer), FALSE);
/* PASS_THROUGH mode is invalid for regular layers.
* We used to change the mode to NORMAL *before* duplicating (see
* #793714 on bugzilla) but it would change the image's render.
* Instead we first duplicate so that the group's render is used
* as-is for the non-group duplicate layer. Then we set NORMAL
* mode.
*/
if (gimp_layer_get_mode (layer) == GIMP_LAYER_MODE_PASS_THROUGH &&
! GIMP_IS_GROUP_LAYER (new_item))
{
GimpLayerColorSpace blend_space;
GimpLayerColorSpace composite_space;
GimpLayerCompositeMode composite_mode;
/* keep the group's current blend space, composite space, and composite
* mode.
*/
blend_space = gimp_layer_get_blend_space (layer);
composite_space = gimp_layer_get_composite_space (layer);
composite_mode = gimp_layer_get_composite_mode (layer);
gimp_layer_set_mode (new_layer, GIMP_LAYER_MODE_NORMAL, FALSE);
gimp_layer_set_blend_space (new_layer, blend_space, FALSE);
gimp_layer_set_composite_space (new_layer, composite_space, FALSE);
gimp_layer_set_composite_mode (new_layer, composite_mode, FALSE);
gimp_layer_set_opacity (new_layer, 1.0, FALSE);
}
else
{
gimp_layer_set_mode (new_layer,
gimp_layer_get_mode (layer), FALSE);
gimp_layer_set_blend_space (new_layer,
gimp_layer_get_blend_space (layer), FALSE);
gimp_layer_set_composite_space (new_layer,
gimp_layer_get_composite_space (layer), FALSE);
gimp_layer_set_composite_mode (new_layer,
gimp_layer_get_composite_mode (layer), FALSE);
gimp_layer_set_opacity (new_layer,
gimp_layer_get_opacity (layer), FALSE);
}
if (gimp_layer_can_lock_alpha (new_layer))
gimp_layer_set_lock_alpha (new_layer,