/* 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 "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "core-types.h" #include "pikachannel.h" #include "pikacontainer.h" #include "pikacontext.h" #include "pikagradient.h" #include "pikaimage.h" #include "pikaimage-colormap.h" #include "pikapalette.h" #include "pikapalette-import.h" #include "pikapalette-load.h" #include "pikapickable.h" #include "pika-intl.h" #define MAX_IMAGE_COLORS (10000 * 2) /* create a palette from a gradient ****************************************/ PikaPalette * pika_palette_import_from_gradient (PikaGradient *gradient, PikaContext *context, gboolean reverse, PikaGradientBlendColorSpace blend_color_space, const gchar *palette_name, gint n_colors) { PikaPalette *palette; PikaGradientSegment *seg = NULL; gdouble dx, cur_x; PikaRGB color; gint i; g_return_val_if_fail (PIKA_IS_GRADIENT (gradient), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (palette_name != NULL, NULL); g_return_val_if_fail (n_colors > 1, NULL); palette = PIKA_PALETTE (pika_palette_new (context, palette_name)); dx = 1.0 / (n_colors - 1); for (i = 0, cur_x = 0; i < n_colors; i++, cur_x += dx) { seg = pika_gradient_get_color_at (gradient, context, seg, cur_x, reverse, blend_color_space, &color); pika_palette_add_entry (palette, -1, NULL, &color); } return palette; } /* create a palette from a non-indexed image *******************************/ typedef struct _ImgColors ImgColors; struct _ImgColors { guint count; guint r_adj; guint g_adj; guint b_adj; guchar r; guchar g; guchar b; }; static gint count_color_entries = 0; static GHashTable * pika_palette_import_store_colors (GHashTable *table, guchar *colors, guchar *colors_real, gint n_colors) { gpointer found_color = NULL; ImgColors *new_color; guint key_colors = colors[0] * 256 * 256 + colors[1] * 256 + colors[2]; if (table == NULL) { table = g_hash_table_new (g_direct_hash, g_direct_equal); count_color_entries = 0; } else { found_color = g_hash_table_lookup (table, GUINT_TO_POINTER (key_colors)); } if (found_color == NULL) { if (count_color_entries > MAX_IMAGE_COLORS) { /* Don't add any more new ones */ return table; } count_color_entries++; new_color = g_slice_new (ImgColors); new_color->count = 1; new_color->r_adj = 0; new_color->g_adj = 0; new_color->b_adj = 0; new_color->r = colors[0]; new_color->g = colors[1]; new_color->b = colors[2]; g_hash_table_insert (table, GUINT_TO_POINTER (key_colors), new_color); } else { new_color = found_color; if (new_color->count < (G_MAXINT - 1)) new_color->count++; /* Now do the adjustments ...*/ new_color->r_adj += (colors_real[0] - colors[0]); new_color->g_adj += (colors_real[1] - colors[1]); new_color->b_adj += (colors_real[2] - colors[2]); /* Boundary conditions */ if(new_color->r_adj > (G_MAXINT - 255)) new_color->r_adj /= new_color->count; if(new_color->g_adj > (G_MAXINT - 255)) new_color->g_adj /= new_color->count; if(new_color->b_adj > (G_MAXINT - 255)) new_color->b_adj /= new_color->count; } return table; } static void pika_palette_import_create_list (gpointer key, gpointer value, gpointer user_data) { GSList **list = user_data; ImgColors *color_tab = value; *list = g_slist_prepend (*list, color_tab); } static gint pika_palette_import_sort_colors (gconstpointer a, gconstpointer b) { const ImgColors *s1 = a; const ImgColors *s2 = b; if(s1->count > s2->count) return -1; if(s1->count < s2->count) return 1; return 0; } static void pika_palette_import_create_image_palette (gpointer data, gpointer user_data) { PikaPalette *palette = user_data; ImgColors *color_tab = data; gint n_colors; gchar *lab; PikaRGB color; n_colors = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (palette), "import-n-colors")); if (pika_palette_get_n_colors (palette) >= n_colors) return; /* TRANSLATORS: the "%s" is an item title and "%u" is the number of occurrences for this item. */ lab = g_strdup_printf (_("%s (occurs %u)"), _("Untitled"), color_tab->count); /* Adjust the colors to the mean of the the sample */ pika_rgba_set_uchar (&color, (guchar) color_tab->r + (color_tab->r_adj / color_tab->count), (guchar) color_tab->g + (color_tab->g_adj / color_tab->count), (guchar) color_tab->b + (color_tab->b_adj / color_tab->count), 255); pika_palette_add_entry (palette, -1, lab, &color); g_free (lab); } static PikaPalette * pika_palette_import_make_palette (GHashTable *table, const gchar *palette_name, PikaContext *context, gint n_colors) { PikaPalette *palette; GSList *list = NULL; GSList *iter; palette = PIKA_PALETTE (pika_palette_new (context, palette_name)); if (! table) return palette; g_hash_table_foreach (table, pika_palette_import_create_list, &list); list = g_slist_sort (list, pika_palette_import_sort_colors); g_object_set_data (G_OBJECT (palette), "import-n-colors", GINT_TO_POINTER (n_colors)); g_slist_foreach (list, pika_palette_import_create_image_palette, palette); g_object_set_data (G_OBJECT (palette), "import-n-colors", NULL); /* Free up used memory * Note the same structure is on both the hash list and the sorted * list. So only delete it once. */ g_hash_table_destroy (table); for (iter = list; iter; iter = iter->next) g_slice_free (ImgColors, iter->data); g_slist_free (list); return palette; } static GHashTable * pika_palette_import_extract (PikaImage *image, GHashTable *colors, PikaPickable *pickable, gint pickable_off_x, gint pickable_off_y, gboolean selection_only, gint x, gint y, gint width, gint height, gint n_colors, gint threshold) { GeglBuffer *buffer; GeglBufferIterator *iter; GeglRectangle *mask_roi = NULL; GeglRectangle rect = { x, y, width, height }; const Babl *format; gint bpp; gint mask_bpp = 0; buffer = pika_pickable_get_buffer (pickable); format = babl_format ("R'G'B'A u8"); iter = gegl_buffer_iterator_new (buffer, &rect, 0, format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2); bpp = babl_format_get_bytes_per_pixel (format); if (selection_only && ! pika_channel_is_empty (pika_image_get_mask (image))) { PikaDrawable *mask = PIKA_DRAWABLE (pika_image_get_mask (image)); rect.x = x + pickable_off_x; rect.y = y + pickable_off_y; buffer = pika_drawable_get_buffer (mask); format = babl_format ("Y u8"); gegl_buffer_iterator_add (iter, buffer, &rect, 0, format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE); mask_roi = &iter->items[1].roi; mask_bpp = babl_format_get_bytes_per_pixel (format); } while (gegl_buffer_iterator_next (iter)) { const guchar *data = iter->items[0].data; const guchar *mask_data = NULL; gint length = iter->length; if (mask_roi) mask_data = iter->items[1].data; while (length--) { /* ignore unselected, and completely transparent pixels */ if ((! mask_data || *mask_data) && data[ALPHA]) { guchar rgba[MAX_CHANNELS] = { 0, }; guchar rgb_real[MAX_CHANNELS] = { 0, }; memcpy (rgba, data, 4); memcpy (rgb_real, rgba, 4); rgba[0] = (rgba[0] / threshold) * threshold; rgba[1] = (rgba[1] / threshold) * threshold; rgba[2] = (rgba[2] / threshold) * threshold; colors = pika_palette_import_store_colors (colors, rgba, rgb_real, n_colors); } data += bpp; if (mask_data) mask_data += mask_bpp; } } return colors; } PikaPalette * pika_palette_import_from_image (PikaImage *image, PikaContext *context, const gchar *palette_name, gint n_colors, gint threshold, gboolean selection_only) { GHashTable *colors; gint x, y; gint width, height; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (palette_name != NULL, NULL); g_return_val_if_fail (n_colors > 1, NULL); g_return_val_if_fail (threshold > 0, NULL); pika_pickable_flush (PIKA_PICKABLE (image)); if (selection_only) { pika_item_bounds (PIKA_ITEM (pika_image_get_mask (image)), &x, &y, &width, &height); } else { x = 0; y = 0; width = pika_image_get_width (image); height = pika_image_get_height (image); } colors = pika_palette_import_extract (image, NULL, PIKA_PICKABLE (image), 0, 0, selection_only, x, y, width, height, n_colors, threshold); return pika_palette_import_make_palette (colors, palette_name, context, n_colors); } /* create a palette from an indexed image **********************************/ PikaPalette * pika_palette_import_from_indexed_image (PikaImage *image, PikaContext *context, const gchar *palette_name) { PikaPalette *palette; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (pika_image_get_base_type (image) == PIKA_INDEXED, NULL); g_return_val_if_fail (palette_name != NULL, NULL); palette = PIKA_PALETTE (pika_data_duplicate (PIKA_DATA (pika_image_get_colormap_palette (image)))); pika_object_set_name (PIKA_OBJECT (palette), palette_name); return palette; } /* create a palette from a drawable ****************************************/ PikaPalette * pika_palette_import_from_drawables (GList *drawables, PikaContext *context, const gchar *palette_name, gint n_colors, gint threshold, gboolean selection_only) { GHashTable *colors = NULL; GList *iter; gint x, y; gint width, height; gint off_x, off_y; g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (palette_name != NULL, NULL); g_return_val_if_fail (n_colors > 1, NULL); g_return_val_if_fail (threshold > 0, NULL); for (iter = drawables; iter; iter = iter->next) { PikaDrawable *drawable = iter->data; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), NULL); if (selection_only) { if (! pika_item_mask_intersect (PIKA_ITEM (drawable), &x, &y, &width, &height)) return NULL; } else { x = 0; y = 0; width = pika_item_get_width (PIKA_ITEM (drawable)); height = pika_item_get_height (PIKA_ITEM (drawable)); } pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); colors = pika_palette_import_extract (pika_item_get_image (PIKA_ITEM (drawable)), colors, PIKA_PICKABLE (drawable), off_x, off_y, selection_only, x, y, width, height, n_colors, threshold); } return pika_palette_import_make_palette (colors, palette_name, context, n_colors); } /* create a palette from a file **********************************/ PikaPalette * pika_palette_import_from_file (PikaContext *context, GFile *file, const gchar *palette_name, GError **error) { GList *palette_list = NULL; GInputStream *input; GError *my_error = NULL; g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (G_IS_FILE (file), NULL); g_return_val_if_fail (palette_name != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error)); if (! input) { g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_OPEN, _("Could not open '%s' for reading: %s"), pika_file_get_utf8_name (file), my_error->message); g_clear_error (&my_error); return NULL; } switch (pika_palette_load_detect_format (file, input)) { case PIKA_PALETTE_FILE_FORMAT_GPL: palette_list = pika_palette_load (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_ACT: palette_list = pika_palette_load_act (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_RIFF_PAL: palette_list = pika_palette_load_riff (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_PSP_PAL: palette_list = pika_palette_load_psp (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_ACO: palette_list = pika_palette_load_aco (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_ACB: palette_list = pika_palette_load_acb (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_ASE: palette_list = pika_palette_load_ase (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_CSS: palette_list = pika_palette_load_css (context, file, input, error); break; case PIKA_PALETTE_FILE_FORMAT_SBZ: palette_list = pika_palette_load_sbz (context, file, input, error); break; default: g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ, _("Unknown type of palette file: %s"), pika_file_get_utf8_name (file)); break; } g_object_unref (input); if (palette_list) { PikaPalette *palette = g_object_ref (palette_list->data); pika_object_set_name (PIKA_OBJECT (palette), palette_name); g_list_free_full (palette_list, (GDestroyNotify) g_object_unref); return palette; } return NULL; }