1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * Despeckle (adaptive median) filter
5  *
6  * Copyright 1997-1998 Michael Sweet (mike@easysw.com)
7  * optimized in 2010 by Przemyslaw Zych (kermidt.zed@gmail.com)
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
21  */
22 
23 #include "config.h"
24 
25 #include <stdlib.h>
26 
27 #include <libgimp/gimp.h>
28 #include <libgimp/gimpui.h>
29 
30 #include "libgimp/stdplugins-intl.h"
31 
32 
33 /*
34  * Constants...
35  */
36 
37 #define PLUG_IN_PROC     "plug-in-despeckle"
38 #define PLUG_IN_BINARY   "despeckle"
39 #define PLUG_IN_ROLE     "gimp-despeckle"
40 #define PLUG_IN_VERSION  "May 2010"
41 #define SCALE_WIDTH      100
42 #define ENTRY_WIDTH        3
43 #define MAX_RADIUS        30
44 
45 #define FILTER_ADAPTIVE  0x01
46 #define FILTER_RECURSIVE 0x02
47 
48 #define despeckle_radius (despeckle_vals[0])    /* diameter of filter */
49 #define filter_type      (despeckle_vals[1])    /* filter type */
50 #define black_level      (despeckle_vals[2])    /* Black level */
51 #define white_level      (despeckle_vals[3])    /* White level */
52 
53 /* List that stores pixels falling in to the same luma bucket */
54 #define MAX_LIST_ELEMS SQR(2 * MAX_RADIUS + 1)
55 
56 typedef struct
57 {
58   const guchar *elems[MAX_LIST_ELEMS];
59   gint          start;
60   gint          count;
61 } PixelsList;
62 
63 typedef struct
64 {
65   gint       elems[256]; /* Number of pixels that fall into each luma bucket */
66   PixelsList origs[256]; /* Original pixels */
67   gint       xmin;
68   gint       ymin;
69   gint       xmax;
70   gint       ymax; /* Source rect */
71 } DespeckleHistogram;
72 
73 /* Number of pixels in actual histogram falling into each category */
74 static gint                hist0;    /* Less than min threshold */
75 static gint                hist255;  /* More than max threshold */
76 static gint                histrest; /* From min to max        */
77 
78 static DespeckleHistogram  histogram;
79 
80 
81 /*
82  * Local functions...
83  */
84 
85 static void      query (void);
86 static void      run   (const gchar      *name,
87                         gint              nparams,
88                         const GimpParam  *param,
89                         gint             *nreturn_vals,
90                         GimpParam       **return_vals);
91 
92 static void      despeckle                 (void);
93 static void      despeckle_median          (guchar        *src,
94                                             guchar        *dst,
95                                             gint           width,
96                                             gint           height,
97                                             gint           bpp,
98                                             gint           radius,
99                                             gboolean       preview);
100 
101 static gboolean  despeckle_dialog          (void);
102 
103 static void      dialog_adaptive_callback  (GtkWidget     *widget,
104                                             gpointer       data);
105 static void      dialog_recursive_callback (GtkWidget     *widget,
106                                             gpointer       data);
107 
108 static void      preview_update            (GtkWidget     *preview);
109 
110 /*
111  * Globals...
112  */
113 
114 const GimpPlugInInfo PLUG_IN_INFO =
115 {
116   NULL,  /* init  */
117   NULL,  /* quit  */
118   query, /* query */
119   run    /* run   */
120 };
121 
122 static GtkWidget *preview;          /* Preview widget   */
123 static gint32     drawable_ID = -1; /* Current drawable */
124 
125 
126 static gint despeckle_vals[4] =
127 {
128   3,                  /* Default value for the diameter */
129   FILTER_ADAPTIVE,    /* Default value for the filter type */
130   7,                  /* Default value for the black level */
131   248                 /* Default value for the white level */
132 };
133 
134 
MAIN()135 MAIN ()
136 
137 
138 static void
139 query (void)
140 {
141   static const GimpParamDef   args[] =
142   {
143     { GIMP_PDB_INT32,    "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
144     { GIMP_PDB_IMAGE,    "image",    "Input image" },
145     { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
146     { GIMP_PDB_INT32,    "radius",   "Filter box radius (default = 3)" },
147     { GIMP_PDB_INT32,    "type",     "Filter type { MEDIAN (0), ADAPTIVE (1), RECURSIVE-MEDIAN (2), RECURSIVE-ADAPTIVE (3) }" },
148     { GIMP_PDB_INT32,    "black",    "Black level (-1 to 255)" },
149     { GIMP_PDB_INT32,    "white",    "White level (0 to 256)" }
150   };
151 
152   gimp_install_procedure (PLUG_IN_PROC,
153                           N_("Remove speckle noise from the image"),
154                           "This plug-in selectively performs a median or "
155                           "adaptive box filter on an image.",
156                           "Michael Sweet <mike@easysw.com>",
157                           "Copyright 1997-1998 by Michael Sweet",
158                           PLUG_IN_VERSION,
159                           N_("Des_peckle..."),
160                           "RGB*, GRAY*",
161                           GIMP_PLUGIN,
162                           G_N_ELEMENTS (args), 0,
163                           args, NULL);
164 
165   gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Enhance");
166 }
167 
168 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)169 run (const gchar      *name,
170      gint              nparams,
171      const GimpParam  *param,
172      gint             *nreturn_vals,
173      GimpParam       **return_vals)
174 {
175   GimpRunMode        run_mode;
176   GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
177   static GimpParam   values[1];
178 
179   INIT_I18N ();
180   gegl_init (NULL, NULL);
181 
182   values[0].type          = GIMP_PDB_STATUS;
183   values[0].data.d_status = status;
184 
185   *nreturn_vals = 1;
186   *return_vals  = values;
187 
188   run_mode    = param[0].data.d_int32;
189   drawable_ID = param[2].data.d_drawable;
190 
191   switch (run_mode)
192     {
193     case GIMP_RUN_INTERACTIVE :
194       gimp_get_data (PLUG_IN_PROC, &despeckle_radius);
195 
196       if (gimp_drawable_is_rgb (drawable_ID) ||
197           gimp_drawable_is_gray (drawable_ID))
198        {
199           if (! despeckle_dialog ())
200           return;
201        }
202       break;
203 
204     case GIMP_RUN_NONINTERACTIVE:
205       if (nparams < 4 || nparams > 9)
206         {
207           status = GIMP_PDB_CALLING_ERROR;
208         }
209       else if (nparams == 4)
210         {
211           despeckle_radius = param[3].data.d_int32;
212           filter_type      = FILTER_ADAPTIVE;
213           black_level      = 7;
214           white_level      = 248;
215         }
216       else if (nparams == 5)
217         {
218           despeckle_radius = param[3].data.d_int32;
219           filter_type      = param[4].data.d_int32;
220           black_level      = 7;
221           white_level      = 248;
222         }
223       else if (nparams == 6)
224         {
225           despeckle_radius = param[3].data.d_int32;
226           filter_type      = param[4].data.d_int32;
227           black_level      = param[5].data.d_int32;
228           white_level      = 248;
229         }
230       else
231         {
232           despeckle_radius = param[3].data.d_int32;
233           filter_type      = param[4].data.d_int32;
234           black_level      = param[5].data.d_int32;
235           white_level      = param[6].data.d_int32;
236         }
237       break;
238 
239     case GIMP_RUN_WITH_LAST_VALS:
240       gimp_get_data (PLUG_IN_PROC, despeckle_vals);
241         break;
242 
243     default:
244       status = GIMP_PDB_CALLING_ERROR;
245       break;
246     }
247 
248   if (status == GIMP_PDB_SUCCESS)
249     {
250       if (gimp_drawable_is_rgb (drawable_ID) ||
251           gimp_drawable_is_gray (drawable_ID))
252         {
253           despeckle ();
254 
255           if (run_mode != GIMP_RUN_NONINTERACTIVE)
256             gimp_displays_flush ();
257 
258           if (run_mode == GIMP_RUN_INTERACTIVE)
259             gimp_set_data (PLUG_IN_PROC,
260                            despeckle_vals, sizeof (despeckle_vals));
261         }
262       else
263         {
264           status = GIMP_PDB_EXECUTION_ERROR;
265         }
266     }
267 
268   values[0].data.d_status = status;
269 }
270 
271 static inline guchar
pixel_luminance(const guchar * p,gint bpp)272 pixel_luminance (const guchar *p,
273                  gint          bpp)
274 {
275   switch (bpp)
276     {
277     case 1:
278     case 2:
279       return p[0];
280 
281     case 3:
282     case 4:
283       return GIMP_RGB_LUMINANCE (p[0], p[1], p[2]);
284 
285     default:
286       return 0; /* should not be reached */
287     }
288 }
289 
290 static inline void
pixel_copy(guchar * dest,const guchar * src,gint bpp)291 pixel_copy (guchar       *dest,
292             const guchar *src,
293             gint          bpp)
294 {
295   switch (bpp)
296     {
297     case 4:
298       *dest++ = *src++;
299     case 3:
300       *dest++ = *src++;
301     case 2:
302       *dest++ = *src++;
303     case 1:
304       *dest++ = *src++;
305     }
306 }
307 
308 /*
309  * 'despeckle()' - Despeckle an image using a median filter.
310  *
311  * A median filter basically collects pixel values in a region around the
312  * target pixel, sorts them, and uses the median value. This code uses a
313  * circular row buffer to improve performance.
314  *
315  * The adaptive filter is based on the median filter but analyzes the histogram
316  * of the region around the target pixel and adjusts the despeckle diameter
317  * accordingly.
318  */
319 
320 static void
despeckle(void)321 despeckle (void)
322 {
323   GeglBuffer *src_buffer;
324   GeglBuffer *dest_buffer;
325   const Babl *format;
326   guchar     *src;
327   guchar     *dst;
328   gint        img_bpp;
329   gint        x, y;
330   gint        width, height;
331 
332   if (! gimp_drawable_mask_intersect (drawable_ID,
333                                       &x, &y, &width, &height))
334     return;
335 
336   if (gimp_drawable_is_rgb (drawable_ID))
337     {
338       if (gimp_drawable_has_alpha (drawable_ID))
339         format = babl_format ("R'G'B'A u8");
340       else
341         format = babl_format ("R'G'B' u8");
342     }
343   else
344     {
345       if (gimp_drawable_has_alpha (drawable_ID))
346         format = babl_format ("Y'A u8");
347       else
348         format = babl_format ("Y' u8");
349     }
350 
351   img_bpp = babl_format_get_bytes_per_pixel (format);
352 
353   src_buffer  = gimp_drawable_get_buffer (drawable_ID);
354   dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID);
355 
356   src = g_new (guchar, width * height * img_bpp);
357   dst = g_new (guchar, width * height * img_bpp);
358 
359   gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x, y, width, height), 1.0,
360                    format, src,
361                    GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
362 
363   despeckle_median (src, dst, width, height, img_bpp, despeckle_radius, FALSE);
364 
365   gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x, y, width, height), 0,
366                    format, dst,
367                    GEGL_AUTO_ROWSTRIDE);
368 
369   g_object_unref (src_buffer);
370   g_object_unref (dest_buffer);
371 
372   gimp_drawable_merge_shadow (drawable_ID, TRUE);
373   gimp_drawable_update (drawable_ID, x, y, width, height);
374 
375   g_free (dst);
376   g_free (src);
377 }
378 
379 static gboolean
despeckle_dialog(void)380 despeckle_dialog (void)
381 {
382   GtkWidget *dialog;
383   GtkWidget *main_vbox;
384   GtkWidget *vbox;
385   GtkWidget *table;
386   GtkWidget *frame;
387   GtkWidget *button;
388   GtkObject *adj;
389   gboolean   run;
390 
391   gimp_ui_init (PLUG_IN_BINARY, TRUE);
392 
393   dialog = gimp_dialog_new (_("Despeckle"), PLUG_IN_ROLE,
394                             NULL, 0,
395                             gimp_standard_help_func, PLUG_IN_PROC,
396 
397                             _("_Cancel"), GTK_RESPONSE_CANCEL,
398                             _("_OK"),     GTK_RESPONSE_OK,
399 
400                             NULL);
401 
402   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
403                                            GTK_RESPONSE_OK,
404                                            GTK_RESPONSE_CANCEL,
405                                            -1);
406 
407   gimp_window_set_transient (GTK_WINDOW (dialog));
408 
409   main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
410   gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
411   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
412                       main_vbox, TRUE, TRUE, 0);
413   gtk_widget_show (main_vbox);
414 
415   preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID);
416   gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
417   gtk_widget_show (preview);
418 
419   g_signal_connect (preview, "invalidated",
420                     G_CALLBACK (preview_update),
421                     NULL);
422 
423   frame = gimp_frame_new (_("Median"));
424   gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
425   gtk_widget_show (frame);
426 
427   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
428   gtk_container_add (GTK_CONTAINER (frame), vbox);
429   gtk_widget_show (vbox);
430 
431   button = gtk_check_button_new_with_mnemonic (_("_Adaptive"));
432   gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
433   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
434                                 filter_type & FILTER_ADAPTIVE);
435   gtk_widget_show (button);
436 
437   g_signal_connect (button, "toggled",
438                     G_CALLBACK (dialog_adaptive_callback),
439                     NULL);
440 
441   button = gtk_check_button_new_with_mnemonic (_("R_ecursive"));
442   gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
443   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
444                                 filter_type & FILTER_RECURSIVE);
445   gtk_widget_show (button);
446 
447   g_signal_connect (button, "toggled",
448                     G_CALLBACK (dialog_recursive_callback),
449                     NULL);
450 
451   table = gtk_table_new (4, 3, FALSE);
452   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
453   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
454   gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
455   gtk_widget_show (table);
456 
457   /*
458    * Box size (diameter) control...
459    */
460 
461   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
462                               _("_Radius:"), SCALE_WIDTH, ENTRY_WIDTH,
463                               despeckle_radius, 1, MAX_RADIUS, 1, 5, 0,
464                               TRUE, 0, 0,
465                               NULL, NULL);
466   g_signal_connect (adj, "value-changed",
467                     G_CALLBACK (gimp_int_adjustment_update),
468                     &despeckle_radius);
469   g_signal_connect_swapped (adj, "value-changed",
470                             G_CALLBACK (gimp_preview_invalidate),
471                             preview);
472 
473   /*
474    * Black level control...
475    */
476 
477   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
478                               _("_Black level:"), SCALE_WIDTH, ENTRY_WIDTH,
479                               black_level, -1, 255, 1, 8, 0,
480                               TRUE, 0, 0,
481                               NULL, NULL);
482   g_signal_connect (adj, "value-changed",
483                     G_CALLBACK (gimp_int_adjustment_update),
484                     &black_level);
485   g_signal_connect_swapped (adj, "value-changed",
486                             G_CALLBACK (gimp_preview_invalidate),
487                             preview);
488 
489   /*
490    * White level control...
491    */
492 
493   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2,
494                               _("_White level:"), SCALE_WIDTH, ENTRY_WIDTH,
495                               white_level, 0, 256, 1, 8, 0,
496                               TRUE, 0, 0,
497                               NULL, NULL);
498   g_signal_connect (adj, "value-changed",
499                     G_CALLBACK (gimp_int_adjustment_update),
500                     &white_level);
501   g_signal_connect_swapped (adj, "value-changed",
502                             G_CALLBACK (gimp_preview_invalidate),
503                             preview);
504 
505   gtk_widget_show (dialog);
506 
507   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
508 
509   gtk_widget_destroy (dialog);
510 
511   return run;
512 }
513 
514 static void
preview_update(GtkWidget * widget)515 preview_update (GtkWidget *widget)
516 {
517   GimpPreview *preview = GIMP_PREVIEW (widget);
518   GeglBuffer  *src_buffer;
519   const Babl  *format;
520   guchar      *dst;
521   guchar      *src;
522   gint         img_bpp;
523   gint         x1,y1;
524   gint         width, height;
525 
526   preview = GIMP_PREVIEW (widget);
527 
528   if (gimp_drawable_is_rgb (drawable_ID))
529     {
530       if (gimp_drawable_has_alpha (drawable_ID))
531         format = babl_format ("R'G'B'A u8");
532       else
533         format = babl_format ("R'G'B' u8");
534     }
535   else
536     {
537       if (gimp_drawable_has_alpha (drawable_ID))
538         format = babl_format ("Y'A u8");
539       else
540         format = babl_format ("Y' u8");
541     }
542 
543   img_bpp = babl_format_get_bytes_per_pixel (format);
544 
545   width  = preview->width;
546   height = preview->height;
547 
548   gimp_preview_get_position (preview, &x1, &y1);
549 
550   src_buffer = gimp_drawable_get_buffer (drawable_ID);
551 
552   dst = g_new (guchar, width * height * img_bpp);
553   src = g_new (guchar, width * height * img_bpp);
554 
555   gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y1, width, height), 1.0,
556                    format, src,
557                    GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
558 
559   despeckle_median (src, dst, width, height, img_bpp, despeckle_radius, TRUE);
560 
561   gimp_preview_draw_buffer (preview, dst, width * img_bpp);
562 
563   g_object_unref (src_buffer);
564 
565   g_free (src);
566   g_free (dst);
567 }
568 
569 static void
dialog_adaptive_callback(GtkWidget * widget,gpointer data)570 dialog_adaptive_callback (GtkWidget *widget,
571                           gpointer   data)
572 {
573   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
574     filter_type |= FILTER_ADAPTIVE;
575   else
576     filter_type &= ~FILTER_ADAPTIVE;
577 
578   gimp_preview_invalidate (GIMP_PREVIEW (preview));
579 }
580 
581 static void
dialog_recursive_callback(GtkWidget * widget,gpointer data)582 dialog_recursive_callback (GtkWidget *widget,
583                            gpointer   data)
584 {
585   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
586     filter_type |= FILTER_RECURSIVE;
587   else
588     filter_type &= ~FILTER_RECURSIVE;
589 
590   gimp_preview_invalidate (GIMP_PREVIEW (preview));
591 }
592 
593 
594 static inline void
list_add_elem(PixelsList * list,const guchar * elem)595 list_add_elem (PixelsList   *list,
596                const guchar *elem)
597 {
598   const gint pos = list->start + list->count++;
599 
600   list->elems[pos >= MAX_LIST_ELEMS ? pos - MAX_LIST_ELEMS : pos] = elem;
601 }
602 
603 static inline void
list_del_elem(PixelsList * list)604 list_del_elem (PixelsList* list)
605 {
606   list->count--;
607   list->start++;
608 
609   if (list->start >= MAX_LIST_ELEMS)
610     list->start = 0;
611 }
612 
613 static inline const guchar *
list_get_random_elem(PixelsList * list)614 list_get_random_elem (PixelsList *list)
615 {
616   const gint pos = list->start + rand () % list->count;
617 
618   if (pos >= MAX_LIST_ELEMS)
619     return list->elems[pos - MAX_LIST_ELEMS];
620 
621   return list->elems[pos];
622 }
623 
624 static inline void
histogram_add(DespeckleHistogram * hist,guchar val,const guchar * orig)625 histogram_add (DespeckleHistogram *hist,
626                guchar              val,
627                const guchar       *orig)
628 {
629   hist->elems[val]++;
630   list_add_elem (&hist->origs[val], orig);
631 }
632 
633 static inline void
histogram_remove(DespeckleHistogram * hist,guchar val)634 histogram_remove (DespeckleHistogram *hist,
635                   guchar              val)
636 {
637   hist->elems[val]--;
638   list_del_elem (&hist->origs[val]);
639 }
640 
641 static inline void
histogram_clean(DespeckleHistogram * hist)642 histogram_clean (DespeckleHistogram *hist)
643 {
644   gint i;
645 
646   for (i = 0; i < 256; i++)
647     {
648       hist->elems[i] = 0;
649       hist->origs[i].count = 0;
650     }
651 }
652 
653 static inline const guchar *
histogram_get_median(DespeckleHistogram * hist,const guchar * _default)654 histogram_get_median (DespeckleHistogram *hist,
655                       const guchar       *_default)
656 {
657   gint count = histrest;
658   gint i;
659   gint sum = 0;
660 
661   if (! count)
662     return _default;
663 
664   count = (count + 1) / 2;
665 
666   i = 0;
667   while ((sum += hist->elems[i]) < count)
668     i++;
669 
670   return list_get_random_elem (&hist->origs[i]);
671 }
672 
673 static inline void
add_val(DespeckleHistogram * hist,const guchar * src,gint width,gint bpp,gint x,gint y)674 add_val (DespeckleHistogram *hist,
675          const guchar       *src,
676          gint                width,
677          gint                bpp,
678          gint                x,
679          gint                y)
680 {
681   const gint pos   = (x + (y * width)) * bpp;
682   const gint value = pixel_luminance (src + pos, bpp);
683 
684   if (value > black_level && value < white_level)
685   {
686     histogram_add (hist, value, src + pos);
687     histrest++;
688   }
689   else
690   {
691     if (value <= black_level)
692       hist0++;
693 
694     if (value >= white_level)
695       hist255++;
696   }
697 }
698 
699 static inline void
del_val(DespeckleHistogram * hist,const guchar * src,gint width,gint bpp,gint x,gint y)700 del_val (DespeckleHistogram *hist,
701          const guchar       *src,
702          gint                width,
703          gint                bpp,
704          gint                x,
705          gint                y)
706 {
707   const gint pos   = (x + (y * width)) * bpp;
708   const gint value = pixel_luminance (src + pos, bpp);
709 
710   if (value > black_level && value < white_level)
711   {
712     histogram_remove (hist, value);
713     histrest--;
714   }
715   else
716   {
717     if (value <= black_level)
718       hist0--;
719 
720     if (value >= white_level)
721       hist255--;
722   }
723 }
724 
725 static inline void
add_vals(DespeckleHistogram * hist,const guchar * src,gint width,gint bpp,gint xmin,gint ymin,gint xmax,gint ymax)726 add_vals (DespeckleHistogram *hist,
727           const guchar       *src,
728           gint                width,
729           gint                bpp,
730           gint                xmin,
731           gint                ymin,
732           gint                xmax,
733           gint                ymax)
734 {
735   gint x;
736   gint y;
737 
738   if (xmin > xmax)
739     return;
740 
741   for (y = ymin; y <= ymax; y++)
742     {
743       for (x = xmin; x <= xmax; x++)
744         {
745           add_val (hist, src, width, bpp, x, y);
746         }
747     }
748 }
749 
750 static inline void
del_vals(DespeckleHistogram * hist,const guchar * src,gint width,gint bpp,gint xmin,gint ymin,gint xmax,gint ymax)751 del_vals (DespeckleHistogram *hist,
752           const guchar       *src,
753           gint                width,
754           gint                bpp,
755           gint                xmin,
756           gint                ymin,
757           gint                xmax,
758           gint                ymax)
759 {
760   gint x;
761   gint y;
762 
763   if (xmin > xmax)
764     return;
765 
766   for (y = ymin; y <= ymax; y++)
767     {
768       for (x = xmin; x <= xmax; x++)
769         {
770           del_val (hist, src, width, bpp, x, y);
771         }
772     }
773 }
774 
775 static inline void
update_histogram(DespeckleHistogram * hist,const guchar * src,gint width,gint bpp,gint xmin,gint ymin,gint xmax,gint ymax)776 update_histogram (DespeckleHistogram *hist,
777                   const guchar       *src,
778                   gint                width,
779                   gint                bpp,
780                   gint                xmin,
781                   gint                ymin,
782                   gint                xmax,
783                   gint                ymax)
784 {
785   /* assuming that radious of the box can change no more than one
786      pixel in each call */
787   /* assuming that box is moving either right or down */
788 
789   del_vals (hist,
790             src, width, bpp, hist->xmin, hist->ymin, xmin - 1, hist->ymax);
791   del_vals (hist, src, width, bpp, xmin, hist->ymin, xmax, ymin - 1);
792   del_vals (hist, src, width, bpp, xmin, ymax + 1, xmax, hist->ymax);
793 
794   add_vals (hist, src, width, bpp, hist->xmax + 1, ymin, xmax, ymax);
795   add_vals (hist, src, width, bpp, xmin, ymin, hist->xmax, hist->ymin - 1);
796   add_vals (hist,
797             src, width, bpp, hist->xmin, hist->ymax + 1, hist->xmax, ymax);
798 
799   hist->xmin = xmin;
800   hist->ymin = ymin;
801   hist->xmax = xmax;
802   hist->ymax = ymax;
803 }
804 
805 static void
despeckle_median(guchar * src,guchar * dst,gint width,gint height,gint bpp,gint radius,gboolean preview)806 despeckle_median (guchar   *src,
807                   guchar   *dst,
808                   gint      width,
809                   gint      height,
810                   gint      bpp,
811                   gint      radius,
812                   gboolean  preview)
813 {
814   guint  progress;
815   guint  max_progress;
816   gint   x, y;
817   gint   adapt_radius;
818   gint   pos;
819   gint   ymin;
820   gint   ymax;
821   gint   xmin;
822   gint   xmax;
823 
824   memset (&histogram, 0, sizeof(histogram));
825   progress     = 0;
826   max_progress = width * height;
827 
828   if (! preview)
829     gimp_progress_init (_("Despeckle"));
830 
831   adapt_radius = radius;
832   for (y = 0; y < height; y++)
833     {
834       x = 0;
835       ymin = MAX (0, y - adapt_radius);
836       ymax = MIN (height - 1, y + adapt_radius);
837       xmin = MAX (0, x - adapt_radius);
838       xmax = MIN (width - 1, x + adapt_radius);
839       hist0   = 0;
840       histrest = 0;
841       hist255 = 0;
842       histogram_clean (&histogram);
843       histogram.xmin = xmin;
844       histogram.ymin = ymin;
845       histogram.xmax = xmax;
846       histogram.ymax = ymax;
847       add_vals (&histogram,
848                 src, width, bpp,
849                 histogram.xmin, histogram.ymin,
850                 histogram.xmax, histogram.ymax);
851 
852       for (x = 0; x < width; x++)
853         {
854           const guchar *pixel;
855 
856           ymin = MAX (0, y - adapt_radius); /* update ymin, ymax when adapt_radius changed (FILTER_ADAPTIVE) */
857           ymax = MIN (height - 1, y + adapt_radius);
858           xmin = MAX (0, x - adapt_radius);
859           xmax = MIN (width - 1, x + adapt_radius);
860 
861           update_histogram (&histogram,
862                             src, width, bpp, xmin, ymin, xmax, ymax);
863 
864           pos = (x + (y * width)) * bpp;
865           pixel = histogram_get_median (&histogram, src + pos);
866 
867           if (filter_type & FILTER_RECURSIVE)
868             {
869               del_val (&histogram, src, width, bpp, x, y);
870               pixel_copy (src + pos, pixel, bpp);
871               add_val (&histogram, src, width, bpp, x, y);
872             }
873 
874           pixel_copy (dst + pos, pixel, bpp);
875 
876           /*
877            * Check the histogram and adjust the diameter accordingly...
878            */
879           if (filter_type & FILTER_ADAPTIVE)
880             {
881               if (hist0 >= adapt_radius || hist255 >= adapt_radius)
882                 {
883                   if (adapt_radius < radius)
884                     adapt_radius++;
885                 }
886               else if (adapt_radius > 1)
887                 {
888                   adapt_radius--;
889                 }
890             }
891         }
892 
893       progress += width;
894 
895       if (! preview && y % 32 == 0)
896         gimp_progress_update ((gdouble) progress / (gdouble) max_progress);
897     }
898 
899   if (! preview)
900     gimp_progress_update (1.0);
901 }
902