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