1 /* borderaverage 0.01 - image processing plug-in for GIMP.
2 *
3 * Copyright (C) 1998 Philipp Klaus (webmaster@access.ch)
4 *
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include <libgimp/gimp.h>
23 #include <libgimp/gimpui.h>
24
25 #include "libgimp/stdplugins-intl.h"
26
27
28 #define PLUG_IN_PROC "plug-in-borderaverage"
29 #define PLUG_IN_BINARY "border-average"
30 #define PLUG_IN_ROLE "gimp-border-average"
31
32
33 /* Declare local functions.
34 */
35 static void query (void);
36 static void run (const gchar *name,
37 gint nparams,
38 const GimpParam *param,
39 gint *nreturn_vals,
40 GimpParam **return_vals);
41
42
43 static void borderaverage (GeglBuffer *buffer,
44 gint32 drawable_id,
45 GimpRGB *result);
46
47 static gboolean borderaverage_dialog (gint32 image_ID,
48 gint32 drawable_id);
49
50 static void add_new_color (const guchar *buffer,
51 gint *cube,
52 gint bucket_expo);
53
54 static void thickness_callback (GtkWidget *widget,
55 gpointer data);
56
57 const GimpPlugInInfo PLUG_IN_INFO =
58 {
59 NULL, /* init */
60 NULL, /* quit */
61 query, /* query */
62 run, /* run */
63 };
64
65 static gint borderaverage_thickness = 3;
66 static gint borderaverage_bucket_exponent = 4;
67
68 struct borderaverage_data
69 {
70 gint thickness;
71 gint bucket_exponent;
72 }
73
74 static borderaverage_data =
75 {
76 3,
77 4
78 };
79
MAIN()80 MAIN ()
81
82 static void
83 query (void)
84 {
85 static const GimpParamDef args[] =
86 {
87 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
88 { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
89 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
90 { GIMP_PDB_INT32, "thickness", "Border size to take in count" },
91 { GIMP_PDB_INT32, "bucket-exponent", "Bits for bucket size (default=4: 16 Levels)" },
92 };
93 static const GimpParamDef return_vals[] =
94 {
95 { GIMP_PDB_COLOR, "borderaverage", "The average color of the specified border." },
96 };
97
98 gimp_install_procedure (PLUG_IN_PROC,
99 N_("Set foreground to the average color of the image border"),
100 "",
101 "Philipp Klaus",
102 "Internet Access AG",
103 "1998",
104 N_("_Border Average..."),
105 "RGB*",
106 GIMP_PLUGIN,
107 G_N_ELEMENTS (args),
108 G_N_ELEMENTS (return_vals),
109 args, return_vals);
110
111 gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Colors/Info");
112 }
113
114 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)115 run (const gchar *name,
116 gint nparams,
117 const GimpParam *param,
118 gint *nreturn_vals,
119 GimpParam **return_vals)
120 {
121 static GimpParam values[3];
122 gint32 image_ID;
123 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
124 GimpRGB result_color = { 0.0, };
125 GimpRunMode run_mode;
126 gint32 drawable_id;
127 GeglBuffer *buffer;
128
129 INIT_I18N ();
130 gegl_init (NULL, NULL);
131
132 run_mode = param[0].data.d_int32;
133 image_ID = param[1].data.d_int32;
134 drawable_id = param[2].data.d_drawable;
135
136 buffer = gimp_drawable_get_buffer (drawable_id);
137
138 switch (run_mode)
139 {
140 case GIMP_RUN_INTERACTIVE:
141 gimp_get_data (PLUG_IN_PROC, &borderaverage_data);
142 borderaverage_thickness = borderaverage_data.thickness;
143 borderaverage_bucket_exponent = borderaverage_data.bucket_exponent;
144 if (! borderaverage_dialog (image_ID, drawable_id))
145 status = GIMP_PDB_EXECUTION_ERROR;
146 break;
147
148 case GIMP_RUN_NONINTERACTIVE:
149 if (nparams != 5)
150 status = GIMP_PDB_CALLING_ERROR;
151 if (status == GIMP_PDB_SUCCESS)
152 {
153 borderaverage_thickness = param[3].data.d_int32;
154 borderaverage_bucket_exponent = param[4].data.d_int32;
155 }
156 break;
157
158 case GIMP_RUN_WITH_LAST_VALS:
159 gimp_get_data (PLUG_IN_PROC, &borderaverage_data);
160 borderaverage_thickness = borderaverage_data.thickness;
161 borderaverage_bucket_exponent = borderaverage_data.bucket_exponent;
162 break;
163
164 default:
165 break;
166 }
167
168 if (status == GIMP_PDB_SUCCESS)
169 {
170 /* Make sure that the drawable is RGB color */
171 if (gimp_drawable_is_rgb (drawable_id))
172 {
173 gimp_progress_init ( _("Border Average"));
174 borderaverage (buffer, drawable_id, &result_color);
175
176 if (run_mode != GIMP_RUN_NONINTERACTIVE)
177 {
178 gimp_context_set_foreground (&result_color);
179 }
180 if (run_mode == GIMP_RUN_INTERACTIVE)
181 {
182 borderaverage_data.thickness = borderaverage_thickness;
183 borderaverage_data.bucket_exponent = borderaverage_bucket_exponent;
184 gimp_set_data (PLUG_IN_PROC,
185 &borderaverage_data, sizeof (borderaverage_data));
186 }
187 }
188 else
189 {
190 status = GIMP_PDB_EXECUTION_ERROR;
191 }
192 }
193 *nreturn_vals = 3;
194 *return_vals = values;
195
196 values[0].type = GIMP_PDB_STATUS;
197 values[0].data.d_status = status;
198
199 values[1].type = GIMP_PDB_COLOR;
200 values[1].data.d_color = result_color;
201
202 g_object_unref (buffer);
203 }
204
205
206 static void
borderaverage(GeglBuffer * buffer,gint32 drawable_id,GimpRGB * result)207 borderaverage (GeglBuffer *buffer,
208 gint32 drawable_id,
209 GimpRGB *result)
210 {
211 gint x, y, width, height;
212 gint max;
213 guchar r, g, b;
214 gint bucket_num, bucket_expo, bucket_rexpo;
215 gint *cube;
216 gint i, j, k;
217 GeglRectangle border[4];
218
219 if (! gimp_drawable_mask_intersect (drawable_id, &x, &y, &width, &height))
220 {
221 gimp_rgba_set_uchar (result, 0, 0, 0, 255);
222 return;
223 }
224
225 /* allocate and clear the cube before */
226 bucket_expo = borderaverage_bucket_exponent;
227 bucket_rexpo = 8 - bucket_expo;
228 cube = g_new (gint, 1 << (bucket_rexpo * 3));
229 bucket_num = 1 << bucket_rexpo;
230
231 for (i = 0; i < bucket_num; i++)
232 {
233 for (j = 0; j < bucket_num; j++)
234 {
235 for (k = 0; k < bucket_num; k++)
236 {
237 cube[(i << (bucket_rexpo << 1)) + (j << bucket_rexpo) + k] = 0;
238 }
239 }
240 }
241
242 /* Top */
243 border[0].x = x;
244 border[0].y = y;
245 border[0].width = width;
246 border[0].height = borderaverage_thickness;
247
248 /* Bottom */
249 border[1].x = x;
250 border[1].y = y + height - borderaverage_thickness;
251 border[1].width = width;
252 border[1].height = borderaverage_thickness;
253
254 /* Left */
255 border[2].x = x;
256 border[2].y = y + borderaverage_thickness;
257 border[2].width = borderaverage_thickness;
258 border[2].height = height - 2 * borderaverage_thickness;
259
260 /* Right */
261 border[3].x = x + width - borderaverage_thickness;
262 border[3].y = y + borderaverage_thickness;
263 border[3].width = borderaverage_thickness;
264 border[3].height = height - 2 * borderaverage_thickness;
265
266 /* Fill the cube */
267 for (i = 0; i < 4; i++)
268 {
269 if (border[i].width > 0 && border[i].height > 0)
270 {
271 GeglBufferIterator *gi;
272
273 gi = gegl_buffer_iterator_new (buffer, &border[i], 0, babl_format ("R'G'B' u8"),
274 GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
275
276 while (gegl_buffer_iterator_next (gi))
277 {
278 guint k;
279 guchar *data;
280
281 data = (guchar*) gi->items[0].data;
282
283 for (k = 0; k < gi->length; k++)
284 {
285 add_new_color (data + k * 3,
286 cube,
287 bucket_expo);
288 }
289 }
290 }
291 }
292
293 max = 0; r = 0; g = 0; b = 0;
294
295 /* get max of cube */
296 for (i = 0; i < bucket_num; i++)
297 {
298 for (j = 0; j < bucket_num; j++)
299 {
300 for (k = 0; k < bucket_num; k++)
301 {
302 if (cube[(i << (bucket_rexpo << 1)) +
303 (j << bucket_rexpo) + k] > max)
304 {
305 max = cube[(i << (bucket_rexpo << 1)) +
306 (j << bucket_rexpo) + k];
307 r = (i<<bucket_expo) + (1<<(bucket_expo - 1));
308 g = (j<<bucket_expo) + (1<<(bucket_expo - 1));
309 b = (k<<bucket_expo) + (1<<(bucket_expo - 1));
310 }
311 }
312 }
313 }
314
315 /* return the color */
316 gimp_rgba_set_uchar (result, r, g, b, 255);
317
318 g_free (cube);
319 }
320
321 static void
add_new_color(const guchar * buffer,gint * cube,gint bucket_expo)322 add_new_color (const guchar *buffer,
323 gint *cube,
324 gint bucket_expo)
325 {
326 guchar r, g, b;
327 gint bucket_rexpo;
328
329 bucket_rexpo = 8 - bucket_expo;
330 r = buffer[0] >> bucket_expo;
331 g = buffer[1] >> bucket_expo;
332 b = buffer[2] >> bucket_expo;
333 cube[(r << (bucket_rexpo << 1)) + (g << bucket_rexpo) + b]++;
334 }
335
336 static gboolean
borderaverage_dialog(gint32 image_ID,gint32 drawable_id)337 borderaverage_dialog (gint32 image_ID,
338 gint32 drawable_id)
339 {
340 GtkWidget *dialog;
341 GtkWidget *frame;
342 GtkWidget *main_vbox;
343 GtkWidget *hbox;
344 GtkWidget *label;
345 GtkWidget *size_entry;
346 GimpUnit unit;
347 GtkWidget *combo;
348 GtkSizeGroup *group;
349 gboolean run;
350 gdouble xres, yres;
351 GeglBuffer *buffer = NULL;
352
353 const gchar *labels[] =
354 { "1", "2", "4", "8", "16", "32", "64", "128", "256" };
355
356 gimp_ui_init (PLUG_IN_BINARY, FALSE);
357
358 dialog = gimp_dialog_new (_("Border Average"), PLUG_IN_ROLE,
359 NULL, 0,
360 gimp_standard_help_func, PLUG_IN_PROC,
361
362 _("_Cancel"), GTK_RESPONSE_CANCEL,
363 _("_OK"), GTK_RESPONSE_OK,
364
365 NULL);
366
367 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
368 GTK_RESPONSE_OK,
369 GTK_RESPONSE_CANCEL,
370 -1);
371
372 gimp_window_set_transient (GTK_WINDOW (dialog));
373
374 main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
375 gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
376 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
377 main_vbox, TRUE, TRUE, 0);
378 gtk_widget_show (main_vbox);
379
380 frame = gimp_frame_new (_("Border Size"));
381 gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
382 gtk_widget_show (frame);
383
384 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
385 gtk_container_add (GTK_CONTAINER (frame), hbox);
386 gtk_widget_show (hbox);
387
388 label = gtk_label_new_with_mnemonic (_("_Thickness:"));
389 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
390 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
391 gtk_widget_show (label);
392
393 group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
394 gtk_size_group_add_widget (group, label);
395 g_object_unref (group);
396
397 /* Get the image resolution and unit */
398 gimp_image_get_resolution (image_ID, &xres, &yres);
399 unit = gimp_image_get_unit (image_ID);
400
401 size_entry = gimp_size_entry_new (1, unit, "%a", TRUE, TRUE, FALSE, 4,
402 GIMP_SIZE_ENTRY_UPDATE_SIZE);
403 gtk_box_pack_start (GTK_BOX (hbox), size_entry, FALSE, FALSE, 0);
404
405 gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (size_entry), GIMP_UNIT_PIXEL);
406 gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_entry), 0, xres, TRUE);
407
408 /* set the size (in pixels) that will be treated as 0% and 100% */
409 buffer = gimp_drawable_get_buffer (drawable_id);
410 if (buffer)
411 gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_entry), 0, 0.0,
412 MIN (gegl_buffer_get_width (buffer),
413 gegl_buffer_get_height (buffer)));
414
415 gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (size_entry), 0,
416 1.0, 256.0);
417 gtk_table_set_col_spacing (GTK_TABLE (size_entry), 0, 4);
418 gtk_table_set_col_spacing (GTK_TABLE (size_entry), 2, 12);
419 gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (size_entry), 0,
420 (gdouble) borderaverage_thickness);
421 g_signal_connect (size_entry, "value-changed",
422 G_CALLBACK (thickness_callback),
423 NULL);
424 gtk_widget_show (size_entry);
425
426 frame = gimp_frame_new (_("Number of Colors"));
427 gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
428 gtk_widget_show (frame);
429
430 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
431 gtk_container_add (GTK_CONTAINER (frame), hbox);
432 gtk_widget_show (hbox);
433
434 label = gtk_label_new_with_mnemonic (_("_Bucket size:"));
435 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
436 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
437 gtk_widget_show (label);
438
439 gtk_size_group_add_widget (group, label);
440
441 combo = gimp_int_combo_box_new_array (G_N_ELEMENTS (labels), labels);
442 gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo),
443 borderaverage_bucket_exponent);
444
445 g_signal_connect (combo, "changed",
446 G_CALLBACK (gimp_int_combo_box_get_active),
447 &borderaverage_bucket_exponent);
448
449 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
450 gtk_widget_show (combo);
451 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
452
453 gtk_widget_show (dialog);
454
455 run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
456
457 gtk_widget_destroy (dialog);
458
459 return run;
460 }
461
462 static void
thickness_callback(GtkWidget * widget,gpointer data)463 thickness_callback (GtkWidget *widget,
464 gpointer data)
465 {
466 borderaverage_thickness =
467 gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0);
468 }
469