Hi,
I've designed a despeckling/denoising algorithm to solve issues with
images taken under very difficult lighting conditions, deep sea diving
in my case. It may be useful in other situations too. The reason to post
it is that (after a number of incarnations which have been thrown away
by me) I think this one is going to stay.
Currently the interface has been added to the grayscale page but I think
(if this is an acceptable feature at all) that it should be put in a
separate denoise page. The next step is to add wavelet chroma denoising.
The current wavelet denoising should be put in this page too IMHO.
There are no command-line options yet for this. It's easy but boring
and doubtfully useful to add them because parameter settings are not
expected to be generally applicable.
--
Frank
diff --git a/ufraw.h b/ufraw.h
index 7abc0dd..bfe334e 100644
--- a/ufraw.h
+++ b/ufraw.h
@@ -194,6 +194,9 @@ typedef struct {
lightness_adjustment lightnessAdjustment[max_adjustments];
int grayscaleMode;
double grayscaleMixer[3];
+ double despeckleWindow[3];
+ double despeckleDecay[3];
+ double despecklePasses[3];
/* SAVE options */
char inputFilename[max_path], outputFilename[max_path],
@@ -292,6 +295,7 @@ typedef struct ufraw_struct {
int hotpixels;
gboolean mark_hotpixels;
unsigned raw_multiplier;
+ int channel_select;
} ufraw_data;
extern const conf_data conf_default;
@@ -331,6 +335,7 @@ void ufraw_invalidate_layer(ufraw_data *uf, UFRawPhase
phase);
void ufraw_invalidate_hotpixel_layer(ufraw_data *uf);
void ufraw_invalidate_denoise_layer(ufraw_data *uf);
void ufraw_invalidate_darkframe_layer(ufraw_data *uf);
+void ufraw_invalidate_despeckle_layer(ufraw_data *uf);
void ufraw_invalidate_whitebalance_layer(ufraw_data *uf);
void ufraw_invalidate_smoothing_layer(ufraw_data *uf);
gboolean ufraw_invalidate_layer_event(ufraw_data *uf, UFRawPhase phase);
diff --git a/ufraw_conf.c b/ufraw_conf.c
index 327b4d8..d9b85ea 100644
--- a/ufraw_conf.c
+++ b/ufraw_conf.c
@@ -87,6 +87,9 @@ const conf_data conf_default = {
}, /* lightness adjustments */
grayscale_none, /* grayscale mode */
{ 1.0, 1.0, 1.0 }, /* grayscale mixer */
+ { 0.0, 0.0, 0.0 }, /* despeckle window */
+ { 0.0, 0.0, 0.0 }, /* despeckle color decay */
+ { 1.0, 1.0, 1.0 }, /* despeckle passes */
/* Save options */
"", "", "", /* inputFilename, outputFilename, outputPath */
"", "", /* inputURI, inputModTime */
@@ -670,6 +673,18 @@ static void conf_parse_text(GMarkupParseContext *context,
const gchar *text,
sscanf(temp, "%lf %lf %lf", &c->grayscaleMixer[0],
&c->grayscaleMixer[1], &c->grayscaleMixer[2]);
}
+ if ( strcmp("DespeckleWindow", element)==0 ) {
+ sscanf(temp, "%lf %lf %lf", &c->despeckleWindow[0],
+ &c->despeckleWindow[1], &c->despeckleWindow[2]);
+ }
+ if ( strcmp("DespeckleDecay", element)==0 ) {
+ sscanf(temp, "%lf %lf %lf", &c->despeckleDecay[0],
+ &c->despeckleDecay[1], &c->despeckleDecay[2]);
+ }
+ if ( strcmp("DespecklePasses", element)==0 ) {
+ sscanf(temp, "%lf %lf %lf", &c->despecklePasses[0],
+ &c->despecklePasses[1], &c->despecklePasses[2]);
+ }
/* OutputIntent replaces Intent starting from ufraw-0.12. */
if ( strcmp("OutputIntent", element)==0 )
c->intent[out_profile] = conf_find_name(temp, intentNames,
@@ -972,6 +987,24 @@ int conf_save(conf_data *c, char *IDFilename, char
**confBuffer)
c->grayscaleMixer[1],
c->grayscaleMixer[2]);
}
+ if (c->despeckleWindow[0] != conf_default.despeckleWindow[0] ||
+ c->despeckleWindow[1] != conf_default.despeckleWindow[1] ||
+ c->despeckleWindow[2] != conf_default.despeckleWindow[2]) {
+ buf = uf_markup_buf(buf, "<DespeckleWindow>%f %f
%f</DespeckleWindow>\n",
+ c->despeckleWindow[0], c->despeckleWindow[1],
c->despeckleWindow[2]);
+ }
+ if (c->despeckleDecay[0] != conf_default.despeckleDecay[0] ||
+ c->despeckleDecay[1] != conf_default.despeckleDecay[1] ||
+ c->despeckleDecay[2] != conf_default.despeckleDecay[2]) {
+ buf = uf_markup_buf(buf, "<DespeckleDecay>%f %f %f</DespeckleDecay>\n",
+ c->despeckleDecay[0], c->despeckleDecay[1],
c->despeckleDecay[2]);
+ }
+ if (c->despecklePasses[0] != conf_default.despecklePasses[0] ||
+ c->despecklePasses[1] != conf_default.despecklePasses[1] ||
+ c->despecklePasses[2] != conf_default.despecklePasses[2]) {
+ buf = uf_markup_buf(buf, "<DespecklePasses>%f %f
%f</DespecklePasses>\n",
+ c->despecklePasses[0], c->despecklePasses[1],
c->despecklePasses[2]);
+ }
if (c->size!=conf_default.size)
buf = uf_markup_buf(buf, "<Size>%d</Size>\n", c->size);
if (c->shrink!=conf_default.shrink)
@@ -1216,6 +1249,9 @@ void conf_copy_image(conf_data *dst, const conf_data *src)
dst->grayscaleMode = src->grayscaleMode;
memcpy(dst->grayscaleMixer, src->grayscaleMixer,
sizeof dst->grayscaleMixer);
+ memcpy(dst->despeckleWindow, src->despeckleWindow, sizeof
(dst->despeckleWindow));
+ memcpy(dst->despeckleDecay, src->despeckleDecay, sizeof
(dst->despeckleDecay));
+ memcpy(dst->despecklePasses, src->despecklePasses, sizeof
(dst->despecklePasses));
g_strlcpy(dst->darkframeFile, src->darkframeFile, max_path);
/* We only copy the current BaseCurve */
if (src->BaseCurveIndex<=camera_curve) {
diff --git a/ufraw_preview.c b/ufraw_preview.c
index fcf1589..8ed1c10 100644
--- a/ufraw_preview.c
+++ b/ufraw_preview.c
@@ -1366,6 +1366,7 @@ static void widget_set_hue(GtkWidget *widget, double hue)
}
static void update_shrink_ranges(preview_data *data);
+static void despeckle_update_sensitive(preview_data *data);
/* update the UI entries that could have changed automatically */
static void update_scales(preview_data *data)
@@ -1453,6 +1454,14 @@ static void update_scales(preview_data *data)
for (i = 0; i < 3; ++i)
gtk_adjustment_set_value(data->GrayscaleMixers[i],
CFG->grayscaleMixer[i]);
+ for (i = 0; i < 3; ++i) {
+ gtk_adjustment_set_value(data->DespeckleWindowAdj[i],
+ CFG->despeckleWindow[i]);
+ gtk_adjustment_set_value(data->DespeckleDecayAdj[i],
+ CFG->despeckleDecay[i]);
+ gtk_adjustment_set_value(data->DespecklePassesAdj[i],
+ CFG->despecklePasses[i]);
+ }
for (i = 0; i < CFG->lightnessAdjustmentCount; ++i) {
gtk_adjustment_set_value(data->LightnessAdjustment[i],
CFG->lightnessAdjustment[i].adjustment);
@@ -1465,6 +1474,7 @@ static void update_scales(preview_data *data)
|| (CFG->grayscaleMixer[2] != conf_default.grayscaleMixer[2]));
gtk_widget_set_sensitive(GTK_WIDGET(data->GrayscaleMixerTable),
CFG->grayscaleMode == grayscale_mixer);
+ despeckle_update_sensitive(data);
for (max = 1, i = 0; i < 3; ++i)
max = MAX(max, CFG->grayscaleMixer[i]);
@@ -2602,6 +2612,15 @@ static void button_update(GtkWidget *button, gpointer
user_data)
CFG->grayscaleMixer[1] = conf_default.grayscaleMixer[1];
CFG->grayscaleMixer[2] = conf_default.grayscaleMixer[2];
}
+ if (button==data->ResetDespeckleButton) {
+ memcpy(CFG->despeckleWindow, conf_default.despeckleWindow,
+ sizeof (CFG->despeckleWindow));
+ memcpy(CFG->despeckleDecay, conf_default.despeckleDecay,
+ sizeof (CFG->despeckleDecay));
+ memcpy(CFG->despecklePasses, conf_default.despecklePasses,
+ sizeof (CFG->despecklePasses));
+ ufraw_invalidate_despeckle_layer(data->UF);
+ }
for (i = 0; i < max_adjustments; ++i) {
if (button == data->ResetLightnessAdjustmentButton[i]) {
CFG->lightnessAdjustment[i].adjustment = 1.0;
@@ -2700,6 +2719,25 @@ static void toggle_button_update(GtkToggleButton
*button, gboolean *valuep)
ufraw_invalidate_layer(data->UF, ufraw_develop_phase);
update_scales(data);
}
+ } else if (valuep==(void*)data->ChannelSelectButton) {
+ if (data->UF->channel_select >= -1) {
+ int i, b = 0;
+ while (data->ChannelSelectButton[b] != button)
+ ++b;
+ if (gtk_toggle_button_get_active(button)) {
+ /* ignore generated events, for render_preview() */
+ data->UF->channel_select = -2;
+ for (i = 0; i < data->UF->colors; ++i)
+ if (i != b)
+ gtk_toggle_button_set_active(
+ data->ChannelSelectButton[i], FALSE);
+ data->UF->channel_select = b;
+ } else {
+ data->UF->channel_select = -1;
+ }
+ ufraw_invalidate_layer(data->UF, ufraw_develop_phase);
+ render_preview(data);
+ }
} else {
*valuep = gtk_toggle_button_get_active(button);
if ( valuep==&CFG->overExp || valuep==&CFG->underExp ) {
@@ -2751,6 +2789,98 @@ static void adjustment_update_int(GtkAdjustment *adj,
int *valuep)
}
}
+static void despeckle_update_sensitive(preview_data *data)
+{
+ conf_data *c = CFG;
+ gboolean b;
+ int i;
+
+ b = FALSE;
+ for (i = 0; i < 3; ++i) {
+ b |= fabs(c->despeckleWindow[i] - conf_default.despeckleWindow[i]) >
0.1;
+ b |= fabs(c->despeckleDecay[i] - conf_default.despeckleDecay[i]) >
0.001;
+ b |= fabs(c->despecklePasses[i] - conf_default.despecklePasses[i]) >
0.1;
+ }
+ gtk_widget_set_sensitive(GTK_WIDGET(data->ResetDespeckleButton), b);
+ b = FALSE;
+ for (i = 1; i < 3; ++i) {
+ b |= c->despeckleWindow[0] != c->despeckleWindow[i];
+ b |= c->despeckleDecay[0] != c->despeckleDecay[i];
+ b |= c->despecklePasses[0] != c->despecklePasses[i];
+ }
+ gtk_widget_set_sensitive(GTK_WIDGET(data->DespeckleLockChannelsButton),
!b);
+}
+
+/*
+ * passes > window makes no sense. Either the number of passes is
+ * ridiculously large or the same effect can be achieved by replacing
+ * decay by pow(decay, original_passes).
+ */
+static void despeckle_apply_constraints(preview_data *data,
+ GtkAdjustment **adjp, int ch)
+{
+ conf_data *c = CFG;
+ double value = gtk_adjustment_get_value(adjp[ch]);
+
+ ++data->FreezeDialog;
+ if (adjp == data->DespeckleWindowAdj &&
+ value < c->despecklePasses[ch] && value) {
+ c->despecklePasses[ch] = value;
+ gtk_adjustment_set_value(data->DespecklePassesAdj[ch], value);
+ }
+ if (adjp == data->DespecklePassesAdj &&
+ value > c->despeckleWindow[ch] && c->despeckleWindow[ch]) {
+ c->despeckleWindow[ch] = value;
+ gtk_adjustment_set_value(data->DespeckleWindowAdj[ch], value);
+ }
+ --data->FreezeDialog;
+}
+
+static gboolean despeckle_adjustment_update(preview_data *data, double *p)
+{
+ conf_data *c = CFG;
+ int i, j, match;
+ GtkAdjustment **adjp = NULL;
+
+ match = 0;
+ for (i = 0; i < 3; ++i) {
+ if (p == &c->despeckleWindow[i]) {
+ adjp = data->DespeckleWindowAdj;
+ match = c->despecklePasses[i] ? 1 : -1;
+ break;
+ }
+ if (p == &c->despeckleDecay[i]) {
+ adjp = data->DespeckleDecayAdj;
+ match = c->despeckleWindow[i] && c->despecklePasses[i] ? 1 : -1;
+ break;
+ }
+ if (p == &c->despecklePasses[i]) {
+ adjp = data->DespecklePassesAdj;
+ match = c->despeckleWindow[i] ? 1 : -1;
+ break;
+ }
+ }
+ if (match > 0)
+ despeckle_apply_constraints(data, adjp, i);
+ if (match) {
+ if (gtk_toggle_button_get_active(data->DespeckleLockChannelsButton)) {
+ ++data->FreezeDialog;
+ for (j = 0; j < 3; ++j) {
+ p[j - i] = *p;
+ gtk_adjustment_set_value(adjp[j], *p);
+ despeckle_apply_constraints(data, adjp, j);
+ }
+ --data->FreezeDialog;
+ }
+ despeckle_update_sensitive(data);
+ }
+ if (match > 0) {
+ ufraw_invalidate_despeckle_layer(data->UF);
+ render_preview(data);
+ }
+ return match ? TRUE : FALSE;
+}
+
static void adjustment_update(GtkAdjustment *adj, double *valuep)
{
preview_data *data = get_preview_data(adj);
@@ -2804,6 +2934,8 @@ static void adjustment_update(GtkAdjustment *adj, double
*valuep)
ufraw_invalidate_denoise_layer(data->UF);
} else if (valuep==&CFG->hotpixel) {
ufraw_invalidate_hotpixel_layer(data->UF);
+ } else if (despeckle_adjustment_update(data, valuep)) {
+ return;
} else {
if (CFG->autoExposure==enabled_state) CFG->autoExposure = apply_state;
if (CFG->autoBlack==enabled_state) CFG->autoBlack = apply_state;
@@ -4368,6 +4500,104 @@ static void grayscale_fill_interface(preview_data *data,
/* End of Grayscale page */
}
+static void denoise_fill_interface(preview_data *data, GtkWidget *page)
+{
+ GtkWidget *button, *label, *icon;
+ GtkTable *table;
+ GtkBox *box;
+ int i;
+
+ table = GTK_TABLE(table_with_frame(page, NULL, TRUE));
+ icon = gtk_image_new_from_stock("gtk-help", GTK_ICON_SIZE_BUTTON);
+ gtk_table_attach(table, icon, 9, 10, 0, 1, 0, 0, 0, 0);
+ uf_widget_set_tooltip(icon, _(
+ "Despeckling is mainly useful when combining a high ISO number "
+ "with a high channel multiplier: when one channel has a very bad "
+ "signal to noise ratio. Try setting window size, color decay and "
+ "number of passes to 50,0,5 for that channel. When a channel "
+ "contains only noise then try 1,0.6,1.\n"
+ "Despeckling is off when window size or passes equals zero. When "
+ "on then window size cannot be smaller than the number of
passes."));
+
+ /* buttons on the right */
+ box = GTK_BOX(gtk_vbox_new(FALSE, 0));
+ button = gtk_toggle_button_new();
+ gtk_container_add(GTK_CONTAINER(button), gtk_image_new_from_stock(
+ "object-lock", GTK_ICON_SIZE_BUTTON));
+ data->DespeckleLockChannelsButton = GTK_TOGGLE_BUTTON(button);
+ gtk_box_pack_start(box, button, FALSE, FALSE, 0);
+ gtk_table_attach(table, GTK_WIDGET(box), 9, 10, 1, 4, GTK_FILL, 0, 0, 0);
+ uf_widget_set_tooltip(button, _("Update channel parameters together"));
+ data->ResetDespeckleButton = reset_button(_("Reset despeckle parameters"),
+ G_CALLBACK(button_update), NULL);
+ gtk_box_pack_start(box, data->ResetDespeckleButton, FALSE, FALSE, 0);
+
+ /* channel to view */
+ label = gtk_label_new(_("View channel:"));
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach(table, label, 0, 6, 0, 1, GTK_FILL|GTK_EXPAND, 0, 0, 0);
+ for (i = 0; i < data->UF->colors; ++i) {
+ button = gtk_toggle_button_new();
+ gtk_container_add(GTK_CONTAINER(button), gtk_image_new_from_stock(
+ i==0 ? "channel-red" : i==1 ? "channel-green" : "channel-blue",
+ GTK_ICON_SIZE_BUTTON));
+ data->ChannelSelectButton[i] = GTK_TOGGLE_BUTTON(button);
+ g_signal_connect(G_OBJECT(button), "toggled",
+ G_CALLBACK(toggle_button_update), data->ChannelSelectButton);
+ gtk_table_attach(table, button, 6 + i, 6 + i + 1, 0, 1, 0, 0, 0, 0);
+ }
+
+ /* Parameters */
+ label = gtk_label_new(_("Window size:"));
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach(table, label, 0, 6, 1, 2, GTK_FILL|GTK_EXPAND, 0, 0, 0);
+ for (i = 0; i < data->UF->colors; ++i) {
+ data->DespeckleWindowAdj[i] = GTK_ADJUSTMENT(
+ gtk_adjustment_new(CFG->despeckleWindow[i],
+ 0.0, 999.0, 1.0, 1.0, 0));
+ g_object_set_data(G_OBJECT(data->DespeckleWindowAdj[i]),
+ "Adjustment-Accuracy", (gpointer)0);
+ button = gtk_spin_button_new(data->DespeckleWindowAdj[i], 1.0, 0);
+ g_object_set_data(G_OBJECT(data->DespeckleWindowAdj[i]),
+ "Parent-Widget", button);
+ g_signal_connect(G_OBJECT(data->DespeckleWindowAdj[i]), "value-changed",
+ G_CALLBACK(adjustment_update), &CFG->despeckleWindow[i]);
+ gtk_table_attach(table, button, 6 + i, 6 + i + 1, 1, 2, GTK_FILL, 0, 0,
0);
+ }
+ label = gtk_label_new(_("Color decay:"));
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach(table, label, 0, 6, 2, 3, GTK_FILL|GTK_EXPAND, 0, 0, 0);
+ for (i = 0; i < data->UF->colors; ++i) {
+ data->DespeckleDecayAdj[i] = GTK_ADJUSTMENT(
+ gtk_adjustment_new(CFG->despeckleDecay[i],
+ 0.0, 1.0, 0.1, 0.1, 0));
+ g_object_set_data(G_OBJECT(data->DespeckleDecayAdj[i]),
+ "Adjustment-Accuracy", (gpointer)2);
+ button = gtk_spin_button_new(data->DespeckleDecayAdj[i], 1.0, 2);
+ g_object_set_data(G_OBJECT(data->DespeckleDecayAdj[i]),
+ "Parent-Widget", button);
+ g_signal_connect(G_OBJECT(data->DespeckleDecayAdj[i]), "value-changed",
+ G_CALLBACK(adjustment_update), &CFG->despeckleDecay[i]);
+ gtk_table_attach(table, button, 6 + i, 6 + i + 1, 2, 3, GTK_FILL, 0, 0,
0);
+ }
+ label = gtk_label_new(_("Passes:"));
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach(table, label, 0, 6, 3, 4, GTK_FILL|GTK_EXPAND, 0, 0, 0);
+ for (i = 0; i < data->UF->colors; ++i) {
+ data->DespecklePassesAdj[i] = GTK_ADJUSTMENT(
+ gtk_adjustment_new(CFG->despecklePasses[i],
+ 0.0, 99.0, 1.0, 1.0, 0));
+ g_object_set_data(G_OBJECT(data->DespecklePassesAdj[i]),
+ "Adjustment-Accuracy", (gpointer)0);
+ button = gtk_spin_button_new(data->DespecklePassesAdj[i], 1.0, 0);
+ g_object_set_data(G_OBJECT(data->DespecklePassesAdj[i]),
+ "Parent-Widget", button);
+ g_signal_connect(G_OBJECT(data->DespecklePassesAdj[i]), "value-changed",
+ G_CALLBACK(adjustment_update), &CFG->despecklePasses[i]);
+ gtk_table_attach(table, button, 6 + i, 6 + i + 1, 3, 4, GTK_FILL, 0, 0,
0);
+ }
+}
+
static void basecurve_fill_interface(preview_data *data, GtkWidget *page,
int curveeditorHeight)
{
@@ -5254,6 +5484,9 @@ int ufraw_preview(ufraw_data *uf, conf_data *rc, int
plugin,
data->PageNumGray = gtk_notebook_page_num(notebook, page);
grayscale_fill_interface(data, page);
+ // page = notebook_page_new(notebook, _("Denoising"), "denoise");
+ denoise_fill_interface(data, page);
+
#ifdef HAVE_LENSFUN
/* Lens correction page */
page = notebook_page_new(notebook, _("Lens correction"), "lens");
diff --git a/ufraw_ufraw.c b/ufraw_ufraw.c
index 3564f5d..64d6449 100644
--- a/ufraw_ufraw.c
+++ b/ufraw_ufraw.c
@@ -251,6 +252,7 @@ ufraw_data *ufraw_open(char *filename)
uf->modifier = NULL;
uf->lanczos_func = NULL;
#endif
+ uf->channel_select = -1;
ufraw_message(UFRAW_SET_LOG, "ufraw_open: w:%d h:%d curvesize:%d\n",
raw->width, raw->height, raw->toneCurveSize);
@@ -734,6 +736,7 @@ void ufraw_developer_prepare(ufraw_data *uf, DeveloperMode
mode)
int ufraw_convert_image(ufraw_data *uf)
{
uf->mark_hotpixels = FALSE;
+ uf->channel_select = -1;
ufraw_developer_prepare(uf, file_developer);
ufraw_convert_image_raw(uf, ufraw_raw_phase);
ufraw_convert_image_first(uf, ufraw_first_phase);
@@ -977,6 +980,117 @@ static void ufraw_shave_hotpixels(ufraw_data *uf,
dcraw_image_type *img,
uf->hotpixels = count;
}
+static void ufraw_despeckle_line(guint16 *base, int step, int size, int window,
+ double decay, int colors, int c)
+{
+ unsigned lum[size];
+ int i, j, start, end, next, v, cold, hot, coldj, hotj, fix;
+ guint16 *p;
+
+ if (colors == 4) {
+ for (i = 0; i < size; ++i) {
+ p = base + i * step;
+ lum[i] = (p[0] + p[1] + p[2] + p[3] - p[c]) / 3;
+ }
+ } else {
+ for (i = 0; i < size; ++i) {
+ p = base + i * step;
+ lum[i] = (p[0] + p[1] + p[2] - p[c]) / 2;
+ }
+ }
+ p = base + c;
+ for (i = 1 - window; i < size; i = next) {
+ start = i;
+ end = i + window;
+ if (start < 0)
+ start = 0;
+ if (end > size)
+ end = size;
+ cold = hot = p[start * step] - lum[start];
+ coldj = hotj = start;
+ for (j = start + 1; j < end; ++j) {
+ v = p[j * step] - lum[j];
+ if (v < cold) {
+ cold = v;
+ coldj = j;
+ }
+ else if (v > hot) {
+ hot = v;
+ hotj = j;
+ }
+ }
+ if (cold < 0 && hot > 0) {
+ fix = -cold;
+ if (fix > hot)
+ fix = hot;
+ p[coldj * step] += fix;
+ p[hotj * step] -= fix;
+ hot -= fix;
+ }
+ if (hot > 0 && decay)
+ p[hotj * step] -= hot * decay;
+ next = coldj < hotj ? coldj : hotj;
+ if (next == start)
+ ++next;
+ }
+}
+
+void ufraw_despeckle(ufraw_data *uf, UFRawPhase phase)
+{
+ ufraw_image_data *img = &uf->Images[phase];
+ const int depth = img->depth / 2, rowstride = img->rowstride / 2;
+ int passes[4], pass, maxpass;
+ int win[4], i, c, colors;
+ guint16 *base;
+ double decay[4];
+
+ ufraw_image_format(&colors, NULL, img, "68", G_STRFUNC);
+ maxpass = 0;
+ for (c = 0; c < colors; ++c) {
+ win[c] = uf->conf->despeckleWindow[c < 3 ? c : 1] + 0.01;
+ decay[c] = uf->conf->despeckleDecay[c < 3 ? c : 1];
+ passes[c] = uf->conf->despecklePasses[c < 3 ? c : 1] + 0.01;
+ if (!win[c])
+ passes[c] = 0;
+ if (passes[c] > maxpass)
+ maxpass = passes[c];
+ }
+ for (pass = maxpass - 1; pass >= 0; --pass) {
+ for (c = 0; c < colors; ++c) {
+ if (pass >= passes[c])
+ continue;
+#ifdef _OPENMP
+#pragma omp parallel for default(shared) private(i,base)
+#endif
+ for (i = 0; i < img->height; ++i) {
+ base = (guint16 *)img->buffer + i * rowstride;
+ ufraw_despeckle_line(base, depth, img->width, win[c],
+ decay[c], colors, c);
+ }
+#ifdef _OPENMP
+#pragma omp parallel for default(shared) private(i,base)
+#endif
+ for (i = 0; i < img->width; ++i) {
+ base = (guint16 *)img->buffer + i * depth;
+ ufraw_despeckle_line(base, rowstride, img->height, win[c],
+ decay[c], colors, c);
+ }
+ }
+ }
+}
+
+static gboolean ufraw_despeckle_active(ufraw_data *uf)
+{
+ int i;
+ gboolean active = FALSE;
+
+ for (i = 0; i < 3; ++i) {
+ if (uf->conf->despeckleWindow[i] && uf->conf->despecklePasses[i])
+ active = TRUE;
+ }
+ return active;
+}
+
static void ufraw_convertshrink(ufraw_data *uf, dcraw_image_data *final,
dcraw_data *raw)
{
int scale = 1;
@@ -1048,6 +1162,7 @@ void ufraw_convert_image_raw(ufraw_data *uf, UFRawPhase
phase)
dcraw_wavelet_denoise(raw, uf->conf->threshold * sqrt(uf->raw_multiplier));
dcraw_finalize_raw(raw, dark, uf->developer->rgbWB);
raw->raw.image = rawimage;
+ ufraw_despeckle(uf, phase);
}
/*
@@ -1473,6 +1588,14 @@ ufraw_image_data *ufraw_convert_image_area(ufraw_data
*uf, unsigned saidx,
for (yy = 0; yy < area.height; yy++, dest += out->rowstride,
src += in->rowstride) {
develop(dest, (void *)src, uf->developer, 8, area.width);
+ if (uf->channel_select >= 0) {
+ int xx;
+ guint8 *p = dest;
+ for (xx = 0; xx < area.width; xx++, p += out->depth) {
+ guint8 px = p[uf->channel_select];
+ p[0] = p[1] = p[2] = px;
+ }
+ }
}
break;
@@ -1666,6 +1789,11 @@ void ufraw_invalidate_darkframe_layer(ufraw_data *uf)
ufraw_invalidate_layer(uf, ufraw_raw_phase);
}
+void ufraw_invalidate_despeckle_layer(ufraw_data *uf)
+{
+ ufraw_invalidate_layer(uf, ufraw_raw_phase);
+}
+
/*
* This one is special. The raw layer applies WB in preparation for optimal
* interpolation but the first layer undoes it for develop() et.al. So, the
@@ -1679,6 +1807,9 @@ void ufraw_invalidate_whitebalance_layer(ufraw_data *uf)
uf->Images[ufraw_raw_phase].valid = 0;
uf->Images[ufraw_raw_phase].invalidate_event = TRUE;
// }
+ /* Despeckling is sensitive for WB changes because it is nonlinear. */
+ if (ufraw_despeckle_active(uf))
+ ufraw_invalidate_despeckle_layer(uf);
}
/*
diff --git a/ufraw_ui.h b/ufraw_ui.h
index f82a0b4..dd238ce 100644
--- a/ufraw_ui.h
+++ b/ufraw_ui.h
@@ -71,6 +71,7 @@ typedef struct {
GtkWidget *ResetContrastButton;
GtkWidget *ResetBlackButton, *ResetBaseCurveButton, *ResetCurveButton;
GtkWidget *ResetGrayscaleChannelMixerButton;
+ GtkWidget *ResetDespeckleButton;
GtkWidget *SaveButton;
GtkWidget *ControlButton[num_buttons];
guint16 ButtonMnemonic[num_buttons];
@@ -111,6 +112,11 @@ typedef struct {
GtkAdjustment *RotationAdjustment;
GtkWidget *ResetRotationAdjustment;
GtkAdjustment *GrayscaleMixers[3];
+ GtkAdjustment *DespeckleWindowAdj[3];
+ GtkAdjustment *DespeckleDecayAdj[3];
+ GtkAdjustment *DespecklePassesAdj[3];
+ GtkToggleButton *DespeckleLockChannelsButton;
+ GtkToggleButton *ChannelSelectButton[3];
#ifdef HAVE_LENSFUN
/* The GtkEntry with camera maker/model name */
GtkWidget *CameraModel;
------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day
trial. Simplify your report design, integration and deployment - and focus on
what you do best, core application coding. Discover what's new with
Crystal Reports now. http://p.sf.net/sfu/bobj-july
_______________________________________________
ufraw-devel mailing list
ufraw-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ufraw-devel