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