1 /*
2 * smooth palette - derive smooth palette from image
3 * Copyright (C) 1997 Scott Draves <spot@cs.cmu.edu>
4 *
5 * GIMP - The GNU Image Manipulation Program
6 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <string.h>
25
26 #include <libgimp/gimp.h>
27 #include <libgimp/gimpui.h>
28
29 #include "libgimp/stdplugins-intl.h"
30
31
32 #define PLUG_IN_PROC "plug-in-smooth-palette"
33 #define PLUG_IN_BINARY "smooth-palette"
34 #define PLUG_IN_ROLE "gimp-smooth-palette"
35
36
37 /* Declare local functions. */
38 static void query (void);
39 static void run (const gchar *name,
40 gint nparams,
41 const GimpParam *param,
42 gint *nreturn_vals,
43 GimpParam **return_vals);
44
45 static gboolean dialog (gint32 drawable_id);
46
47 static gint32 smooth_palette (gint32 drawable_id,
48 gint32 *layer_id);
49
50
51 const GimpPlugInInfo PLUG_IN_INFO =
52 {
53 NULL, /* init_proc */
54 NULL, /* quit_proc */
55 query, /* query_proc */
56 run, /* run_proc */
57 };
58
59
MAIN()60 MAIN ()
61
62 static void
63 query (void)
64 {
65 static const GimpParamDef args[] =
66 {
67 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
68 { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
69 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
70 { GIMP_PDB_INT32, "width", "Width" },
71 { GIMP_PDB_INT32, "height", "Height" },
72 { GIMP_PDB_INT32, "ntries", "Search Depth" },
73 { GIMP_PDB_INT32, "show-image", "Show Image?" }
74 };
75
76 static const GimpParamDef return_vals[] =
77 {
78 { GIMP_PDB_IMAGE, "new-image", "Output image" },
79 { GIMP_PDB_LAYER, "new-layer", "Output layer" }
80 };
81
82 gimp_install_procedure (PLUG_IN_PROC,
83 N_("Derive a smooth color palette from the image"),
84 "help!",
85 "Scott Draves",
86 "Scott Draves",
87 "1997",
88 N_("Smoo_th Palette..."),
89 "RGB*",
90 GIMP_PLUGIN,
91 G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals),
92 args, return_vals);
93
94 gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Colors/Info");
95 }
96
97 static struct
98 {
99 gint width;
100 gint height;
101 gint ntries;
102 gint try_size;
103 gint show_image;
104 } config =
105 {
106 256,
107 64,
108 50,
109 10000,
110 1
111 };
112
113 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)114 run (const gchar *name,
115 gint nparams,
116 const GimpParam *param,
117 gint *nreturn_vals,
118 GimpParam **return_vals)
119 {
120 static GimpParam values[3];
121 GimpRunMode run_mode;
122 gint32 drawable_id;
123 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
124
125 run_mode = param[0].data.d_int32;
126
127 INIT_I18N ();
128
129 *nreturn_vals = 3;
130 *return_vals = values;
131
132 values[0].type = GIMP_PDB_STATUS;
133 values[0].data.d_status = status;
134 values[1].type = GIMP_PDB_IMAGE;
135 values[2].type = GIMP_PDB_LAYER;
136
137 drawable_id = param[2].data.d_drawable;
138
139 switch (run_mode)
140 {
141 case GIMP_RUN_INTERACTIVE:
142 gimp_get_data (PLUG_IN_PROC, &config);
143 if (! dialog (drawable_id))
144 return;
145 break;
146
147 case GIMP_RUN_NONINTERACTIVE:
148 if (nparams != 7)
149 {
150 status = GIMP_PDB_CALLING_ERROR;
151 }
152 else
153 {
154 config.width = param[3].data.d_int32;
155 config.height = param[4].data.d_int32;
156 config.ntries = param[5].data.d_int32;
157 config.show_image = param[6].data.d_int32 ? TRUE : FALSE;
158 }
159
160 if (status == GIMP_PDB_SUCCESS &&
161 ((config.width <= 0) || (config.height <= 0) || config.ntries <= 0))
162 status = GIMP_PDB_CALLING_ERROR;
163
164 break;
165
166 case GIMP_RUN_WITH_LAST_VALS:
167 /* Possibly retrieve data */
168 gimp_get_data (PLUG_IN_PROC, &config);
169 break;
170
171 default:
172 break;
173 }
174
175 if (status == GIMP_PDB_SUCCESS)
176 {
177 if (gimp_drawable_is_rgb (drawable_id))
178 {
179 gimp_progress_init (_("Deriving smooth palette"));
180
181 gegl_init (NULL, NULL);
182 values[1].data.d_image = smooth_palette (drawable_id,
183 &values[2].data.d_layer);
184 gegl_exit ();
185
186 if (run_mode == GIMP_RUN_INTERACTIVE)
187 gimp_set_data (PLUG_IN_PROC, &config, sizeof (config));
188
189 if (config.show_image)
190 gimp_display_new (values[1].data.d_image);
191 }
192 else
193 {
194 status = GIMP_PDB_EXECUTION_ERROR;
195 }
196 }
197
198 values[0].data.d_status = status;
199 }
200
201 static gfloat
pix_diff(gfloat * pal,guint bpp,gint i,gint j)202 pix_diff (gfloat *pal,
203 guint bpp,
204 gint i,
205 gint j)
206 {
207 gfloat r = 0.f;
208 guint k;
209
210 for (k = 0; k < bpp; k++)
211 {
212 gfloat p1 = pal[j * bpp + k];
213 gfloat p2 = pal[i * bpp + k];
214 r += (p1 - p2) * (p1 - p2);
215 }
216
217 return r;
218 }
219
220 static void
pix_swap(gfloat * pal,guint bpp,gint i,gint j)221 pix_swap (gfloat *pal,
222 guint bpp,
223 gint i,
224 gint j)
225 {
226 guint k;
227
228 for (k = 0; k < bpp; k++)
229 {
230 gfloat t = pal[j * bpp + k];
231 pal[j * bpp + k] = pal[i * bpp + k];
232 pal[i * bpp + k] = t;
233 }
234 }
235
236 static gint32
smooth_palette(gint32 drawable_id,gint32 * layer_id)237 smooth_palette (gint32 drawable_id,
238 gint32 *layer_id)
239 {
240 gint32 new_image_id;
241 gint psize, i, j;
242 guint bpp;
243 gint sel_x1, sel_y1;
244 gint width, height;
245 GeglBuffer *buffer;
246 GeglSampler *sampler;
247 gfloat *pal;
248 GRand *gr;
249
250 const Babl *format = babl_format ("RGB float");
251
252 new_image_id = gimp_image_new_with_precision (config.width,
253 config.height,
254 GIMP_RGB,
255 GIMP_PRECISION_FLOAT_LINEAR);
256
257 gimp_image_undo_disable (new_image_id);
258
259 *layer_id = gimp_layer_new (new_image_id, _("Background"),
260 config.width, config.height,
261 gimp_drawable_type (drawable_id),
262 100,
263 gimp_image_get_default_new_layer_mode (new_image_id));
264
265 gimp_image_insert_layer (new_image_id, *layer_id, -1, 0);
266
267 if (! gimp_drawable_mask_intersect (drawable_id,
268 &sel_x1, &sel_y1, &width, &height))
269 return new_image_id;
270
271 gr = g_rand_new ();
272
273 psize = config.width;
274
275 buffer = gimp_drawable_get_buffer (drawable_id);
276
277 sampler = gegl_buffer_sampler_new (buffer, format, GEGL_SAMPLER_NEAREST);
278
279 bpp = babl_format_get_n_components (gegl_buffer_get_format (buffer));
280
281 pal = g_new (gfloat, psize * bpp);
282
283 /* get initial palette */
284
285 for (i = 0; i < psize; i++)
286 {
287 gint x = sel_x1 + g_rand_int_range (gr, 0, width);
288 gint y = sel_y1 + g_rand_int_range (gr, 0, height);
289
290 gegl_sampler_get (sampler,
291 (gdouble) x, (gdouble) y, NULL, pal + i * bpp,
292 GEGL_ABYSS_NONE);
293 }
294
295 g_object_unref (sampler);
296 g_object_unref (buffer);
297
298 /* reorder */
299 if (1)
300 {
301 gfloat *pal_best;
302 gfloat *original;
303 gdouble len_best = 0;
304 gint try;
305
306 pal_best = g_memdup (pal, bpp * psize);
307 original = g_memdup (pal, bpp * psize);
308
309 for (try = 0; try < config.ntries; try++)
310 {
311 gdouble len;
312
313 if (!(try%5))
314 gimp_progress_update (try / (double) config.ntries);
315 memcpy (pal, original, bpp * psize);
316
317 /* scramble */
318 for (i = 1; i < psize; i++)
319 pix_swap (pal, bpp, i, g_rand_int_range (gr, 0, psize));
320
321 /* measure */
322 len = 0.0;
323 for (i = 1; i < psize; i++)
324 len += pix_diff (pal, bpp, i, i-1);
325
326 /* improve */
327 for (i = 0; i < config.try_size; i++)
328 {
329 gint i0 = 1 + g_rand_int_range (gr, 0, psize-2);
330 gint i1 = 1 + g_rand_int_range (gr, 0, psize-2);
331 gfloat as_is, swapd;
332
333 if (1 == (i0 - i1))
334 {
335 as_is = (pix_diff (pal, bpp, i1 - 1, i1) +
336 pix_diff (pal, bpp, i0, i0 + 1));
337 swapd = (pix_diff (pal, bpp, i1 - 1, i0) +
338 pix_diff (pal, bpp, i1, i0 + 1));
339 }
340 else if (1 == (i1 - i0))
341 {
342 as_is = (pix_diff (pal, bpp, i0 - 1, i0) +
343 pix_diff (pal, bpp, i1, i1 + 1));
344 swapd = (pix_diff (pal, bpp, i0 - 1, i1) +
345 pix_diff (pal, bpp, i0, i1 + 1));
346 }
347 else
348 {
349 as_is = (pix_diff (pal, bpp, i0, i0 + 1) +
350 pix_diff (pal, bpp, i0, i0 - 1) +
351 pix_diff (pal, bpp, i1, i1 + 1) +
352 pix_diff (pal, bpp, i1, i1 - 1));
353 swapd = (pix_diff (pal, bpp, i1, i0 + 1) +
354 pix_diff (pal, bpp, i1, i0 - 1) +
355 pix_diff (pal, bpp, i0, i1 + 1) +
356 pix_diff (pal, bpp, i0, i1 - 1));
357 }
358 if (swapd < as_is)
359 {
360 pix_swap (pal, bpp, i0, i1);
361 len += swapd - as_is;
362 }
363 }
364 /* best? */
365 if (0 == try || len < len_best)
366 {
367 memcpy (pal_best, pal, bpp * psize);
368 len_best = len;
369 }
370 }
371
372 gimp_progress_update (1.0);
373 memcpy (pal, pal_best, bpp * psize);
374 g_free (pal_best);
375 g_free (original);
376
377 /* clean */
378 for (i = 1; i < 4 * psize; i++)
379 {
380 gfloat as_is, swapd;
381 gint i0 = 1 + g_rand_int_range (gr, 0, psize - 2);
382 gint i1 = i0 + 1;
383
384 as_is = (pix_diff (pal, bpp, i0 - 1, i0) +
385 pix_diff (pal, bpp, i1, i1 + 1));
386 swapd = (pix_diff (pal, bpp, i0 - 1, i1) +
387 pix_diff (pal, bpp, i0, i1 + 1));
388
389 if (swapd < as_is)
390 {
391 pix_swap (pal, bpp, i0, i1);
392 len_best += swapd - as_is;
393 }
394 }
395 }
396
397 /* store smooth palette */
398
399 buffer = gimp_drawable_get_buffer (*layer_id);
400
401 for (j = 0; j < config.height; j++)
402 {
403 GeglRectangle row = {0, j, config.width, 1};
404 gegl_buffer_set (buffer, &row, 0, format, pal, GEGL_AUTO_ROWSTRIDE);
405 }
406
407 gegl_buffer_flush (buffer);
408
409 gimp_drawable_update (*layer_id, 0, 0,
410 config.width, config.height);
411 gimp_image_undo_enable (new_image_id);
412
413 g_object_unref (buffer);
414 g_free (pal);
415 g_rand_free (gr);
416
417 return new_image_id;
418 }
419
420 static gboolean
dialog(gint32 drawable_id)421 dialog (gint32 drawable_id)
422 {
423 GtkWidget *dlg;
424 GtkWidget *spinbutton;
425 GtkAdjustment *adj;
426 GtkWidget *sizeentry;
427 guint32 image_id;
428 GimpUnit unit;
429 gdouble xres, yres;
430 gboolean run;
431
432 gimp_ui_init (PLUG_IN_BINARY, FALSE);
433
434 dlg = gimp_dialog_new (_("Smooth Palette"), PLUG_IN_ROLE,
435 NULL, 0,
436 gimp_standard_help_func, PLUG_IN_PROC,
437
438 _("_Cancel"), GTK_RESPONSE_CANCEL,
439 _("_OK"), GTK_RESPONSE_OK,
440
441 NULL);
442
443 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg),
444 GTK_RESPONSE_OK,
445 GTK_RESPONSE_CANCEL,
446 -1);
447
448 gimp_window_set_transient (GTK_WINDOW (dlg));
449
450 image_id = gimp_item_get_image (drawable_id);
451 unit = gimp_image_get_unit (image_id);
452 gimp_image_get_resolution (image_id, &xres, &yres);
453
454 sizeentry = gimp_coordinates_new (unit, "%a", TRUE, FALSE, 6,
455 GIMP_SIZE_ENTRY_UPDATE_SIZE,
456 FALSE, FALSE,
457
458 _("_Width:"),
459 config.width, xres,
460 2, GIMP_MAX_IMAGE_SIZE,
461 2, GIMP_MAX_IMAGE_SIZE,
462
463 _("_Height:"),
464 config.height, yres,
465 1, GIMP_MAX_IMAGE_SIZE,
466 1, GIMP_MAX_IMAGE_SIZE);
467 gtk_container_set_border_width (GTK_CONTAINER (sizeentry), 12);
468 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
469 sizeentry, FALSE, FALSE, 0);
470 gtk_widget_show (sizeentry);
471
472 adj = GTK_ADJUSTMENT (gtk_adjustment_new (config.ntries,
473 1, 1024, 1, 10, 0));
474 spinbutton = gimp_spin_button_new (adj, 1, 0);
475 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
476
477 gimp_table_attach_aligned (GTK_TABLE (sizeentry), 0, 2,
478 _("_Search depth:"), 0.0, 0.5,
479 spinbutton, 1, FALSE);
480 g_signal_connect (adj, "value-changed",
481 G_CALLBACK (gimp_int_adjustment_update),
482 &config.ntries);
483
484 gtk_widget_show (dlg);
485
486 run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK);
487
488 if (run)
489 {
490 config.width = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry),
491 0);
492 config.height = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry),
493 1);
494 }
495
496 gtk_widget_destroy (dlg);
497
498 return run;
499 }
500