Issue #12359: encoding conversion undo does not undo the mask conversion.

In particular, we could end up with mask of wrong bytes per pixel, which
was what was happening in the report.

This commit adds a new GimpDrawableFilterMask class because we needed to
implement a specific is_attached() method for effect masks.

Note: the file is added to POTFILES but the only localized string
already existed elsewhere. So this doesn't break string freeze.
This commit is contained in:
Jehan 2024-12-26 15:34:40 +01:00
parent a6fdc51178
commit bfe842cd1d
12 changed files with 337 additions and 52 deletions

View File

@ -163,6 +163,7 @@ typedef struct _GimpTagCache GimpTagCache;
/* drawable objects */
typedef struct _GimpDrawable GimpDrawable;
typedef struct _GimpDrawableFilterMask GimpDrawableFilterMask;
typedef struct _GimpChannel GimpChannel;
typedef struct _GimpLayerMask GimpLayerMask;
typedef struct _GimpSelection GimpSelection;

View File

@ -580,7 +580,7 @@ gimp_drawable_scale (GimpItem *item,
if (GIMP_IS_DRAWABLE_FILTER (list->data))
{
GimpDrawableFilter *filter = list->data;
GimpChannel *mask = gimp_drawable_filter_get_mask (filter);
GimpChannel *mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter));
GeglRectangle *rect = GEGL_RECTANGLE (0, 0,
new_width,
new_height);
@ -689,7 +689,7 @@ gimp_drawable_resize (GimpItem *item,
if (GIMP_IS_DRAWABLE_FILTER (list->data))
{
GimpDrawableFilter *filter = list->data;
GimpChannel *mask = gimp_drawable_filter_get_mask (filter);
GimpChannel *mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter));
GeglRectangle rect = {0, 0, new_width, new_height};
/* Don't resize partial layer effects */
@ -1399,8 +1399,8 @@ gimp_drawable_convert_type (GimpDrawable *drawable,
{
if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
{
GimpDrawableFilter *filter = filter_list->data;
GimpChannel *mask;
GimpDrawableFilter *filter = filter_list->data;
GimpDrawableFilterMask *mask;
mask = gimp_drawable_filter_get_mask (filter);

View File

@ -46,6 +46,7 @@
#include "gimpchannel.h"
#include "gimpdrawable-filters.h"
#include "gimpdrawablefilter.h"
#include "gimpdrawablefiltermask.h"
#include "gimperror.h"
#include "gimpidtable.h"
#include "gimpimage.h"
@ -63,6 +64,7 @@ enum
{
PROP_0,
PROP_ID,
PROP_DRAWABLE,
PROP_MASK,
N_PROPS
};
@ -75,7 +77,7 @@ struct _GimpDrawableFilter
gint ID;
GimpDrawable *drawable;
GimpChannel *mask;
GimpDrawableFilterMask *mask;
GeglNode *operation;
gboolean has_input;
@ -189,14 +191,22 @@ gimp_drawable_filter_class_init (GimpDrawableFilterClass *klass)
object_class->dispose = gimp_drawable_filter_dispose;
object_class->finalize = gimp_drawable_filter_finalize;
drawable_filter_props[PROP_ID] = g_param_spec_int ("id", NULL, NULL,
0, G_MAXINT, 0,
GIMP_PARAM_READABLE);
drawable_filter_props[PROP_ID] = g_param_spec_int ("id", NULL, NULL,
0, G_MAXINT, 0,
GIMP_PARAM_READABLE);
drawable_filter_props[PROP_MASK] = g_param_spec_object ("mask",
NULL, NULL,
GIMP_TYPE_CHANNEL,
GIMP_PARAM_READWRITE);
drawable_filter_props[PROP_DRAWABLE] = g_param_spec_object ("drawable", NULL, NULL,
GIMP_TYPE_DRAWABLE,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY);
/* The mask is in fact a GimpDrawableFilterMask but the property is a
* GimpDrawable to allow setting any drawable. The set_property() code
* will take care of creating a new object of the proper type.
*/
drawable_filter_props[PROP_MASK] = g_param_spec_object ("mask",
NULL, NULL,
GIMP_TYPE_DRAWABLE,
GIMP_PARAM_READWRITE);
g_object_class_install_properties (object_class, N_PROPS, drawable_filter_props);
}
@ -223,15 +233,30 @@ gimp_drawable_filter_set_property (GObject *object,
const GValue *value,
GParamSpec *pspec)
{
GimpDrawableFilter *filter = GIMP_DRAWABLE_FILTER (object);
GimpDrawableFilter *filter = GIMP_DRAWABLE_FILTER (object);
GimpDrawableFilterMask *mask = NULL;
GimpImage *image;
switch (property_id)
{
case PROP_DRAWABLE:
g_set_object (&filter->drawable, g_value_get_object (value));
break;
case PROP_MASK:
g_set_object (&filter->mask, g_value_get_object (value));
image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
if (g_value_get_object (value) != NULL)
mask = GIMP_DRAWABLE_FILTER_MASK (gimp_item_convert (GIMP_ITEM (g_value_get_object (value)),
image, GIMP_TYPE_DRAWABLE_FILTER_MASK));
g_set_object (&filter->mask, mask);
g_clear_object (&mask);
if (filter->mask)
gimp_drawable_filter_sync_mask (filter);
{
gimp_drawable_filter_mask_set_filter (filter->mask, filter);
gimp_drawable_filter_sync_mask (filter);
}
break;
default:
@ -321,10 +346,10 @@ gimp_drawable_filter_new (GimpDrawable *drawable,
filter = g_object_new (GIMP_TYPE_DRAWABLE_FILTER,
"name", undo_desc,
"icon-name", icon_name,
"drawable", drawable,
"mask", NULL,
NULL);
filter->drawable = g_object_ref (drawable);
filter->operation = g_object_ref (operation);
image = gimp_item_get_image (GIMP_ITEM (drawable));
@ -397,7 +422,6 @@ gimp_drawable_filter_duplicate (GimpDrawable *drawable,
{
GimpImage *image;
GimpDrawableFilter *filter;
GimpChannel *mask;
GeglNode *prior_node;
GeglNode *node = gegl_node_new ();
gchar *operation;
@ -466,15 +490,9 @@ gimp_drawable_filter_duplicate (GimpDrawable *drawable,
image = gimp_item_get_image (GIMP_ITEM (drawable));
if (image != NULL)
{
mask = GIMP_CHANNEL (gimp_item_convert (GIMP_ITEM (prior_filter->mask),
image, GIMP_TYPE_CHANNEL));
g_object_set (filter,
"mask", mask,
NULL);
g_object_unref (mask);
}
g_object_set (filter,
"mask", prior_filter->mask,
NULL);
g_free (operation);
@ -517,7 +535,7 @@ gimp_drawable_filter_get_operation (GimpDrawableFilter *filter)
return filter->operation;
}
GimpChannel *
GimpDrawableFilterMask *
gimp_drawable_filter_get_mask (GimpDrawableFilter *filter)
{
g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
@ -1183,17 +1201,12 @@ gimp_drawable_filter_abort (GimpDrawableFilter *filter)
void
gimp_drawable_filter_layer_mask_freeze (GimpDrawableFilter *filter)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
GimpChannel *mask;
GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
if (! filter->mask)
{
mask = GIMP_CHANNEL (gimp_item_duplicate (GIMP_ITEM (gimp_image_get_mask (image)),
GIMP_TYPE_CHANNEL));
g_set_object (&filter->mask, mask);
g_object_unref (mask);
}
g_object_set (filter,
"mask", GIMP_DRAWABLE (gimp_image_get_mask (image)),
NULL);
g_signal_handlers_disconnect_by_func (image,
gimp_drawable_filter_mask_changed,

View File

@ -66,7 +66,7 @@ GimpDrawableFilter *
GimpDrawable *
gimp_drawable_filter_get_drawable (GimpDrawableFilter *filter);
GeglNode * gimp_drawable_filter_get_operation (GimpDrawableFilter *filter);
GimpChannel *
GimpDrawableFilterMask *
gimp_drawable_filter_get_mask (GimpDrawableFilter *filter);
gdouble gimp_drawable_filter_get_opacity (GimpDrawableFilter *filter);
GimpLayerMode

View File

@ -0,0 +1,208 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpdrawablefiltermask.c
* Copyright (C) 2024 Jehan
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libgimpcolor/gimpcolor.h"
#include "core-types.h"
#include "gegl/gimp-babl.h"
#include "gimpdrawablefilter.h"
#include "gimpdrawablefiltermask.h"
#include "gimperror.h"
#include "gimpimage.h"
#include "gimp-intl.h"
static gboolean gimp_drawable_filter_mask_is_attached (GimpItem *item);
static GimpItemTree * gimp_drawable_filter_mask_get_tree (GimpItem *item);
static gboolean gimp_drawable_filter_mask_rename (GimpItem *item,
const gchar *new_name,
const gchar *undo_desc,
GError **error);
static void gimp_drawable_filter_mask_convert_type (GimpDrawable *drawable,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *src_profile,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress);
G_DEFINE_TYPE (GimpDrawableFilterMask, gimp_drawable_filter_mask, GIMP_TYPE_CHANNEL)
#define parent_class gimp_drawable_filter_mask_parent_class
static void
gimp_drawable_filter_mask_class_init (GimpDrawableFilterMaskClass *klass)
{
GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
item_class->is_attached = gimp_drawable_filter_mask_is_attached;
item_class->get_tree = gimp_drawable_filter_mask_get_tree;
item_class->rename = gimp_drawable_filter_mask_rename;
drawable_class->convert_type = gimp_drawable_filter_mask_convert_type;
}
static void
gimp_drawable_filter_mask_init (GimpDrawableFilterMask *drawable_filter_mask)
{
}
static gboolean
gimp_drawable_filter_mask_is_attached (GimpItem *item)
{
GimpDrawableFilterMask *mask = GIMP_DRAWABLE_FILTER_MASK (item);
GimpDrawable *drawable;
if (! mask->filter)
return FALSE;
drawable = gimp_drawable_filter_get_drawable (mask->filter);
return (drawable &&
gimp_item_is_attached (GIMP_ITEM (drawable)) &&
gimp_drawable_filter_get_mask (mask->filter) == mask);
}
static GimpItemTree *
gimp_drawable_filter_mask_get_tree (GimpItem *item)
{
return NULL;
}
static gboolean
gimp_drawable_filter_mask_rename (GimpItem *item,
const gchar *new_name,
const gchar *undo_desc,
GError **error)
{
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
/* TODO: localized after string freeze. */
"Cannot rename effect masks.");
return FALSE;
}
static void
gimp_drawable_filter_mask_convert_type (GimpDrawable *drawable,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *src_profile,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress)
{
new_format =
gimp_babl_mask_format (gimp_babl_format_get_precision (new_format));
GIMP_DRAWABLE_CLASS (parent_class)->convert_type (drawable, dest_image,
new_format,
src_profile,
dest_profile,
layer_dither_type,
mask_dither_type,
push_undo,
progress);
}
/* public functions */
GimpDrawableFilterMask *
gimp_drawable_filter_mask_new (GimpImage *image,
gint width,
gint height)
{
GeglColor *black = gegl_color_new ("black");
GimpChannel *channel;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (width > 0 && height > 0, NULL);
gimp_color_set_alpha (black, 0.5);
channel = GIMP_CHANNEL (gimp_drawable_new (GIMP_TYPE_DRAWABLE_FILTER_MASK,
image, NULL,
0, 0, width, height,
gimp_image_get_mask_format (image)));
gimp_channel_set_color (channel, black, FALSE);
gimp_channel_set_show_masked (channel, TRUE);
channel->x2 = width;
channel->y2 = height;
g_object_unref (black);
return GIMP_DRAWABLE_FILTER_MASK (channel);
}
void
gimp_drawable_filter_mask_set_filter (GimpDrawableFilterMask *mask,
GimpDrawableFilter *filter)
{
g_return_if_fail (GIMP_IS_DRAWABLE_FILTER_MASK (mask));
g_return_if_fail (filter == NULL || GIMP_IS_DRAWABLE_FILTER (filter));
mask->filter = filter;
if (filter)
{
GimpDrawable *drawable;
gchar *mask_name;
gint offset_x;
gint offset_y;
drawable = gimp_drawable_filter_get_drawable (mask->filter);
if (drawable)
{
gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
gimp_item_set_offset (GIMP_ITEM (mask), offset_x, offset_y);
}
mask_name = g_strdup_printf (_("%s mask"), gimp_object_get_name (filter));
gimp_object_take_name (GIMP_OBJECT (mask), mask_name);
}
}
GimpDrawableFilter *
gimp_drawable_filter_mask_get_filter (GimpDrawableFilterMask *mask)
{
g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER_MASK (mask), NULL);
return mask->filter;
}

View File

@ -0,0 +1,61 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpdrawablefiltermask.h
* Copyright (C) 2024 Jehan
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef __GIMP_DRAWABLE_FILTER_MASK_H__
#define __GIMP_DRAWABLE_FILTER_MASK_H__
#include "gimpchannel.h"
#define GIMP_TYPE_DRAWABLE_FILTER_MASK (gimp_drawable_filter_mask_get_type ())
#define GIMP_DRAWABLE_FILTER_MASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_FILTER_MASK, GimpDrawableFilterMask))
#define GIMP_DRAWABLE_FILTER_MASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_FILTER_MASK, GimpDrawableFilterMaskClass))
#define GIMP_IS_DRAWABLE_FILTER_MASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_FILTER_MASK))
#define GIMP_IS_DRAWABLE_FILTER_MASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_FILTER_MASK))
#define GIMP_DRAWABLE_FILTER_MASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE_FILTER_MASK, GimpDrawableFilterMaskClass))
typedef struct _GimpDrawableFilterMaskClass GimpDrawableFilterMaskClass;
struct _GimpDrawableFilterMask
{
GimpChannel parent_instance;
GimpDrawableFilter *filter;
};
struct _GimpDrawableFilterMaskClass
{
GimpChannelClass parent_class;
};
GType gimp_drawable_filter_mask_get_type (void) G_GNUC_CONST;
GimpDrawableFilterMask * gimp_drawable_filter_mask_new (GimpImage *image,
gint width,
gint height);
void gimp_drawable_filter_mask_set_filter (GimpDrawableFilterMask *mask,
GimpDrawableFilter *filter);
GimpDrawableFilter * gimp_drawable_filter_mask_get_filter (GimpDrawableFilterMask *mask);
#endif /* __GIMP_DRAWABLE_FILTER_MASK_H__ */

View File

@ -2136,7 +2136,7 @@ gimp_group_layer_update_size (GimpGroupLayer *group)
GimpDrawableFilter *filter = list->data;
GimpChannel *filter_mask;
filter_mask = gimp_drawable_filter_get_mask (filter);
filter_mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter));
/* Don't resize partial layer effects */
if (gimp_channel_is_empty (filter_mask))

View File

@ -1807,7 +1807,7 @@ gimp_item_transform (GimpItem *item,
GimpDrawableFilter *filter = filter_list->data;
GimpChannel *mask;
mask = gimp_drawable_filter_get_mask (filter);
mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter));
/* Don't resize partial layer effects */
if (! mask || gimp_channel_is_empty (mask))

View File

@ -114,6 +114,7 @@ libappcore_sources = [
'gimpdrawable-transform.c',
'gimpdrawable.c',
'gimpdrawablefilter.c',
'gimpdrawablefiltermask.c',
'gimpdrawablefilterundo.c',
'gimpdrawablemodundo.c',
'gimpdrawablepropundo.c',

View File

@ -1553,7 +1553,7 @@ gimp_filter_tool_create_filter (GimpFilterTool *filter_tool)
{
GimpChannel *mask = NULL;
mask = gimp_drawable_filter_get_mask (filter_tool->existing_filter);
mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter_tool->existing_filter));
gimp_drawable_filter_apply_with_mask (filter_tool->filter, mask,
NULL);
}
@ -2189,11 +2189,11 @@ gimp_filter_tool_set_config (GimpFilterTool *filter_tool,
if (filter_tool->existing_filter &&
gimp_drawable_filter_get_mask (filter_tool->filter) == NULL)
{
GimpContainer *filters;
GimpChannel *mask;
GimpDrawable *existing_drawable;
gint index;
const gchar *name = _("Editing filter...");
GimpContainer *filters;
GimpDrawableFilterMask *mask;
GimpDrawable *existing_drawable;
gint index;
const gchar *name = _("Editing filter...");
/* Get drawable from existing filter, as we might have a different
* drawable selected in the layer tree */

View File

@ -1966,9 +1966,9 @@ xcf_save_layer (XcfInfo *info,
{
if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
{
GimpDrawableFilter *filter = filter_list->data;
GimpChannel *mask = NULL;
GeglNode *op_node = NULL;
GimpDrawableFilter *filter = filter_list->data;
GimpDrawableFilterMask *mask = NULL;
GeglNode *op_node = NULL;
mask = gimp_drawable_filter_get_mask (filter);
op_node = gimp_drawable_filter_get_operation (filter);
@ -2040,9 +2040,9 @@ xcf_save_layer (XcfInfo *info,
{
if (GIMP_IS_DRAWABLE_FILTER (list->data))
{
GimpDrawableFilter *filter = list->data;
GimpChannel *mask = NULL;
GeglNode *op_node = NULL;
GimpDrawableFilter *filter = list->data;
GimpDrawableFilterMask *mask = NULL;
GeglNode *op_node = NULL;
mask = gimp_drawable_filter_get_mask (filter);
op_node = gimp_drawable_filter_get_operation (filter);
@ -2174,7 +2174,7 @@ xcf_save_effect (XcfInfo *info,
offset = info->cp + info->bytes_per_offset;
xcf_write_offset_check_error (info, &offset, 1, ;);
effect_mask = gimp_drawable_filter_get_mask (filter_drawable);
effect_mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter_drawable));
xcf_check_error (xcf_save_channel (info, image, effect_mask,
error), ;);

View File

@ -148,6 +148,7 @@ app/core/gimpdrawable-levels.c
app/core/gimpdrawable-offset.c
app/core/gimpdrawable-stroke.c
app/core/gimpdrawable-transform.c
app/core/gimpdrawablefiltermask.c
app/core/gimpdynamicsoutput.c
app/core/gimpfilloptions.c
app/core/gimpgradient-load.c