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