1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <string.h>
21 
22 #include <cairo.h>
23 #include <gegl.h>
24 #include <gdk-pixbuf/gdk-pixbuf.h>
25 
26 #include "libgimpbase/gimpbase.h"
27 #include "libgimpcolor/gimpcolor.h"
28 
29 #include "core-types.h"
30 
31 #include "gimp-memsize.h"
32 #include "gimppalette.h"
33 #include "gimppalette-load.h"
34 #include "gimppalette-save.h"
35 #include "gimptagged.h"
36 #include "gimptempbuf.h"
37 
38 #include "gimp-intl.h"
39 
40 
41 #define RGB_EPSILON 1e-6
42 
43 
44 /*  local function prototypes  */
45 
46 static void          gimp_palette_tagged_iface_init (GimpTaggedInterface  *iface);
47 
48 static void          gimp_palette_finalize          (GObject              *object);
49 
50 static gint64        gimp_palette_get_memsize       (GimpObject           *object,
51                                                      gint64               *gui_size);
52 
53 static void          gimp_palette_get_preview_size  (GimpViewable         *viewable,
54                                                      gint                  size,
55                                                      gboolean              popup,
56                                                      gboolean              dot_for_dot,
57                                                      gint                 *width,
58                                                      gint                 *height);
59 static gboolean      gimp_palette_get_popup_size    (GimpViewable         *viewable,
60                                                      gint                  width,
61                                                      gint                  height,
62                                                      gboolean              dot_for_dot,
63                                                      gint                 *popup_width,
64                                                      gint                 *popup_height);
65 static GimpTempBuf * gimp_palette_get_new_preview   (GimpViewable         *viewable,
66                                                      GimpContext          *context,
67                                                      gint                  width,
68                                                      gint                  height);
69 static gchar       * gimp_palette_get_description   (GimpViewable         *viewable,
70                                                      gchar               **tooltip);
71 static const gchar * gimp_palette_get_extension     (GimpData             *data);
72 static void          gimp_palette_copy              (GimpData             *data,
73                                                      GimpData             *src_data);
74 
75 static void          gimp_palette_entry_free        (GimpPaletteEntry     *entry);
76 static gint64        gimp_palette_entry_get_memsize (GimpPaletteEntry     *entry,
77                                                      gint64               *gui_size);
78 static gchar       * gimp_palette_get_checksum      (GimpTagged           *tagged);
79 
80 
G_DEFINE_TYPE_WITH_CODE(GimpPalette,gimp_palette,GIMP_TYPE_DATA,G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,gimp_palette_tagged_iface_init))81 G_DEFINE_TYPE_WITH_CODE (GimpPalette, gimp_palette, GIMP_TYPE_DATA,
82                          G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
83                                                 gimp_palette_tagged_iface_init))
84 
85 #define parent_class gimp_palette_parent_class
86 
87 
88 static void
89 gimp_palette_class_init (GimpPaletteClass *klass)
90 {
91   GObjectClass      *object_class      = G_OBJECT_CLASS (klass);
92   GimpObjectClass   *gimp_object_class = GIMP_OBJECT_CLASS (klass);
93   GimpViewableClass *viewable_class    = GIMP_VIEWABLE_CLASS (klass);
94   GimpDataClass     *data_class        = GIMP_DATA_CLASS (klass);
95 
96   object_class->finalize            = gimp_palette_finalize;
97 
98   gimp_object_class->get_memsize    = gimp_palette_get_memsize;
99 
100   viewable_class->default_icon_name = "gtk-select-color";
101   viewable_class->get_preview_size  = gimp_palette_get_preview_size;
102   viewable_class->get_popup_size    = gimp_palette_get_popup_size;
103   viewable_class->get_new_preview   = gimp_palette_get_new_preview;
104   viewable_class->get_description   = gimp_palette_get_description;
105 
106   data_class->save                  = gimp_palette_save;
107   data_class->get_extension         = gimp_palette_get_extension;
108   data_class->copy                  = gimp_palette_copy;
109 }
110 
111 static void
gimp_palette_tagged_iface_init(GimpTaggedInterface * iface)112 gimp_palette_tagged_iface_init (GimpTaggedInterface *iface)
113 {
114   iface->get_checksum = gimp_palette_get_checksum;
115 }
116 
117 static void
gimp_palette_init(GimpPalette * palette)118 gimp_palette_init (GimpPalette *palette)
119 {
120   palette->colors    = NULL;
121   palette->n_colors  = 0;
122   palette->n_columns = 0;
123 }
124 
125 static void
gimp_palette_finalize(GObject * object)126 gimp_palette_finalize (GObject *object)
127 {
128   GimpPalette *palette = GIMP_PALETTE (object);
129 
130   if (palette->colors)
131     {
132       g_list_free_full (palette->colors,
133                         (GDestroyNotify) gimp_palette_entry_free);
134       palette->colors = NULL;
135     }
136 
137   G_OBJECT_CLASS (parent_class)->finalize (object);
138 }
139 
140 static gint64
gimp_palette_get_memsize(GimpObject * object,gint64 * gui_size)141 gimp_palette_get_memsize (GimpObject *object,
142                           gint64     *gui_size)
143 {
144   GimpPalette *palette = GIMP_PALETTE (object);
145   gint64       memsize = 0;
146 
147   memsize += gimp_g_list_get_memsize_foreach (palette->colors,
148                                               (GimpMemsizeFunc)
149                                               gimp_palette_entry_get_memsize,
150                                               gui_size);
151 
152   return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
153                                                                   gui_size);
154 }
155 
156 static void
gimp_palette_get_preview_size(GimpViewable * viewable,gint size,gboolean popup,gboolean dot_for_dot,gint * width,gint * height)157 gimp_palette_get_preview_size (GimpViewable *viewable,
158                                gint          size,
159                                gboolean      popup,
160                                gboolean      dot_for_dot,
161                                gint         *width,
162                                gint         *height)
163 {
164   *width  = size;
165   *height = 1 + size / 2;
166 }
167 
168 static gboolean
gimp_palette_get_popup_size(GimpViewable * viewable,gint width,gint height,gboolean dot_for_dot,gint * popup_width,gint * popup_height)169 gimp_palette_get_popup_size (GimpViewable *viewable,
170                              gint          width,
171                              gint          height,
172                              gboolean      dot_for_dot,
173                              gint         *popup_width,
174                              gint         *popup_height)
175 {
176   GimpPalette *palette = GIMP_PALETTE (viewable);
177   gint         p_width;
178   gint         p_height;
179 
180   if (! palette->n_colors)
181     return FALSE;
182 
183   if (palette->n_columns)
184     p_width = palette->n_columns;
185   else
186     p_width = MIN (palette->n_colors, 16);
187 
188   p_height = MAX (1, palette->n_colors / p_width);
189 
190   if (p_width * 4 > width || p_height * 4 > height)
191     {
192       *popup_width  = p_width  * 4;
193       *popup_height = p_height * 4;
194 
195       return TRUE;
196     }
197 
198   return FALSE;
199 }
200 
201 static GimpTempBuf *
gimp_palette_get_new_preview(GimpViewable * viewable,GimpContext * context,gint width,gint height)202 gimp_palette_get_new_preview (GimpViewable *viewable,
203                               GimpContext  *context,
204                               gint          width,
205                               gint          height)
206 {
207   GimpPalette *palette  = GIMP_PALETTE (viewable);
208   GimpTempBuf *temp_buf;
209   guchar      *buf;
210   guchar      *b;
211   GList       *list;
212   gint         columns;
213   gint         rows;
214   gint         cell_size;
215   gint         x, y;
216 
217   temp_buf = gimp_temp_buf_new (width, height, babl_format ("R'G'B' u8"));
218   memset (gimp_temp_buf_get_data (temp_buf), 255, width * height * 3);
219 
220   if (palette->n_columns > 1)
221     cell_size = MAX (4, width / palette->n_columns);
222   else
223     cell_size = 4;
224 
225   columns = width  / cell_size;
226   rows    = height / cell_size;
227 
228   buf = gimp_temp_buf_get_data (temp_buf);
229   b   = g_new (guchar, width * 3);
230 
231   list = palette->colors;
232 
233   for (y = 0; y < rows && list; y++)
234     {
235       gint i;
236 
237       memset (b, 255, width * 3);
238 
239       for (x = 0; x < columns && list; x++)
240         {
241           GimpPaletteEntry *entry = list->data;
242 
243           list = g_list_next (list);
244 
245           gimp_rgb_get_uchar (&entry->color,
246                               &b[x * cell_size * 3 + 0],
247                               &b[x * cell_size * 3 + 1],
248                               &b[x * cell_size * 3 + 2]);
249 
250           for (i = 1; i < cell_size; i++)
251             {
252               b[(x * cell_size + i) * 3 + 0] = b[(x * cell_size) * 3 + 0];
253               b[(x * cell_size + i) * 3 + 1] = b[(x * cell_size) * 3 + 1];
254               b[(x * cell_size + i) * 3 + 2] = b[(x * cell_size) * 3 + 2];
255             }
256         }
257 
258       for (i = 0; i < cell_size; i++)
259         memcpy (buf + ((y * cell_size + i) * width) * 3, b, width * 3);
260     }
261 
262   g_free (b);
263 
264   return temp_buf;
265 }
266 
267 static gchar *
gimp_palette_get_description(GimpViewable * viewable,gchar ** tooltip)268 gimp_palette_get_description (GimpViewable  *viewable,
269                               gchar        **tooltip)
270 {
271   GimpPalette *palette = GIMP_PALETTE (viewable);
272 
273   return g_strdup_printf ("%s (%d)",
274                           gimp_object_get_name (palette),
275                           palette->n_colors);
276 }
277 
278 GimpData *
gimp_palette_new(GimpContext * context,const gchar * name)279 gimp_palette_new (GimpContext *context,
280                   const gchar *name)
281 {
282   g_return_val_if_fail (name != NULL, NULL);
283   g_return_val_if_fail (*name != '\0', NULL);
284 
285   return g_object_new (GIMP_TYPE_PALETTE,
286                        "name", name,
287                        NULL);
288 }
289 
290 GimpData *
gimp_palette_get_standard(GimpContext * context)291 gimp_palette_get_standard (GimpContext *context)
292 {
293   static GimpData *standard_palette = NULL;
294 
295   if (! standard_palette)
296     {
297       standard_palette = gimp_palette_new (context, "Standard");
298 
299       gimp_data_clean (standard_palette);
300       gimp_data_make_internal (standard_palette, "gimp-palette-standard");
301 
302       g_object_add_weak_pointer (G_OBJECT (standard_palette),
303                                  (gpointer *) &standard_palette);
304     }
305 
306   return standard_palette;
307 }
308 
309 static const gchar *
gimp_palette_get_extension(GimpData * data)310 gimp_palette_get_extension (GimpData *data)
311 {
312   return GIMP_PALETTE_FILE_EXTENSION;
313 }
314 
315 static void
gimp_palette_copy(GimpData * data,GimpData * src_data)316 gimp_palette_copy (GimpData *data,
317                    GimpData *src_data)
318 {
319   GimpPalette *palette     = GIMP_PALETTE (data);
320   GimpPalette *src_palette = GIMP_PALETTE (src_data);
321   GList       *list;
322 
323   gimp_data_freeze (data);
324 
325   if (palette->colors)
326     {
327       g_list_free_full (palette->colors,
328                         (GDestroyNotify) gimp_palette_entry_free);
329       palette->colors = NULL;
330     }
331 
332   palette->n_colors  = 0;
333   palette->n_columns = src_palette->n_columns;
334 
335   for (list = src_palette->colors; list; list = g_list_next (list))
336     {
337       GimpPaletteEntry *entry = list->data;
338 
339       gimp_palette_add_entry (palette, -1, entry->name, &entry->color);
340     }
341 
342   gimp_data_thaw (data);
343 }
344 
345 static gchar *
gimp_palette_get_checksum(GimpTagged * tagged)346 gimp_palette_get_checksum (GimpTagged *tagged)
347 {
348   GimpPalette *palette         = GIMP_PALETTE (tagged);
349   gchar       *checksum_string = NULL;
350 
351   if (palette->n_colors > 0)
352     {
353       GChecksum *checksum       = g_checksum_new (G_CHECKSUM_MD5);
354       GList     *color_iterator = palette->colors;
355 
356       g_checksum_update (checksum, (const guchar *) &palette->n_colors, sizeof (palette->n_colors));
357       g_checksum_update (checksum, (const guchar *) &palette->n_columns, sizeof (palette->n_columns));
358 
359       while (color_iterator)
360         {
361           GimpPaletteEntry *entry = (GimpPaletteEntry *) color_iterator->data;
362 
363           g_checksum_update (checksum, (const guchar *) &entry->color, sizeof (entry->color));
364           if (entry->name)
365             g_checksum_update (checksum, (const guchar *) entry->name, strlen (entry->name));
366 
367           color_iterator = g_list_next (color_iterator);
368         }
369 
370       checksum_string = g_strdup (g_checksum_get_string (checksum));
371 
372       g_checksum_free (checksum);
373     }
374 
375   return checksum_string;
376 }
377 
378 
379 /*  public functions  */
380 
381 GList *
gimp_palette_get_colors(GimpPalette * palette)382 gimp_palette_get_colors (GimpPalette *palette)
383 {
384   g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
385 
386   return palette->colors;
387 }
388 
389 gint
gimp_palette_get_n_colors(GimpPalette * palette)390 gimp_palette_get_n_colors (GimpPalette *palette)
391 {
392   g_return_val_if_fail (GIMP_IS_PALETTE (palette), 0);
393 
394   return palette->n_colors;
395 }
396 
397 void
gimp_palette_move_entry(GimpPalette * palette,GimpPaletteEntry * entry,gint position)398 gimp_palette_move_entry (GimpPalette      *palette,
399                          GimpPaletteEntry *entry,
400                          gint              position)
401 {
402   GList *list;
403   gint   pos = 0;
404 
405   g_return_if_fail (GIMP_IS_PALETTE (palette));
406   g_return_if_fail (entry != NULL);
407 
408   if (g_list_find (palette->colors, entry))
409     {
410       pos = entry->position;
411 
412       if (entry->position == position)
413         return;
414 
415       entry->position = position;
416       palette->colors = g_list_remove (palette->colors,
417                                        entry);
418       palette->colors = g_list_insert (palette->colors,
419                                        entry, position);
420 
421       if (pos < position)
422         {
423           for (list = g_list_nth (palette->colors, pos);
424                list && pos < position;
425                list = g_list_next (list))
426             {
427               entry = (GimpPaletteEntry *) list->data;
428 
429               entry->position = pos++;
430             }
431         }
432       else
433         {
434           for (list = g_list_nth (palette->colors, position + 1);
435                list && position < pos;
436                list = g_list_next (list))
437             {
438               entry = (GimpPaletteEntry *) list->data;
439 
440               entry->position += 1;
441               pos--;
442             }
443         }
444 
445       gimp_data_dirty (GIMP_DATA (palette));
446     }
447 }
448 
449 GimpPaletteEntry *
gimp_palette_add_entry(GimpPalette * palette,gint position,const gchar * name,const GimpRGB * color)450 gimp_palette_add_entry (GimpPalette   *palette,
451                         gint           position,
452                         const gchar   *name,
453                         const GimpRGB *color)
454 {
455   GimpPaletteEntry *entry;
456 
457   g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
458   g_return_val_if_fail (color != NULL, NULL);
459 
460   entry = g_slice_new0 (GimpPaletteEntry);
461 
462   entry->color = *color;
463   entry->name  = g_strdup (name ? name : _("Untitled"));
464 
465   if (position < 0 || position >= palette->n_colors)
466     {
467       entry->position = palette->n_colors;
468       palette->colors = g_list_append (palette->colors, entry);
469     }
470   else
471     {
472       GList *list;
473 
474       entry->position = position;
475       palette->colors = g_list_insert (palette->colors, entry, position);
476 
477       /* renumber the displaced entries */
478       for (list = g_list_nth (palette->colors, position + 1);
479            list;
480            list = g_list_next (list))
481         {
482           GimpPaletteEntry *entry2 = list->data;
483 
484           entry2->position += 1;
485         }
486     }
487 
488   palette->n_colors += 1;
489 
490   gimp_data_dirty (GIMP_DATA (palette));
491 
492   return entry;
493 }
494 
495 void
gimp_palette_delete_entry(GimpPalette * palette,GimpPaletteEntry * entry)496 gimp_palette_delete_entry (GimpPalette      *palette,
497                            GimpPaletteEntry *entry)
498 {
499   GList *list;
500   gint   pos = 0;
501 
502   g_return_if_fail (GIMP_IS_PALETTE (palette));
503   g_return_if_fail (entry != NULL);
504 
505   if (g_list_find (palette->colors, entry))
506     {
507       pos = entry->position;
508       gimp_palette_entry_free (entry);
509 
510       palette->colors = g_list_remove (palette->colors, entry);
511 
512       palette->n_colors--;
513 
514       for (list = g_list_nth (palette->colors, pos);
515            list;
516            list = g_list_next (list))
517         {
518           entry = (GimpPaletteEntry *) list->data;
519 
520           entry->position = pos++;
521         }
522 
523       gimp_data_dirty (GIMP_DATA (palette));
524     }
525 }
526 
527 gboolean
gimp_palette_set_entry(GimpPalette * palette,gint position,const gchar * name,const GimpRGB * color)528 gimp_palette_set_entry (GimpPalette   *palette,
529                         gint           position,
530                         const gchar   *name,
531                         const GimpRGB *color)
532 {
533   GimpPaletteEntry *entry;
534 
535   g_return_val_if_fail (GIMP_IS_PALETTE (palette), FALSE);
536   g_return_val_if_fail (color != NULL, FALSE);
537 
538   entry = gimp_palette_get_entry (palette, position);
539 
540   if (! entry)
541     return FALSE;
542 
543   entry->color = *color;
544 
545   if (entry->name)
546     g_free (entry->name);
547 
548   entry->name = g_strdup (name);
549 
550   gimp_data_dirty (GIMP_DATA (palette));
551 
552   return TRUE;
553 }
554 
555 gboolean
gimp_palette_set_entry_color(GimpPalette * palette,gint position,const GimpRGB * color)556 gimp_palette_set_entry_color (GimpPalette   *palette,
557                               gint           position,
558                               const GimpRGB *color)
559 {
560   GimpPaletteEntry *entry;
561 
562   g_return_val_if_fail (GIMP_IS_PALETTE (palette), FALSE);
563   g_return_val_if_fail (color != NULL, FALSE);
564 
565   entry = gimp_palette_get_entry (palette, position);
566 
567   if (! entry)
568     return FALSE;
569 
570   entry->color = *color;
571 
572   gimp_data_dirty (GIMP_DATA (palette));
573 
574   return TRUE;
575 }
576 
577 gboolean
gimp_palette_set_entry_name(GimpPalette * palette,gint position,const gchar * name)578 gimp_palette_set_entry_name (GimpPalette *palette,
579                              gint         position,
580                              const gchar *name)
581 {
582   GimpPaletteEntry *entry;
583 
584   g_return_val_if_fail (GIMP_IS_PALETTE (palette), FALSE);
585 
586   entry = gimp_palette_get_entry (palette, position);
587 
588   if (! entry)
589     return FALSE;
590 
591   if (entry->name)
592     g_free (entry->name);
593 
594   entry->name = g_strdup (name);
595 
596   gimp_data_dirty (GIMP_DATA (palette));
597 
598   return TRUE;
599 }
600 
601 GimpPaletteEntry *
gimp_palette_get_entry(GimpPalette * palette,gint position)602 gimp_palette_get_entry (GimpPalette *palette,
603                         gint         position)
604 {
605   g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
606 
607   return g_list_nth_data (palette->colors, position);
608 }
609 
610 void
gimp_palette_set_columns(GimpPalette * palette,gint columns)611 gimp_palette_set_columns (GimpPalette *palette,
612                           gint         columns)
613 {
614   g_return_if_fail (GIMP_IS_PALETTE (palette));
615 
616   columns = CLAMP (columns, 0, 64);
617 
618   if (palette->n_columns != columns)
619     {
620       palette->n_columns = columns;
621 
622       gimp_data_dirty (GIMP_DATA (palette));
623     }
624 }
625 
626 gint
gimp_palette_get_columns(GimpPalette * palette)627 gimp_palette_get_columns (GimpPalette *palette)
628 {
629   g_return_val_if_fail (GIMP_IS_PALETTE (palette), 0);
630 
631   return palette->n_columns;
632 }
633 
634 GimpPaletteEntry *
gimp_palette_find_entry(GimpPalette * palette,const GimpRGB * color,GimpPaletteEntry * start_from)635 gimp_palette_find_entry (GimpPalette      *palette,
636                          const GimpRGB    *color,
637                          GimpPaletteEntry *start_from)
638 {
639   GimpPaletteEntry *entry;
640 
641   g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
642   g_return_val_if_fail (color != NULL, NULL);
643 
644   if (! start_from)
645     {
646       GList *list;
647 
648       /* search from the start */
649 
650       for (list = palette->colors; list; list = g_list_next (list))
651         {
652           entry = (GimpPaletteEntry *) list->data;
653           if (gimp_rgb_distance (&entry->color, color) < RGB_EPSILON)
654             return entry;
655         }
656     }
657   else if (gimp_rgb_distance (&start_from->color, color) < RGB_EPSILON)
658     {
659       return start_from;
660     }
661   else
662     {
663       GList *old = g_list_find (palette->colors, start_from);
664       GList *next;
665       GList *prev;
666 
667       g_return_val_if_fail (old != NULL, NULL);
668 
669       next = old->next;
670       prev = old->prev;
671 
672       /* proximity-based search */
673 
674       while (next || prev)
675         {
676           if (next)
677             {
678               entry = (GimpPaletteEntry *) next->data;
679               if (gimp_rgb_distance (&entry->color, color) < RGB_EPSILON)
680                 return entry;
681 
682               next = next->next;
683             }
684 
685           if (prev)
686             {
687               entry = (GimpPaletteEntry *) prev->data;
688               if (gimp_rgb_distance (&entry->color, color) < RGB_EPSILON)
689                 return entry;
690 
691               prev = prev->prev;
692             }
693         }
694     }
695 
696   return NULL;
697 }
698 
699 
700 /*  private functions  */
701 
702 static void
gimp_palette_entry_free(GimpPaletteEntry * entry)703 gimp_palette_entry_free (GimpPaletteEntry *entry)
704 {
705   g_return_if_fail (entry != NULL);
706 
707   g_free (entry->name);
708 
709   g_slice_free (GimpPaletteEntry, entry);
710 }
711 
712 static gint64
gimp_palette_entry_get_memsize(GimpPaletteEntry * entry,gint64 * gui_size)713 gimp_palette_entry_get_memsize (GimpPaletteEntry *entry,
714                                 gint64           *gui_size)
715 {
716   gint64 memsize = sizeof (GimpPaletteEntry);
717 
718   memsize += gimp_string_get_memsize (entry->name);
719 
720   return memsize;
721 }
722