/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 . */ #include "config.h" #include #include #include #include #include "libpikacolor/pikacolor.h" #include "core-types.h" #include "config/pikageglconfig.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-loops.h" #include "pika.h" #include "pikacontainer.h" #include "pikadatafactory.h" #include "pikadrawable.h" #include "pikaimage.h" #include "pikaimage-colormap.h" #include "pikaimage-private.h" #include "pikaimage-undo.h" #include "pikaimage-undo-push.h" #include "pikapalette.h" #include "pika-intl.h" typedef struct { GeglBuffer *buffer; const Babl *format; /* Shared by jobs. */ gboolean *found; GRWLock *lock; } IndexUsedJobData; /* local function prototype */ static void pika_image_colormap_set_palette_entry (PikaImage *image, const PikaRGB *color, gint index); static void pika_image_colormap_thread_is_index_used (IndexUsedJobData *data, gint index); /* public functions */ void pika_image_colormap_init (PikaImage *image) { PikaImagePrivate *private; PikaContainer *palettes; gchar *palette_name; gchar *palette_id; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (private->palette == NULL); palette_name = g_strdup_printf (_("Colormap of Image #%d (%s)"), pika_image_get_id (image), pika_image_get_display_name (image)); palette_id = g_strdup_printf ("pika-indexed-image-palette-%d", pika_image_get_id (image)); private->palette = PIKA_PALETTE (pika_palette_new (NULL, palette_name)); pika_image_colormap_update_formats (image); pika_palette_set_columns (private->palette, 16); pika_data_set_image (PIKA_DATA (private->palette), image, TRUE, FALSE); palettes = pika_data_factory_get_container (image->pika->palette_factory); pika_container_add (palettes, PIKA_OBJECT (private->palette)); g_free (palette_name); g_free (palette_id); } void pika_image_colormap_dispose (PikaImage *image) { PikaImagePrivate *private; PikaContainer *palettes; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (PIKA_IS_PALETTE (private->palette)); palettes = pika_data_factory_get_container (image->pika->palette_factory); pika_container_remove (palettes, PIKA_OBJECT (private->palette)); } void pika_image_colormap_free (PikaImage *image) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (PIKA_IS_PALETTE (private->palette)); g_clear_object (&private->palette); /* don't touch the image's babl_palettes because we might still have * buffers with that palette on the undo stack, and on undoing the * image back to indexed, we must have exactly these palettes around */ } void pika_image_colormap_update_formats (PikaImage *image) { PikaImagePrivate *private; const Babl *space; gchar *format_name; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); space = pika_image_get_layer_space (image); format_name = g_strdup_printf ("-pika-indexed-format-%d", pika_image_get_id (image)); babl_new_palette_with_space (format_name, space, &private->babl_palette_rgb, &private->babl_palette_rgba); g_free (format_name); if (private->palette && pika_palette_get_n_colors (private->palette) > 0) { guchar *colormap; gint n_colors; colormap = pika_image_get_colormap (image); n_colors = pika_image_get_colormap_size (image); babl_palette_set_palette (private->babl_palette_rgb, pika_babl_format (PIKA_RGB, private->precision, FALSE, space), colormap, n_colors); babl_palette_set_palette (private->babl_palette_rgba, pika_babl_format (PIKA_RGB, private->precision, FALSE, space), colormap, n_colors); g_free (colormap); } } const Babl * pika_image_colormap_get_rgb_format (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->babl_palette_rgb; } const Babl * pika_image_colormap_get_rgba_format (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->babl_palette_rgba; } PikaPalette * pika_image_get_colormap_palette (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->palette; } void pika_image_set_colormap_palette (PikaImage *image, PikaPalette *palette, gboolean push_undo) { PikaImagePrivate *private; PikaPaletteEntry *entry; gint n_colors, i; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (palette != NULL); n_colors = pika_palette_get_n_colors (palette); g_return_if_fail (n_colors >= 0 && n_colors <= 256); private = PIKA_IMAGE_GET_PRIVATE (image); if (push_undo) pika_image_undo_push_image_colormap (image, C_("undo-type", "Set Colormap")); if (!private->palette) pika_image_colormap_init (image); pika_data_freeze (PIKA_DATA (private->palette)); while ((entry = pika_palette_get_entry (private->palette, 0))) pika_palette_delete_entry (private->palette, entry); for (i = 0; i < n_colors; i++) { PikaPaletteEntry *entry = pika_palette_get_entry (palette, i); pika_image_colormap_set_palette_entry (image, &entry->color, i); } pika_data_thaw (PIKA_DATA (private->palette)); pika_image_colormap_changed (image, -1); } guchar * pika_image_get_colormap (PikaImage *image) { PikaImagePrivate *private; guchar *colormap = NULL; gint n_colors, i; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); if (private->palette == NULL) return NULL; n_colors = pika_palette_get_n_colors (private->palette); if (n_colors > 0) { colormap = g_new0 (guchar, PIKA_IMAGE_COLORMAP_SIZE); for (i = 0; i < n_colors; i++) { PikaPaletteEntry *entry = pika_palette_get_entry (private->palette, i); pika_rgb_get_uchar (&entry->color, &colormap[i * 3], &colormap[i * 3 + 1], &colormap[i * 3 + 2]); } } return colormap; } gint pika_image_get_colormap_size (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); private = PIKA_IMAGE_GET_PRIVATE (image); if (private->palette == NULL) return 0; return pika_palette_get_n_colors (private->palette); } void pika_image_set_colormap (PikaImage *image, const guchar *colormap, gint n_colors, gboolean push_undo) { PikaImagePrivate *private; PikaPaletteEntry *entry; gint i; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (colormap != NULL || n_colors == 0); g_return_if_fail (n_colors >= 0 && n_colors <= 256); private = PIKA_IMAGE_GET_PRIVATE (image); if (push_undo) pika_image_undo_push_image_colormap (image, C_("undo-type", "Set Colormap")); if (!private->palette) pika_image_colormap_init (image); pika_data_freeze (PIKA_DATA (private->palette)); while ((entry = pika_palette_get_entry (private->palette, 0))) pika_palette_delete_entry (private->palette, entry); for (i = 0; i < n_colors; i++) { PikaRGB color; pika_rgba_set_uchar (&color, colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2], 255); pika_image_colormap_set_palette_entry (image, &color, i); } pika_image_colormap_changed (image, -1); pika_data_thaw (PIKA_DATA (private->palette)); } void pika_image_unset_colormap (PikaImage *image, gboolean push_undo) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); if (push_undo) pika_image_undo_push_image_colormap (image, C_("undo-type", "Unset Colormap")); if (private->palette) { pika_image_colormap_dispose (image); pika_image_colormap_free (image); } pika_image_colormap_changed (image, -1); } gboolean pika_image_colormap_is_index_used (PikaImage *image, gint color_index) { GList *layers; GList *iter; GThreadPool *pool; GRWLock lock; gboolean found = FALSE; gint num_processors; g_rw_lock_init (&lock); num_processors = PIKA_GEGL_CONFIG (image->pika->config)->num_processors; layers = pika_image_get_layer_list (image); pool = g_thread_pool_new_full ((GFunc) pika_image_colormap_thread_is_index_used, GINT_TO_POINTER (color_index), (GDestroyNotify) g_free, num_processors, TRUE, NULL); for (iter = layers; iter; iter = g_list_next (iter)) { IndexUsedJobData *job_data; job_data = g_malloc (sizeof (IndexUsedJobData )); job_data->buffer = pika_drawable_get_buffer (iter->data); job_data->format = pika_drawable_get_format_without_alpha (iter->data); job_data->found = &found; job_data->lock = &lock; g_thread_pool_push (pool, job_data, NULL); } g_thread_pool_free (pool, FALSE, TRUE); g_rw_lock_clear (&lock); g_list_free (layers); return found; } void pika_image_get_colormap_entry (PikaImage *image, gint color_index, PikaRGB *color) { PikaImagePrivate *private; PikaPaletteEntry *entry; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (private->palette != NULL); g_return_if_fail (color_index >= 0 && color_index < pika_palette_get_n_colors (private->palette)); g_return_if_fail (color != NULL); entry = pika_palette_get_entry (private->palette, color_index); g_return_if_fail (entry != NULL); *color = entry->color; } void pika_image_set_colormap_entry (PikaImage *image, gint color_index, const PikaRGB *color, gboolean push_undo) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (private->palette != NULL); g_return_if_fail (color_index >= 0 && color_index < pika_palette_get_n_colors (private->palette)); g_return_if_fail (color != NULL); if (push_undo) pika_image_undo_push_image_colormap (image, C_("undo-type", "Change Colormap entry")); pika_image_colormap_set_palette_entry (image, color, color_index); pika_image_colormap_changed (image, color_index); } void pika_image_add_colormap_entry (PikaImage *image, const PikaRGB *color) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (private->palette != NULL); g_return_if_fail (pika_palette_get_n_colors (private->palette) < 256); g_return_if_fail (color != NULL); pika_image_undo_push_image_colormap (image, C_("undo-type", "Add Color to Colormap")); pika_image_colormap_set_palette_entry (image, color, pika_palette_get_n_colors (private->palette)); pika_image_colormap_changed (image, -1); } gboolean pika_image_delete_colormap_entry (PikaImage *image, gint color_index, gboolean push_undo) { g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); if (! pika_image_colormap_is_index_used (image, color_index)) { PikaImagePrivate *private; PikaPaletteEntry *entry; GList *layers; GList *iter; if (push_undo) { pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_COLORMAP_REMAP, C_("undo-type", "Delete Colormap entry")); pika_image_undo_push_image_colormap (image, NULL); } private = PIKA_IMAGE_GET_PRIVATE (image); layers = pika_image_get_layer_list (image); for (iter = layers; iter; iter = g_list_next (iter)) { if (push_undo) pika_image_undo_push_drawable_mod (image, NULL, iter->data, TRUE); pika_gegl_shift_index (pika_drawable_get_buffer (iter->data), NULL, pika_drawable_get_format (iter->data), color_index, -1); } entry = pika_palette_get_entry (private->palette, color_index); pika_palette_delete_entry (private->palette, entry); g_list_free (layers); if (push_undo) pika_image_undo_group_end (image); pika_image_colormap_changed (image, -1); return TRUE; } return FALSE; } /* private functions */ static void pika_image_colormap_set_palette_entry (PikaImage *image, const PikaRGB *color, gint index) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); PikaRGB black; gchar name[64]; g_return_if_fail (color != NULL); pika_rgb_set (&black, 0, 0, 0); while (pika_palette_get_n_colors (private->palette) <= index) pika_palette_add_entry (private->palette, index, name, &black); g_snprintf (name, sizeof (name), "#%d", index); pika_palette_set_entry (private->palette, index, name, color); } static void pika_image_colormap_thread_is_index_used (IndexUsedJobData *data, gint index) { g_rw_lock_reader_lock (data->lock); if (*data->found) { g_rw_lock_reader_unlock (data->lock); return; } g_rw_lock_reader_unlock (data->lock); if (pika_gegl_is_index_used (data->buffer, NULL, data->format, index)) { g_rw_lock_writer_lock (data->lock); *data->found = TRUE; g_rw_lock_writer_unlock (data->lock); } }