app, pdb: make it possible to delete a color from a colormap if unused.

Until now, it was not really possible to delete a colormap color, but since we
now use GimpPalette, people would definitely try to do so. It just makes sense
to allow doing this, but only if the color is unused.

Additionally when we do this, all the pixels refering to bigger indexes will be
edited so that they continue to refer to the same color (bigger indexes are
shifted by -1). Therefore removing an unused color does not change the image
render.

I wondered if we might want more options, e.g. the ability to delete a color
without fixing indexes (i.e. that colors over the deleted color index would
shift to the next color). This would even allow to delete used colors (though
now the last index would have to be unused one, unless we cycle colors).
Yet I don't think this should belong to this basic API. The most expected
behavior when deleting a color from an image colormap is to fix all indexes
stored in pixels so that the image still shows the same. So that's what this
function will do in this generic usage.
This commit is contained in:
Jehan 2023-10-09 00:45:50 +02:00
parent c3c0a70dd5
commit dbaa8b6a1c
10 changed files with 248 additions and 10 deletions

View file

@ -1195,6 +1195,7 @@ gimp_undo_type_get_type (void)
{ GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE, "GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE", "group-image-vectors-merge" },
{ GIMP_UNDO_GROUP_IMAGE_QUICK_MASK, "GIMP_UNDO_GROUP_IMAGE_QUICK_MASK", "group-image-quick-mask" },
{ GIMP_UNDO_GROUP_IMAGE_GRID, "GIMP_UNDO_GROUP_IMAGE_GRID", "group-image-grid" },
{ GIMP_UNDO_GROUP_IMAGE_COLORMAP_REMAP, "GIMP_UNDO_GROUP_IMAGE_COLORMAP_REMAP", "group-image-colormap-remap" },
{ GIMP_UNDO_GROUP_GUIDE, "GIMP_UNDO_GROUP_GUIDE", "group-guide" },
{ GIMP_UNDO_GROUP_SAMPLE_POINT, "GIMP_UNDO_GROUP_SAMPLE_POINT", "group-sample-point" },
{ GIMP_UNDO_GROUP_DRAWABLE, "GIMP_UNDO_GROUP_DRAWABLE", "group-drawable" },
@ -1303,6 +1304,7 @@ gimp_undo_type_get_type (void)
{ GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE, NC_("undo-type", "Merge paths"), NULL },
{ GIMP_UNDO_GROUP_IMAGE_QUICK_MASK, NC_("undo-type", "Quick Mask"), NULL },
{ GIMP_UNDO_GROUP_IMAGE_GRID, NC_("undo-type", "Grid"), NULL },
{ GIMP_UNDO_GROUP_IMAGE_COLORMAP_REMAP, NC_("undo-type", "Colormap remapping"), NULL },
{ GIMP_UNDO_GROUP_GUIDE, NC_("undo-type", "Guide"), NULL },
{ GIMP_UNDO_GROUP_SAMPLE_POINT, NC_("undo-type", "Sample Point"), NULL },
{ GIMP_UNDO_GROUP_DRAWABLE, NC_("undo-type", "Layer/Channel"), NULL },

View file

@ -544,6 +544,7 @@ typedef enum /*< pdb-skip >*/
GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE, /*< desc="Merge paths" >*/
GIMP_UNDO_GROUP_IMAGE_QUICK_MASK, /*< desc="Quick Mask" >*/
GIMP_UNDO_GROUP_IMAGE_GRID, /*< desc="Grid" >*/
GIMP_UNDO_GROUP_IMAGE_COLORMAP_REMAP, /*< desc="Colormap remapping" >*/
GIMP_UNDO_GROUP_GUIDE, /*< desc="Guide" >*/
GIMP_UNDO_GROUP_SAMPLE_POINT, /*< desc="Sample Point" >*/
GIMP_UNDO_GROUP_DRAWABLE, /*< desc="Layer/Channel" >*/

View file

@ -28,13 +28,16 @@
#include "core-types.h"
#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-loops.h"
#include "gimp.h"
#include "gimpcontainer.h"
#include "gimpdatafactory.h"
#include "gimpdrawable.h"
#include "gimpimage.h"
#include "gimpimage-colormap.h"
#include "gimpimage-private.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimppalette.h"
@ -342,6 +345,30 @@ gimp_image_unset_colormap (GimpImage *image,
gimp_image_colormap_changed (image, -1);
}
gboolean
gimp_image_colormap_is_index_used (GimpImage *image,
gint color_index)
{
GList *layers;
GList *iter;
gboolean found;
layers = gimp_image_get_layer_list (image);
for (iter = layers; iter; iter = g_list_next (iter))
{
found = gimp_gegl_is_index_used (gimp_drawable_get_buffer (iter->data), NULL,
gimp_drawable_get_format_without_alpha (iter->data),
color_index);
if (found)
break;
}
g_list_free (layers);
return found;
}
void
gimp_image_get_colormap_entry (GimpImage *image,
gint color_index,
@ -415,6 +442,57 @@ gimp_image_add_colormap_entry (GimpImage *image,
gimp_image_colormap_changed (image, -1);
}
gboolean
gimp_image_delete_colormap_entry (GimpImage *image,
gint color_index,
gboolean push_undo)
{
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
if (! gimp_image_colormap_is_index_used (image, color_index))
{
GimpImagePrivate *private;
GimpPaletteEntry *entry;
GList *layers;
GList *iter;
if (push_undo)
{
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_COLORMAP_REMAP,
C_("undo-type", "Delete Colormap entry"));
gimp_image_undo_push_image_colormap (image, NULL);
}
private = GIMP_IMAGE_GET_PRIVATE (image);
layers = gimp_image_get_layer_list (image);
for (iter = layers; iter; iter = g_list_next (iter))
{
if (push_undo)
gimp_image_undo_push_drawable_mod (image, NULL, iter->data, TRUE);
gimp_gegl_shift_index (gimp_drawable_get_buffer (iter->data), NULL,
gimp_drawable_get_format_without_alpha (iter->data),
color_index, -1);
}
entry = gimp_palette_get_entry (private->palette, color_index);
gimp_palette_delete_entry (private->palette, entry);
g_list_free (layers);
if (push_undo)
gimp_image_undo_group_end (image);
gimp_image_colormap_changed (image, -1);
return TRUE;
}
return FALSE;
}
/* private functions */

View file

@ -45,6 +45,9 @@ void gimp_image_set_colormap (GimpImage *image,
void gimp_image_unset_colormap (GimpImage *image,
gboolean push_undo);
gboolean gimp_image_colormap_is_index_used (GimpImage *image,
gint color_index);
void gimp_image_get_colormap_entry (GimpImage *image,
gint color_index,
GimpRGB *color);
@ -55,6 +58,9 @@ void gimp_image_set_colormap_entry (GimpImage *image,
void gimp_image_add_colormap_entry (GimpImage *image,
const GimpRGB *color);
gboolean gimp_image_delete_colormap_entry (GimpImage *image,
gint color_index,
gboolean push_undo);
#endif /* __GIMP_IMAGE_COLORMAP_H__ */

View file

@ -621,6 +621,9 @@ gimp_image_undo_dirty_from_type (GimpUndoType undo_type)
case GIMP_UNDO_GROUP_IMAGE_CONVERT:
return GIMP_DIRTY_IMAGE | GIMP_DIRTY_DRAWABLE;
case GIMP_UNDO_GROUP_IMAGE_COLORMAP_REMAP:
return GIMP_DIRTY_IMAGE | GIMP_DIRTY_DRAWABLE;
case GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE:
return GIMP_DIRTY_IMAGE_STRUCTURE | GIMP_DIRTY_DRAWABLE;

View file

@ -914,6 +914,120 @@ gimp_gegl_index_to_mask (GeglBuffer *indexed_buffer,
});
}
gboolean
gimp_gegl_is_index_used (GeglBuffer *indexed_buffer,
const GeglRectangle *indexed_rect,
const Babl *indexed_format,
gint index)
{
GRWLock lock;
gboolean found = FALSE;
g_rw_lock_init (&lock);
if (! indexed_rect)
indexed_rect = gegl_buffer_get_extent (indexed_buffer);
gegl_parallel_distribute_area (
indexed_rect, PIXELS_PER_THREAD,
[&] (const GeglRectangle *indexed_area)
{
GeglBufferIterator *iter;
iter = gegl_buffer_iterator_new (indexed_buffer, indexed_area, 0,
indexed_format,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
while (gegl_buffer_iterator_next (iter))
{
const guchar *indexed = (const guchar *) iter->items[0].data;
gint count = iter->length;
while (count--)
{
if (*indexed == index)
{
/*
* Position of one item using this color index:
gint x = iter->items[0].roi.x + (iter->length - count - 1) % iter->items[0].roi.width;
gint y = iter->items[0].roi.y + (gint) ((iter->length - count - 1) / iter->items[0].roi.width);
*/
g_rw_lock_writer_lock (&lock);
found = TRUE;
g_rw_lock_writer_unlock (&lock);
break;
}
else
{
g_rw_lock_reader_lock (&lock);
if (found)
{
g_rw_lock_reader_unlock (&lock);
break;
}
g_rw_lock_reader_unlock (&lock);
}
indexed++;
}
if (count > 0)
{
gegl_buffer_iterator_stop (iter);
break;
}
g_rw_lock_reader_lock (&lock);
if (found)
{
g_rw_lock_reader_unlock (&lock);
gegl_buffer_iterator_stop (iter);
break;
}
g_rw_lock_reader_unlock (&lock);
}
});
g_rw_lock_clear (&lock);
return found;
}
void
gimp_gegl_shift_index (GeglBuffer *indexed_buffer,
const GeglRectangle *indexed_rect,
const Babl *indexed_format,
gint from_index,
gint shift)
{
if (! indexed_rect)
indexed_rect = gegl_buffer_get_extent (indexed_buffer);
gegl_parallel_distribute_area (
indexed_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *indexed_area)
{
GeglBufferIterator *iter;
iter = gegl_buffer_iterator_new (indexed_buffer, indexed_area, 0,
indexed_format,
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
while (gegl_buffer_iterator_next (iter))
{
guchar *indexed = (guchar *) iter->items[0].data;
gint count = iter->length;
while (count--)
{
if (*indexed >= from_index)
*indexed += shift;
indexed++;
}
}
});
}
static void
gimp_gegl_convert_color_profile_progress (GimpProgress *progress,
gdouble value)

View file

@ -87,6 +87,15 @@ void gimp_gegl_index_to_mask (GeglBuffer *indexed_buffer
GeglBuffer *mask_buffer,
const GeglRectangle *mask_rect,
gint index);
gboolean gimp_gegl_is_index_used (GeglBuffer *indexed_buffer,
const GeglRectangle *indexed_rect,
const Babl *indexed_format,
gint index);
void gimp_gegl_shift_index (GeglBuffer *indexed_buffer,
const GeglRectangle *indexed_rect,
const Babl *indexed_format,
gint from_index,
gint shift);
void gimp_gegl_convert_color_profile (GeglBuffer *src_buffer,
const GeglRectangle *src_rect,

View file

@ -37,6 +37,7 @@
#include "core/gimp.h"
#include "core/gimpcontext.h"
#include "core/gimpdatafactory.h"
#include "core/gimpimage-colormap.h"
#include "core/gimppalette.h"
#include "core/gimpparamspecs.h"
@ -304,9 +305,18 @@ palette_delete_entry_invoker (GimpProcedure *procedure,
GimpPaletteEntry *entry = gimp_palette_get_entry (palette, entry_num);
if (entry)
gimp_palette_delete_entry (palette, entry);
{
GimpImage *image = gimp_data_get_image (GIMP_DATA (palette));
if (image != NULL)
success = gimp_image_delete_colormap_entry (image, entry_num, TRUE);
else
gimp_palette_delete_entry (palette, entry);
}
else
success = FALSE;
{
success = FALSE;
}
}
else
success = FALSE;
@ -683,7 +693,8 @@ register_palette_procs (GimpPDB *pdb)
"gimp-palette-delete-entry");
gimp_procedure_set_static_help (procedure,
"Deletes an entry from the palette.",
"Deletes an entry from the palette. Returns an error if the index is out or range. Returns an error if the palette is not editable.",
"This function will fail and return %FALSE if the index is out or range or if the palette is not editable.\n"
"Additionally if the palette belongs to an indexed image, it will only be possible to delete palette colors not in use in the image.",
NULL);
gimp_procedure_set_static_attribution (procedure,
"Michael Natterer <mitch@gimp.org>",

View file

@ -326,8 +326,10 @@ gimp_palette_add_entry (GimpPalette *palette,
*
* Deletes an entry from the palette.
*
* Deletes an entry from the palette. Returns an error if the index is
* out or range. Returns an error if the palette is not editable.
* This function will fail and return %FALSE if the index is out or
* range or if the palette is not editable.
* Additionally if the palette belongs to an indexed image, it will
* only be possible to delete palette colors not in use in the image.
*
* Returns: TRUE on success.
*

View file

@ -251,9 +251,11 @@ sub palette_delete_entry {
$blurb = 'Deletes an entry from the palette.';
$help = <<'HELP';
Deletes an entry from the palette.
Returns an error if the index is out or range.
Returns an error if the palette is not editable.
This function will fail and return %FALSE if the index is out or range or if the
palette is not editable.
Additionally if the palette belongs to an indexed image, it will only be possible
to delete palette colors not in use in the image.
HELP
&mitch_pdb_misc('2004', '2.2');
@ -272,9 +274,18 @@ HELP
GimpPaletteEntry *entry = gimp_palette_get_entry (palette, entry_num);
if (entry)
gimp_palette_delete_entry (palette, entry);
{
GimpImage *image = gimp_data_get_image (GIMP_DATA (palette));
if (image != NULL)
success = gimp_image_delete_colormap_entry (image, entry_num, TRUE);
else
gimp_palette_delete_entry (palette, entry);
}
else
success = FALSE;
{
success = FALSE;
}
}
else
success = FALSE;
@ -420,6 +431,7 @@ CODE
"core/gimp.h"
"core/gimpcontext.h"
"core/gimpdatafactory.h"
"core/gimpimage-colormap.h"
"core/gimppalette.h"
"gimppdb-utils.h");