2 * Wavelet Denoise filter for GIMP - The GNU Image Manipulation Program
4 * Copyright (C) 2008 Monty
5 * Code based on research by Crystal Wagner and Prof. Ivan Selesnik,
6 * Polytechnic University, Brooklyn, NY
7 * See: http://taco.poly.edu/selesi/DoubleSoftware/
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 2 of the License, or
12 * (at your option) any later version.
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.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30 #include <libgimp/gimp.h>
31 #include <libgimp/gimpui.h>
37 #define PLUG_IN_PROC "plug-in-simple-denoise"
38 #define PLUG_IN_PROC2 "plug-in-selective-denoise"
39 #define PLUG_IN_BINARY "denoise"
40 #define PLUG_IN_VERSION "17 Dec 2008"
46 static void query (void);
47 static void run (const gchar
*name
,
49 const GimpParam
*param
,
51 GimpParam
**returm_vals
);
53 static void denoise (GimpDrawable
*drawable
);
54 static void denoise_pre (GimpDrawable
*drawable
);
56 static gboolean
denoise_dialog (GimpDrawable
*drawable
);
57 static gboolean
denoise_dialog_simple (GimpDrawable
*drawable
);
58 static void denoise_work(int w
, int h
, int bpp
, guchar
*buffer
);
60 static void preview_update (GtkWidget
*preview
, GtkWidget
*dialog
);
66 const GimpPlugInInfo PLUG_IN_INFO
=
70 query
, /* query_proc */
88 static DenoiseParams denoise_params
=
95 static GtkWidget
*preview
;
96 static GtkToggleButton
*preview_toggle
=NULL
;
97 static GtkObject
*madj
[4];
98 static GtkObject
*ladj
[1];
99 static guchar
*preview_cache_blit
=NULL
;
100 static float *preview_cache_luma
=NULL
;
101 static float *preview_cache_Pb
=NULL
;
102 static float *preview_cache_Pr
=NULL
;
103 static int preview_cache_x
;
104 static int preview_cache_y
;
105 static int preview_cache_w
;
106 static int preview_cache_h
;
108 static int variance_median
=0;
109 static int pre_run
=0;
118 static const GimpParamDef args
[] =
120 { GIMP_PDB_INT32
, "run-mode", "Interactive, non-interactive" },
121 { GIMP_PDB_IMAGE
, "image", "Input image" },
122 { GIMP_PDB_DRAWABLE
, "drawable", "Input drawable" },
123 { GIMP_PDB_FLOAT
, "filter", "Denoise filter strength" },
126 static const GimpParamDef args2
[] =
128 { GIMP_PDB_INT32
, "run-mode", "Interactive, non-interactive" },
129 { GIMP_PDB_IMAGE
, "image", "Input image" },
130 { GIMP_PDB_DRAWABLE
, "drawable", "Input drawable" },
131 { GIMP_PDB_FLOAT
, "filterY", "Denoise filter luma strength" },
132 { GIMP_PDB_FLOAT
, "filterC", "Denoise filter chroma strength" },
133 { GIMP_PDB_INT32
, "soft", "Use soft thresholding" },
134 { GIMP_PDB_INT32
, "multiscale", "Enable multiscale adjustment" },
135 { GIMP_PDB_FLOAT
, "f1", "Fine detail adjust" },
136 { GIMP_PDB_FLOAT
, "f2", "Detail adjust" },
137 { GIMP_PDB_FLOAT
, "f3", "Mid adjust" },
138 { GIMP_PDB_FLOAT
, "f4", "Coarse adjust" },
139 { GIMP_PDB_INT32
, "lowlight", "Low light noise mode" },
140 { GIMP_PDB_FLOAT
, "lowlight_adj", "Low light threshold adj" },
143 gimp_install_procedure (PLUG_IN_PROC
,
144 "Simple denoise filter",
145 "This plugin removes random noise at all scales from "
146 "an image; at an extreme produces an airbrush-like"
148 "Monty <monty@xiph.org>",
149 "Copyright 2008 by Monty",
154 G_N_ELEMENTS (args
), 0,
157 gimp_install_procedure (PLUG_IN_PROC2
,
158 "Selective denoise filter",
159 "This plugin selectively removes remove random "
160 "noise from an image; at an extreme produces an airbrush-like"
162 "Monty <monty@xiph.org>",
163 "Copyright 2008 by Monty",
165 "Selecti_ve Denoise...",
168 G_N_ELEMENTS (args2
), 0,
171 gimp_plugin_menu_register (PLUG_IN_PROC
, "<Image>/Filters/Montypak");
172 gimp_plugin_menu_register (PLUG_IN_PROC2
, "<Image>/Filters/Montypak");
176 run (const gchar
*name
,
178 const GimpParam
*param
,
180 GimpParam
**return_vals
)
182 static GimpParam values
[1]; /* Return values */
183 GimpRunMode run_mode
; /* Current run mode */
184 GimpPDBStatusType status
; /* Return status */
185 GimpDrawable
*drawable
; /* Current image */
188 * Initialize parameter data...
191 status
= GIMP_PDB_SUCCESS
;
192 run_mode
= param
[0].data
.d_int32
;
195 *return_vals
= values
;
197 values
[0].type
= GIMP_PDB_STATUS
;
198 values
[0].data
.d_status
= status
;
201 * Get drawable information...
204 drawable
= gimp_drawable_get (param
[2].data
.d_drawable
);
205 gimp_tile_cache_ntiles (2 * drawable
->ntile_cols
);
208 * See how we will run
213 case GIMP_RUN_INTERACTIVE
:
215 * Possibly retrieve data...
217 gimp_get_data (PLUG_IN_PROC
, &denoise_params
);
220 * Get information from the dialog...
222 if (strcmp (name
, PLUG_IN_PROC
) == 0){
223 if (!denoise_dialog_simple (drawable
))
226 if (!denoise_dialog (drawable
))
231 case GIMP_RUN_NONINTERACTIVE
:
233 * Make sure all the arguments are present...
235 if (strcmp (name
, PLUG_IN_PROC
) == 0){
237 status
= GIMP_PDB_CALLING_ERROR
;
239 memset(&denoise_params
,0,sizeof(denoise_params
));
240 denoise_params
.filterC
= denoise_params
.filterY
= param
[3].data
.d_float
;
244 status
= GIMP_PDB_CALLING_ERROR
;
246 denoise_params
.filterY
= param
[3].data
.d_float
;
247 denoise_params
.filterC
= param
[4].data
.d_float
;
248 denoise_params
.soft
= param
[5].data
.d_int32
;
249 denoise_params
.multiscale
= param
[6].data
.d_int32
;
250 denoise_params
.f1
= param
[7].data
.d_float
;
251 denoise_params
.f2
= param
[8].data
.d_float
;
252 denoise_params
.f3
= param
[9].data
.d_float
;
253 denoise_params
.f4
= param
[10].data
.d_float
;
254 denoise_params
.lowlight
= param
[11].data
.d_int32
;
255 denoise_params
.lowlight_adj
= param
[12].data
.d_float
;
260 case GIMP_RUN_WITH_LAST_VALS
:
262 * Possibly retrieve data...
264 gimp_get_data (PLUG_IN_PROC
, &denoise_params
);
268 status
= GIMP_PDB_CALLING_ERROR
;
272 if (status
== GIMP_PDB_SUCCESS
)
274 if ((gimp_drawable_is_rgb (drawable
->drawable_id
) ||
275 gimp_drawable_is_gray (drawable
->drawable_id
)))
283 * If run mode is interactive, flush displays...
285 if (run_mode
!= GIMP_RUN_NONINTERACTIVE
)
286 gimp_displays_flush ();
291 if (run_mode
== GIMP_RUN_INTERACTIVE
)
292 gimp_set_data (PLUG_IN_PROC
,
293 &denoise_params
, sizeof (DenoiseParams
));
296 status
= GIMP_PDB_EXECUTION_ERROR
;
300 * Reset the current run status...
302 values
[0].data
.d_status
= status
;
305 * Detach from the drawable...
307 gimp_drawable_detach (drawable
);
310 static void denoise (GimpDrawable
*drawable
){
312 GimpPixelRgn src_rgn
; /* Source image region */
313 GimpPixelRgn dst_rgn
; /* Destination image region */
321 gimp_drawable_mask_bounds (drawable
->drawable_id
,
326 bpp
= gimp_drawable_bpp (drawable
->drawable_id
);
329 * Setup for filter...
332 gimp_pixel_rgn_init (&src_rgn
, drawable
,
333 x1
, y1
, w
, h
, FALSE
, FALSE
);
334 gimp_pixel_rgn_init (&dst_rgn
, drawable
,
335 x1
, y1
, w
, h
, TRUE
, TRUE
);
337 /***************************************/
338 buffer
= g_new (guchar
, w
* h
* bpp
);
339 gimp_pixel_rgn_get_rect (&src_rgn
, buffer
, x1
, y1
, w
, h
);
340 if(!pre_run
) denoise_pre(drawable
);
341 denoise_work(w
,h
,bpp
,buffer
);
342 gimp_pixel_rgn_set_rect (&dst_rgn
, buffer
, x1
, y1
, w
, h
);
343 /**************************************/
347 * Update the screen...
350 gimp_drawable_flush (drawable
);
351 gimp_drawable_merge_shadow (drawable
->drawable_id
, TRUE
);
352 gimp_drawable_update (drawable
->drawable_id
, x1
, y1
, w
, h
);
357 static void dialog_filterB_callback (GtkWidget
*widget
,
360 if(preview_cache_luma
)
361 g_free(preview_cache_luma
);
362 preview_cache_luma
=NULL
;
364 g_free(preview_cache_Pb
);
365 preview_cache_Pb
=NULL
;
367 g_free(preview_cache_Pr
);
368 preview_cache_Pr
=NULL
;
369 if(!preview_toggle
->active
){
370 if(preview_cache_blit
)
371 g_free(preview_cache_blit
);
372 preview_cache_blit
=NULL
;
376 static void dialog_filterY_callback (GtkWidget
*widget
,
379 if(preview_cache_luma
)
380 g_free(preview_cache_luma
);
381 preview_cache_luma
=NULL
;
382 if(!preview_toggle
->active
){
383 if(preview_cache_blit
)
384 g_free(preview_cache_blit
);
385 preview_cache_blit
=NULL
;
389 static void dialog_filterC_callback (GtkWidget
*widget
,
393 g_free(preview_cache_Pb
);
394 preview_cache_Pb
=NULL
;
396 g_free(preview_cache_Pr
);
397 preview_cache_Pr
=NULL
;
398 if(!preview_toggle
->active
){
399 if(preview_cache_blit
)
400 g_free(preview_cache_blit
);
401 preview_cache_blit
=NULL
;
405 static void dialog_soft_callback (GtkWidget
*widget
,
408 denoise_params
.soft
= (GTK_TOGGLE_BUTTON (widget
)->active
);
409 if(denoise_params
.filterY
>0.)
410 dialog_filterY_callback(widget
,data
);
411 if(denoise_params
.filterC
>0.)
412 dialog_filterC_callback(widget
,data
);
413 if(denoise_params
.filterY
>0. || denoise_params
.filterC
>0.)
414 gimp_preview_invalidate (GIMP_PREVIEW (preview
));
417 static void dialog_multiscale_callback (GtkWidget
*widget
,
420 denoise_params
.multiscale
= (GTK_TOGGLE_BUTTON (widget
)->active
);
421 gimp_scale_entry_set_sensitive(madj
[0],denoise_params
.multiscale
);
422 gimp_scale_entry_set_sensitive(madj
[1],denoise_params
.multiscale
);
423 gimp_scale_entry_set_sensitive(madj
[2],denoise_params
.multiscale
);
424 gimp_scale_entry_set_sensitive(madj
[3],denoise_params
.multiscale
);
425 if(denoise_params
.f1
!=0. ||
426 denoise_params
.f2
!=0. ||
427 denoise_params
.f3
!=0. ||
428 denoise_params
.f4
!=0.){
429 if(denoise_params
.filterY
>0.)
430 dialog_filterY_callback(widget
,data
);
431 if(denoise_params
.filterC
>0.)
432 dialog_filterC_callback(widget
,data
);
433 if(denoise_params
.filterY
>0. || denoise_params
.filterC
>0.)
434 gimp_preview_invalidate (GIMP_PREVIEW (preview
));
438 static void dialog_multiadj_callback (GtkWidget
*widget
,
440 if(denoise_params
.filterY
>0.)
441 dialog_filterY_callback(widget
,data
);
442 if(denoise_params
.filterC
>0.)
443 dialog_filterC_callback(widget
,data
);
444 if(denoise_params
.filterY
>0. || denoise_params
.filterC
>0.)
445 gimp_preview_invalidate (GIMP_PREVIEW (preview
));
448 static void dialog_mask_callback (GtkWidget
*widget
,
451 if(!preview_toggle
->active
){
452 if(preview_cache_blit
)
453 g_free(preview_cache_blit
);
454 preview_cache_blit
=NULL
;
458 static void dialog_lowlight_callback (GtkWidget
*widget
,
461 denoise_params
.lowlight
= (GTK_TOGGLE_BUTTON (widget
)->active
);
462 gimp_scale_entry_set_sensitive(ladj
[0],denoise_params
.lowlight
);
463 dialog_mask_callback(widget
,data
);
464 gimp_preview_invalidate (GIMP_PREVIEW (preview
));
467 static void find_preview_toggle(GtkWidget
*widget
, gpointer dummy
){
469 preview_toggle
= GTK_TOGGLE_BUTTON(widget
);
472 static void set_busy(GtkWidget
*preview
, GtkWidget
*dialog
){
473 GdkDisplay
*display
= gtk_widget_get_display (dialog
);
474 GdkCursor
*cursor
= gdk_cursor_new_for_display (display
, GDK_WATCH
);
475 gdk_window_set_cursor(preview
->window
, cursor
);
476 gdk_window_set_cursor(gimp_preview_get_area(GIMP_PREVIEW(preview
))->window
, cursor
);
477 gdk_window_set_cursor(dialog
->window
, cursor
);
478 gdk_cursor_unref(cursor
);
481 static gboolean
denoise_dialog_simple (GimpDrawable
*drawable
){
483 GtkWidget
*main_vbox
;
488 gint planes
= gimp_drawable_bpp (drawable
->drawable_id
);
489 gimp_ui_init (PLUG_IN_BINARY
, TRUE
);
491 dialog
= gimp_dialog_new ("Simple Denoise", PLUG_IN_BINARY
,
493 gimp_standard_help_func
, PLUG_IN_PROC
,
495 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
496 GTK_STOCK_OK
, GTK_RESPONSE_OK
,
500 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog
),
505 gimp_window_set_transient (GTK_WINDOW (dialog
));
507 main_vbox
= gtk_vbox_new (FALSE
, 12);
508 gtk_container_set_border_width (GTK_CONTAINER (main_vbox
), 12);
509 gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog
)->vbox
), main_vbox
);
510 gtk_widget_show (main_vbox
);
512 preview
= gimp_drawable_preview_new (drawable
, NULL
);
513 gtk_box_pack_start (GTK_BOX (main_vbox
), preview
, TRUE
, TRUE
, 0);
514 gtk_widget_show (preview
);
516 g_signal_connect (preview
, "invalidated",
517 G_CALLBACK (preview_update
),
519 gtk_container_foreach(GTK_CONTAINER(gimp_preview_get_controls(GIMP_PREVIEW(preview
))),
520 find_preview_toggle
,NULL
);
522 /* Filter strength adjust */
523 table
= gtk_table_new (1+(planes
>2), 3, FALSE
);
524 gtk_table_set_col_spacings (GTK_TABLE (table
), 6);
525 gtk_box_pack_start (GTK_BOX (main_vbox
), table
, FALSE
, FALSE
, 0);
526 gtk_widget_show (table
);
529 adj
= gimp_scale_entry_new (GTK_TABLE (table
), 0, 0,
532 denoise_params
.filterY
,
536 g_signal_connect (adj
, "value-changed",
537 G_CALLBACK (gimp_float_adjustment_update
),
538 &denoise_params
.filterY
);
539 g_signal_connect (adj
, "value-changed",
540 G_CALLBACK (gimp_float_adjustment_update
),
541 &denoise_params
.filterC
);
542 g_signal_connect_swapped (adj
, "value-changed",
543 G_CALLBACK (gimp_preview_invalidate
),
545 g_signal_connect (adj
, "value-changed",G_CALLBACK (dialog_filterB_callback
),NULL
);
547 gtk_widget_show (dialog
);
548 run
= (gimp_dialog_run (GIMP_DIALOG (dialog
)) == GTK_RESPONSE_OK
);
550 gtk_widget_destroy (dialog
);
552 if(preview_cache_blit
)
553 g_free(preview_cache_blit
);
554 preview_cache_blit
=NULL
;
556 if(preview_cache_luma
)
557 g_free(preview_cache_luma
);
558 preview_cache_luma
=NULL
;
560 g_free(preview_cache_Pb
);
561 preview_cache_Pb
=NULL
;
563 g_free(preview_cache_Pr
);
564 preview_cache_Pr
=NULL
;
569 static gboolean
denoise_dialog (GimpDrawable
*drawable
){
571 GtkWidget
*main_vbox
;
576 gint planes
= gimp_drawable_bpp (drawable
->drawable_id
);
577 gimp_ui_init (PLUG_IN_BINARY
, TRUE
);
579 dialog
= gimp_dialog_new ("Selective Denoise", PLUG_IN_BINARY
,
581 gimp_standard_help_func
, PLUG_IN_PROC
,
583 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
584 GTK_STOCK_OK
, GTK_RESPONSE_OK
,
588 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog
),
593 gimp_window_set_transient (GTK_WINDOW (dialog
));
595 main_vbox
= gtk_vbox_new (FALSE
, 12);
596 gtk_container_set_border_width (GTK_CONTAINER (main_vbox
), 12);
597 gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog
)->vbox
), main_vbox
);
598 gtk_widget_show (main_vbox
);
600 preview
= gimp_drawable_preview_new (drawable
, NULL
);
601 gtk_box_pack_start (GTK_BOX (main_vbox
), preview
, TRUE
, TRUE
, 0);
602 gtk_widget_show (preview
);
604 g_signal_connect (preview
, "invalidated",
605 G_CALLBACK (preview_update
),
607 gtk_container_foreach(GTK_CONTAINER(gimp_preview_get_controls(GIMP_PREVIEW(preview
))),
608 find_preview_toggle
,NULL
);
610 /* Filter strength adjust */
611 table
= gtk_table_new (1+(planes
>2), 3, FALSE
);
612 gtk_table_set_col_spacings (GTK_TABLE (table
), 6);
613 gtk_box_pack_start (GTK_BOX (main_vbox
), table
, FALSE
, FALSE
, 0);
614 gtk_widget_show (table
);
617 adj
= gimp_scale_entry_new (GTK_TABLE (table
), 0, 0,
618 (planes
>2?"Luma _Denoise":"_Denoise"),
620 denoise_params
.filterY
,
624 g_signal_connect (adj
, "value-changed",
625 G_CALLBACK (gimp_float_adjustment_update
),
626 &denoise_params
.filterY
);
627 g_signal_connect_swapped (adj
, "value-changed",
628 G_CALLBACK (gimp_preview_invalidate
),
630 g_signal_connect (adj
, "value-changed",G_CALLBACK (dialog_filterY_callback
),NULL
);
633 /* Chroma filter strength adjust */
635 adj
= gimp_scale_entry_new (GTK_TABLE (table
), 0, 1,
636 "_Chroma Denoise", 300, 0,
637 denoise_params
.filterC
,
641 g_signal_connect (adj
, "value-changed",
642 G_CALLBACK (gimp_float_adjustment_update
),
643 &denoise_params
.filterC
);
644 g_signal_connect_swapped (adj
, "value-changed",
645 G_CALLBACK (gimp_preview_invalidate
),
647 g_signal_connect (adj
, "value-changed",G_CALLBACK (dialog_filterC_callback
),NULL
);
650 /* Threshold shape */
651 button
= gtk_check_button_new_with_mnemonic ("So_ft thresholding");
652 gtk_box_pack_start (GTK_BOX (main_vbox
), button
, FALSE
, FALSE
, 0);
653 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button
),
654 denoise_params
.soft
);
655 gtk_widget_show (button
);
656 g_signal_connect (button
, "toggled", G_CALLBACK (dialog_soft_callback
), NULL
);
659 button
= gtk_check_button_new_with_mnemonic ("_Low-light noise mode");
660 gtk_box_pack_start (GTK_BOX (main_vbox
), button
, FALSE
, FALSE
, 0);
661 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button
),
662 denoise_params
.lowlight
);
663 gtk_widget_show (button
);
664 g_signal_connect (button
, "toggled", G_CALLBACK (dialog_lowlight_callback
),NULL
);
666 /* Subadjustments for lowlight mode */
667 table
= gtk_table_new (1, 4, FALSE
);
668 gtk_table_set_col_spacings (GTK_TABLE (table
), 6);
669 gtk_table_set_col_spacing (GTK_TABLE (table
), 0, 20);
670 gtk_box_pack_start (GTK_BOX (main_vbox
), table
, FALSE
, FALSE
, 0);
671 gtk_widget_show (table
);
673 /* low light threshold adj */
674 ladj
[0] = adj
= gimp_scale_entry_new (GTK_TABLE (table
), 1, 0,
675 "_Threshold adjust:", 300, 0,
676 denoise_params
.lowlight_adj
,
677 -100, +100, 1, 10, 0,
680 g_signal_connect (adj
, "value-changed",
681 G_CALLBACK (gimp_float_adjustment_update
),
682 &denoise_params
.lowlight_adj
);
683 g_signal_connect_swapped (adj
, "value-changed",
684 G_CALLBACK (gimp_preview_invalidate
),
686 g_signal_connect (adj
, "value-changed",G_CALLBACK (dialog_mask_callback
),NULL
);
687 gimp_scale_entry_set_sensitive(ladj
[0],denoise_params
.lowlight
);
689 /* multiscale adjust select */
690 button
= gtk_check_button_new_with_mnemonic ("Multiscale _adjustment");
691 gtk_box_pack_start (GTK_BOX (main_vbox
), button
, FALSE
, FALSE
, 0);
692 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button
),
693 denoise_params
.multiscale
);
694 gtk_widget_show (button
);
695 g_signal_connect (button
, "toggled",
696 G_CALLBACK (dialog_multiscale_callback
),
699 /* Subadjustments for multiscale */
700 table
= gtk_table_new (4, 4, FALSE
);
701 gtk_table_set_col_spacings (GTK_TABLE (table
), 6);
702 gtk_table_set_col_spacing (GTK_TABLE (table
), 0, 20);
703 gtk_box_pack_start (GTK_BOX (main_vbox
), table
, FALSE
, FALSE
, 0);
704 gtk_widget_show (table
);
706 /* fine detail adjust */
707 madj
[0] = adj
= gimp_scale_entry_new (GTK_TABLE (table
), 1, 0,
708 "_Very fine denoise:", 300, 0,
710 -100, +100, 1, 10, 0,
713 g_signal_connect (adj
, "value-changed",
714 G_CALLBACK (gimp_float_adjustment_update
),
716 g_signal_connect (adj
,"value-changed",G_CALLBACK(dialog_multiadj_callback
),NULL
);
719 madj
[1] = adj
= gimp_scale_entry_new (GTK_TABLE (table
), 1, 1,
720 "_Fine denoise:", 300, 0,
725 g_signal_connect (adj
, "value-changed",
726 G_CALLBACK (gimp_float_adjustment_update
),
728 g_signal_connect (adj
,"value-changed",G_CALLBACK(dialog_multiadj_callback
),NULL
);
731 madj
[2] = adj
= gimp_scale_entry_new (GTK_TABLE (table
), 1, 2,
732 "_Mid denoise:", 300, 0,
737 g_signal_connect (adj
, "value-changed",
738 G_CALLBACK (gimp_float_adjustment_update
),
740 g_signal_connect (adj
,"value-changed",G_CALLBACK(dialog_multiadj_callback
),NULL
);
743 madj
[3] = adj
= gimp_scale_entry_new (GTK_TABLE (table
), 1, 3,
744 "_Coarse denoise:", 300, 0,
749 g_signal_connect (adj
, "value-changed",
750 G_CALLBACK (gimp_float_adjustment_update
),
752 g_signal_connect (adj
,"value-changed",G_CALLBACK(dialog_multiadj_callback
),NULL
);
754 gimp_scale_entry_set_sensitive(madj
[0],denoise_params
.multiscale
);
755 gimp_scale_entry_set_sensitive(madj
[1],denoise_params
.multiscale
);
756 gimp_scale_entry_set_sensitive(madj
[2],denoise_params
.multiscale
);
757 gimp_scale_entry_set_sensitive(madj
[3],denoise_params
.multiscale
);
759 gtk_widget_show (dialog
);
760 run
= (gimp_dialog_run (GIMP_DIALOG (dialog
)) == GTK_RESPONSE_OK
);
762 gtk_widget_destroy (dialog
);
764 if(preview_cache_blit
)
765 g_free(preview_cache_blit
);
766 preview_cache_blit
=NULL
;
768 if(preview_cache_luma
)
769 g_free(preview_cache_luma
);
770 preview_cache_luma
=NULL
;
772 g_free(preview_cache_Pb
);
773 preview_cache_Pb
=NULL
;
775 g_free(preview_cache_Pr
);
776 preview_cache_Pr
=NULL
;
781 static int denoise_active_interruptable
;
782 static int denoise_active_interrupt
;
784 static int check_recompute(){
785 while(gtk_events_pending())
786 gtk_main_iteration ();
787 return denoise_active_interrupt
;
791 #define clamp(x) ((x)<0?0:((x)>255?255:(x)))
795 #define Kg (1.f-Kr-Kb)
796 #define Ir (2.f*Kr-2.f)
797 #define Ib (2.f*Kb-2.f)
799 static void compute_luma(guchar
*buffer
, guchar
*luma
, int width
, int height
, int planes
){
803 for(i
=0;i
<width
*height
;i
++)
807 for(i
=0;i
<width
*height
;i
++)
808 luma
[i
]=buffer
[i
<<1];
811 for(i
=0;i
<width
*height
;i
++)
812 luma
[i
]=clamp(rint(buffer
[i
*3]*Kr
+
817 for(i
=0;i
<width
*height
;i
++)
818 luma
[i
]=clamp(rint(buffer
[i
*4]*Kr
+
825 static int compute_YPbPr(guchar
*buffer
, float *luma
, float *Pb
, float *Pr
,
826 int width
, int height
, int planes
, int (*check
)(void)){
828 if(!check
&& planes
>2)
829 gimp_progress_init( "Converting colorspace...");
831 for(j
=0;j
<height
*width
;j
+=width
){
834 for(i
=j
;i
<j
+width
;i
++)
835 if(luma
)luma
[i
]=buffer
[i
];
838 for(i
=j
;i
<j
+width
;i
++)
839 if(luma
)luma
[i
]=buffer
[i
<<1];
842 for(i
=j
;i
<j
+width
;i
++){
843 float Y
= buffer
[i
*3]*Kr
+ buffer
[i
*3+1]*Kg
+ buffer
[i
*3+2]*Kb
;
845 if(Pb
) Pb
[i
] = (buffer
[i
*3+2] - Y
) * (.5f
/ (1.f
- Kb
));
846 if(Pr
) Pr
[i
] = (buffer
[i
*3] - Y
) * (.5f
/ (1.f
- Kr
));
848 if(!check
&& planes
>2)
849 gimp_progress_update((float)i
/(width
*height
));
852 for(i
=j
;i
<j
+width
;i
++){
853 float Y
= buffer
[i
*4]*Kr
+ buffer
[i
*4+1]*Kg
+ buffer
[i
*4+2]*Kb
;
855 if(Pb
) Pb
[i
] = (buffer
[i
*4+2] - Y
) * (.5f
/ (1.f
- Kb
));
856 if(Pr
) Pr
[i
] = (buffer
[i
*4] - Y
) * (.5f
/ (1.f
- Kr
));
858 if(!check
&& planes
>2)
859 gimp_progress_update((float)i
/(width
*height
));
863 if(check
&& check())return 1;
865 if(!check
&& planes
>2)
870 #define computeR(Y,Pb,Pr) ((Y) - Ir*(Pr))
871 #define computeG(Y,Pb,Pr) ((Y) + (Ir*Kr/Kg)*(Pr) + (Ib*Kb/Kg)*(Pb))
872 #define computeB(Y,Pb,Pr) ((Y) - Ib*(Pb))
874 /* find the variance median for the whole image, not just preview, not just the selection */
875 static void denoise_pre(GimpDrawable
*drawable
){
878 gint w
= gimp_drawable_width(drawable
->drawable_id
);
879 gint h
= gimp_drawable_height(drawable
->drawable_id
);
880 gint bpp
= gimp_drawable_bpp (drawable
->drawable_id
);
888 gimp_pixel_rgn_init (&rgn
, drawable
,
889 0, 0, w
, h
, FALSE
, FALSE
);
890 buffer
= g_new (guchar
, w
* h
* bpp
);
891 luma
= g_new (guchar
, w
* h
);
892 gimp_pixel_rgn_get_rect (&rgn
, buffer
, 0, 0, w
, h
);
893 compute_luma(buffer
,luma
,w
,h
,bpp
);
896 /* collect var/mean on the luma plane */
897 v
= g_new(float,w
*h
);
898 collect_var(luma
, v
, NULL
, w
, h
, 5);
902 memset(d
,0,sizeof(d
));
905 int val
= clamp(rint(sqrt(v
[i
])));
923 static int compute_filter(float *buffer
, int w
, int h
, float f
, int (*check
)(void), char *m
){
934 if(denoise_params
.multiscale
){
935 T
[0]*=(denoise_params
.f1
+100)*.01;
936 T
[1]*=(denoise_params
.f2
+100)*.01;
937 T
[2]*=(denoise_params
.f3
+100)*.01;
939 T
[i
]*=(denoise_params
.f4
+100)*.01;
946 if(m
)gimp_progress_init(m
);
947 i
= wavelet_filter(w
, h
, buffer
, (m
!=NULL
), T
, denoise_params
.soft
, check
);
948 if(m
)gimp_progress_end();
954 static int compute_mask(guchar
*buffer
, float *luma
, float *Pb
, float *Pr
,
955 int w
, int h
, int p
, int(*check
)(void)){
959 if(p
>2 && (!Pb
|| !Pr
)) return 1;
961 if(denoise_params
.lowlight
){
962 float l
= denoise_params
.lowlight_adj
*.01;
963 float med
= variance_median
*8.f
;
965 if(!check
)gimp_progress_init( "Masking and unconverting...");
977 float mask
= 1.f
- (luma
[j
]-med
)/med
;
978 if(mask
<0.f
)mask
=0.f
;
979 if(mask
>1.f
)mask
=1.f
;
980 buffer
[j
*p
] = clamp(rint(luma
[j
]*mask
+ (1.f
-mask
)*buffer
[j
*p
]));
982 if(!check
)gimp_progress_update((gfloat
)(i
+w
)/(w
*h
));
983 if(check
&& check()){
984 if(!check
)gimp_progress_end();
992 float mask
= 1.f
- (luma
[j
]-med
)/med
;
993 if(mask
<0.f
)mask
=0.f
;
994 if(mask
>1.f
)mask
=1.f
;
995 buffer
[j
*p
] = clamp(rint(computeR(luma
[j
],Pb
[j
],Pr
[j
])*mask
+ (1.f
-mask
)*buffer
[j
*p
]));
996 buffer
[j
*p
+1] = clamp(rint(computeG(luma
[j
],Pb
[j
],Pr
[j
])*mask
+ (1.f
-mask
)*buffer
[j
*p
+1]));
997 buffer
[j
*p
+2] = clamp(rint(computeB(luma
[j
],Pb
[j
],Pr
[j
])*mask
+ (1.f
-mask
)*buffer
[j
*p
+2]));
999 if(!check
)gimp_progress_update((gfloat
)(i
+w
)/(w
*h
));
1000 if(check
&& check()){
1001 if(!check
)gimp_progress_end();
1007 if(!check
)gimp_progress_end();
1010 if(!check
)gimp_progress_init( "Unconverting...");
1014 for(i
=0;i
<w
*h
;i
+=w
){
1016 buffer
[j
*p
] = clamp(rint(luma
[j
]));
1018 if(!check
)gimp_progress_update((gfloat
)(i
+w
)/(w
*h
));
1019 if(check
&& check()){
1020 if(!check
)gimp_progress_end();
1026 for(i
=0;i
<w
*h
;i
+=w
){
1028 buffer
[j
*p
] = clamp(rint(computeR(luma
[j
],Pb
[j
],Pr
[j
])));
1029 buffer
[j
*p
+1] = clamp(rint(computeG(luma
[j
],Pb
[j
],Pr
[j
])));
1030 buffer
[j
*p
+2] = clamp(rint(computeB(luma
[j
],Pb
[j
],Pr
[j
])));
1032 if(!check
)gimp_progress_update((gfloat
)(i
+w
)/(w
*h
));
1033 if(check
&& check()){
1034 if(!check
)gimp_progress_end();
1040 if(!check
)gimp_progress_end();
1045 static void preview_update (GtkWidget
*preview
, GtkWidget
*dialog
){
1047 GimpDrawable
*drawable
;
1048 GimpPixelRgn rgn
; /* previw region */
1050 gint bpp
; /* Bytes-per-pixel in image */
1051 guchar
*buffer
= NULL
;
1053 /* Because we do async event processing in this plugin (so that the
1054 UI doesn't freeze during long comutation times), a stray expose
1055 of the GimpPreviewArea already queud after this update call
1056 causes it to reload the original unfiltered data, which is
1057 annoying when doing small parameter tweaks. Make sure the
1058 previous run's data is blitted in if appropriate. */
1059 drawable
= gimp_drawable_preview_get_drawable (GIMP_DRAWABLE_PREVIEW (preview
));
1060 bpp
= gimp_drawable_bpp (drawable
->drawable_id
);
1061 gimp_preview_get_position (GIMP_PREVIEW(preview
), &x
, &y
);
1062 gimp_preview_get_size (GIMP_PREVIEW(preview
), &w
, &h
);
1063 if(!pre_run
) denoise_pre(drawable
);
1065 if(x
== preview_cache_x
&&
1066 y
== preview_cache_y
&&
1067 w
== preview_cache_w
&&
1068 h
== preview_cache_h
){
1070 if(preview_cache_blit
)
1071 gimp_preview_draw_buffer (GIMP_PREVIEW(preview
), preview_cache_blit
, w
*bpp
);
1075 /* the preview pane has shifted; dump all caches */
1077 if(preview_cache_blit
)
1078 g_free(preview_cache_blit
);
1079 preview_cache_blit
=NULL
;
1081 if(preview_cache_luma
)
1082 g_free(preview_cache_luma
);
1083 preview_cache_luma
=NULL
;
1085 if(preview_cache_Pb
)
1086 g_free(preview_cache_Pb
);
1087 preview_cache_Pb
=NULL
;
1089 if(preview_cache_Pr
)
1090 g_free(preview_cache_Pr
);
1091 preview_cache_Pr
=NULL
;
1095 denoise_active_interrupt
=1;
1096 if(denoise_active_interruptable
) return;
1097 denoise_active_interruptable
= 1;
1099 while(denoise_active_interrupt
){
1100 denoise_active_interrupt
=0;
1101 set_busy(preview
,dialog
);
1103 gimp_preview_get_position (GIMP_PREVIEW(preview
), &x
, &y
);
1104 gimp_preview_get_size (GIMP_PREVIEW(preview
), &w
, &h
);
1105 gimp_pixel_rgn_init (&rgn
, drawable
, x
, y
, w
, h
, FALSE
, FALSE
);
1107 if(buffer
) g_free (buffer
);
1108 buffer
= g_new (guchar
, w
* h
* bpp
);
1109 gimp_pixel_rgn_get_rect (&rgn
, buffer
, x
, y
, w
, h
);
1111 /* compute what filtered planes we actually need */
1113 float *tempY
= NULL
;
1114 float *tempPb
= NULL
;
1115 float *tempPr
= NULL
;
1117 if(preview_cache_luma
== NULL
) tempY
= g_new(float,w
*h
);
1118 if(bpp
> 2 && preview_cache_Pb
== NULL
) tempPb
= g_new(float,w
*h
);
1119 if(bpp
> 2 && preview_cache_Pr
== NULL
) tempPr
= g_new(float,w
*h
);
1121 compute_YPbPr(buffer
,tempY
,tempPb
,tempPr
,w
,h
,bpp
, check_recompute
);
1123 if(!compute_filter(tempY
, w
, h
, denoise_params
.filterY
, check_recompute
, NULL
)){
1124 preview_cache_luma
= tempY
;
1128 if(!compute_filter(tempPb
, w
, h
, denoise_params
.filterC
, check_recompute
, NULL
)){
1129 preview_cache_Pb
= tempPb
;
1133 if(!compute_filter(tempPr
, w
, h
, denoise_params
.filterC
, check_recompute
, NULL
)){
1134 preview_cache_Pr
= tempPr
;
1138 if(tempY
) g_free(tempY
);
1139 if(tempPb
) g_free(tempPb
);
1140 if(tempPr
) g_free(tempPr
);
1142 if(check_recompute())continue;
1146 if(compute_mask(buffer
, preview_cache_luma
, preview_cache_Pb
, preview_cache_Pr
,
1147 w
, h
, bpp
, check_recompute
))
1148 continue; /* interrupted */
1150 if(preview_cache_blit
)
1151 g_free(preview_cache_blit
);
1152 preview_cache_x
= x
;
1153 preview_cache_y
= y
;
1154 preview_cache_w
= w
;
1155 preview_cache_h
= h
;
1156 preview_cache_blit
= buffer
;
1159 gimp_preview_draw_buffer (GIMP_PREVIEW(preview
), preview_cache_blit
, w
*bpp
);
1160 gimp_drawable_flush (drawable
);
1164 if(buffer
) g_free(buffer
);
1165 denoise_active_interruptable
= 0;
1168 static void denoise_work(int w
, int h
, int p
, guchar
*buffer
){
1169 float *Y
= g_new(float,w
*h
);
1170 float *Pb
= (p
<3 ? NULL
: g_new(float,w
*h
));
1171 float *Pr
= (p
<3 ? NULL
: g_new(float,w
*h
));
1173 compute_YPbPr(buffer
,Y
,Pb
,Pr
,w
,h
,p
,NULL
);
1174 compute_filter(Y
, w
, h
, denoise_params
.filterY
, NULL
, (p
>2?"Filtering luma...":"Filtering..."));
1175 compute_filter(Pb
, w
, h
, denoise_params
.filterC
, NULL
, "Filtering blue difference...");
1176 compute_filter(Pr
, w
, h
, denoise_params
.filterC
, NULL
, "Filtering red difference...");
1178 compute_mask(buffer
, Y
, Pb
, Pr
, w
, h
, p
, NULL
);