Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
defringe.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2014 Dennis Gnad.
4 Copyright (C) 2014-2016 Roman Lebedev.
5 Copyright (C) 2014, 2016, 2019 Tobias Ellinghaus.
6 Copyright (C) 2015 Pedro Côrte-Real.
7 Copyright (C) 2016 johannes hanika.
8 Copyright (C) 2017 Heiko Bauke.
9 Copyright (C) 2017-2019 luzpaz.
10 Copyright (C) 2017 Ulrich Pegelow.
11 Copyright (C) 2018, 2020, 2023, 2025-2026 Aurélien PIERRE.
12 Copyright (C) 2018 Edgardo Hoszowski.
13 Copyright (C) 2018 Maurizio Paglia.
14 Copyright (C) 2018-2022 Pascal Obry.
15 Copyright (C) 2018 rawfiner.
16 Copyright (C) 2019 Andreas Schneider.
17 Copyright (C) 2019 Diederik ter Rahe.
18 Copyright (C) 2020 Aldric Renaudin.
19 Copyright (C) 2020 Chris Elston.
20 Copyright (C) 2020 Diederik Ter Rahe.
21 Copyright (C) 2020-2021 Hubert Kowalski.
22 Copyright (C) 2020-2021 Ralf Brown.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Philipp Lutz.
25
26 darktable is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 darktable is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with darktable. If not, see <http://www.gnu.org/licenses/>.
38*/
39#ifdef HAVE_CONFIG_H
40#include "config.h"
41#endif
42#include "widgets/bauhaus.h"
43#include "system/macros.h"
44#include "system/mem_alloc.h"
46#include "system/openmp.h"
48#include "pixel/gaussian.h"
49#include "common/imagebuf.h"
50#include "develop/develop.h"
51#include "develop/imageop.h"
53#include "develop/imageop_gui.h"
54
55#include "iop/iop_api.h"
56#include <gtk/gtk.h>
57#include <stdlib.h>
58
60
62{
63 MODE_GLOBAL_AVERAGE = 0, // $DESCRIPTION: "global average (fast)"
64 MODE_LOCAL_AVERAGE = 1, // $DESCRIPTION: "local average (slow)"
65 MODE_STATIC = 2 // $DESCRIPTION: "static threshold (fast)"
67
69{
70 float radius; // $MIN: 0.5 $MAX: 20.0 $DEFAULT: 4.0 $DESCRIPTION: "edge detection radius"
71 float thresh; // $MIN: 0.5 $MAX: 128.0 $DEFAULT: 20.0 $DESCRIPTION: "threshold"
72 dt_iop_defringe_mode_t op_mode; // $DEFAULT: MODE_GLOBAL_AVERAGE $DESCRIPTION: "operation mode"
74
76
83
84// would be nice to be able to precompute this only once for an image
85// typedef struct dt_iop_defringe_global_data_t
86//{
87// float avg_edge_chroma;
88//}
89// dt_iop_defringe_global_data_t;
90
91
92const char *name()
93{
94 return _("defringe");
95}
96
97const char *aliases()
98{
99 return _("chromatic aberrations");
100}
101
102const char **description(struct dt_iop_module_t *self)
103{
104 return dt_iop_set_description(self, _("attenuate chromatic aberration by desaturating edges"),
105 _("corrective"),
106 _("linear or non-linear, Lab, display-referred"),
107 _("non-linear, Lab"),
108 _("non-linear, Lab, display-referred"));
109}
110
112{
113 return IOP_GROUP_REPAIR;
114}
115
116int flags()
117{
118 // a second instance might help to reduce artifacts when thick fringe needs to be removed
120}
121
122const char *deprecated_msg()
123{
124 return _("this module is deprecated. please use the chromatic aberration module instead.");
125}
126
128{
129 return IOP_CS_LAB;
130}
131
132// Verify before actually using this
133/*
134void tiling_callback (dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_in,
135const dt_iop_roi_t *roi_out, dt_develop_tiling_t *tiling)
136{
137 dt_iop_defringe_data_t *p = (dt_iop_defringe_data_t *)piece->data;
138
139 const int width = roi_in->width;
140 const int height = roi_in->height;
141 const int channels = piece->dsc_in.channels;
142 const size_t basebuffer = width*height*channels*sizeof(float);
143
144 tiling->factor = 2.0f + (float)dt_gaussian_memory_use(width, height, channels)/basebuffer;
145#ifdef HAVE_OPENCL
146 tiling->factor_cl = 2.0f + (float)dt_gaussian_memory_use_cl(width, height, channels)/basebuffer;
147#endif
148 tiling->maxbuf = fmax(1.0f, (float)dt_gaussian_singlebuffer_size(width, height, channels)/basebuffer);
149 tiling->overhead = 0;
150 tiling->overlap = p->window;
151 tiling->xalign = 1;
152 tiling->yalign = 1;
153 return;
154}
155*/
156
157// fibonacci lattice to select surrounding pixels for different cases
158static const float fib[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 };
159// 0,1,2,3,4,5,6, 7, 8, 9,10,11, 12, 13
160
161static inline void fib_latt(int *const x, int *const y, float radius, int step, int idx)
162{
163 // idx < 1 because division by zero is also a problem in the following line
164 if(idx >= sizeof(fib) / sizeof(float) - 1 || idx < 1)
165 {
166 *x = 0;
167 *y = 0;
168 fprintf(stderr, "Fibonacci lattice index wrong/out of bounds in: defringe module\n");
169 return;
170 }
171 float px = step / fib[idx], py = step * (fib[idx + 1] / fib[idx]);
172 py -= (int)py;
173 float dx = px * radius, dy = py * radius;
174 *x = round(dx - radius / 2.0);
175 *y = round(dy - radius / 2.0);
176}
177
178#define MAGIC_THRESHOLD_COEFF 33.0
179
180// the basis of how the following algorithm works comes from rawtherapee (http://rawtherapee.com/)
181// defringe -- thanks to Emil Martinec <ejmartin@uchicago.edu> for that
182// quite some modifications were done though:
183// 1. use a fibonacci lattice instead of full window, to speed things up
184// 2. option for local averaging or static (RT used the global/region one)
185// 3. additional condition to reduce sharp edged artifacts, by blurring pixels near pixels over threshold,
186// this really helps improving the filter with thick fringes
187// -----------------------------------------------------------------------------------------
188// in the following you will also see some more "magic numbers",
189// most are chosen arbitrarily and/or by experiment/trial+error ... I am sorry ;-)
190// and having everything user-defineable would be just too much
191// -----------------------------------------------------------------------------------------
192
194{
196 piece->data_size = sizeof(dt_iop_defringe_data_t);
197}
198
200{
201 dt_free_align(piece->data);
202 piece->data = NULL;
203}
204
206int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
207 const void *const i,
208 void *const o)
209{
210 (void)pipe;
211 const dt_iop_roi_t *const roi_in = &piece->roi_in;
212 const dt_iop_roi_t *const roi_out = &piece->roi_out;
214
215 const int order = 1; // 0,1,2
216 int err = 0;
217 const float sigma = fmax(0.1f, fabs(d->radius)) * roi_in->scale;
218 const float Labmax[] = { 100.0f, 128.0f, 128.0f, 1.0f };
219 const float Labmin[] = { 0.0f, -128.0f, -128.0f, 0.0f };
220 const int ch = 4;
221 const int radius = ceil(2.0 * ceilf(sigma));
222
223 // save the fibonacci lattices in them later
224 int *xy_avg = NULL;
225 int *xy_small = NULL;
226
227 if(roi_out->width < 2 * radius + 1 || roi_out->height < 2 * radius + 1) goto ERROR_EXIT;
228
229 float avg_edge_chroma = 0.0;
230
231 const float *const restrict in = (float *const)i;
232 float *const restrict out = (float *const)o;
233 const int width = roi_in->width;
234 const int height = roi_in->height;
235
236 dt_gaussian_t *gauss = NULL;
237 gauss = dt_gaussian_init(width, height, 4, Labmax, Labmin, sigma, order);
238 if(IS_NULL_PTR(gauss))
239 {
240 fprintf(stderr, "Error allocating memory for gaussian blur in: defringe module\n");
241 err = 1;
242 goto ERROR_EXIT;
243 }
244 dt_gaussian_blur_4c(gauss, in, out);
245 dt_gaussian_free(gauss);
246
247 const int samples_wish = radius * radius;
248 int sampleidx_avg;
249 // select samples by fibonacci number
250 if(samples_wish > 89)
251 {
252 sampleidx_avg = 12; // 144 samples
253 }
254 else if(samples_wish > 55)
255 {
256 sampleidx_avg = 11; // 89 samples
257 }
258 else if(samples_wish > 34)
259 {
260 sampleidx_avg = 10; // ..you get the idea
261 }
262 else if(samples_wish > 21)
263 {
264 sampleidx_avg = 9;
265 }
266 else if(samples_wish > 13)
267 {
268 sampleidx_avg = 8;
269 }
270 else
271 { // don't use less than 13 samples
272 sampleidx_avg = 7;
273 }
274 const int sampleidx_small = sampleidx_avg - 1;
275
276 const int small_radius = MAX(radius, 3);
277 const int avg_radius = 24 + radius * 4;
278
279 const int samples_small = fib[sampleidx_small];
280 const int samples_avg = fib[sampleidx_avg];
281
282 // Pre-Compute Fibonacci Lattices
283
284 xy_avg = malloc(sizeof(int) * 2 * samples_avg);
285 xy_small = malloc(sizeof(int) * 2 * samples_small);
286 if(IS_NULL_PTR(xy_avg) || IS_NULL_PTR(xy_small))
287 {
288 fprintf(stderr, "Error allocating memory for fibonacci lattice in: defringe module\n");
289 err = 1;
290 goto ERROR_EXIT;
291 }
292
293 // precompute all required fibonacci lattices:
294 for(int u = 0; u < samples_avg; u++)
295 {
296 int dx, dy;
297 fib_latt(&dx, &dy, avg_radius, u, sampleidx_avg);
298 xy_avg[2*u] = dx;
299 xy_avg[2*u+1] = dy;
300 }
301 for(int u = 0; u < samples_small; u++)
302 {
303 int dx, dy;
304 fib_latt(&dx, &dy, small_radius, u, sampleidx_small);
305 xy_small[2*u] = dx;
306 xy_small[2*u+1] = dy;
307 }
308
309 const float use_global_average = MODE_GLOBAL_AVERAGE == d->op_mode;
310 __OMP_PARALLEL_FOR_SIMD__(reduction(+ : avg_edge_chroma))
311 for(size_t j = 0; j < (size_t)height * width * 4; j += 4)
312 {
313 // edge-detect on color channels
314 // method: difference of original to gaussian blurred image:
315 const float a = in[j + 1] - out[j + 1];
316 const float b = in[j + 2] - out[j + 2];
317 const float edge = (a * a + b * b); // range up to 2*(256)^2 -> approx. 0 to 131072
318
319 // save local edge chroma in out[.. +3] , this is later compared with threshold
320 out[j + 3] = edge;
321 // the average chroma of the edge-layer in the roi
322 avg_edge_chroma += edge * use_global_average;
323 }
324
325 float thresh;
326 if(use_global_average)
327 {
328 avg_edge_chroma = avg_edge_chroma / (width * height) + 10.0 * FLT_EPSILON;
329 thresh = fmax(0.1f, 4.0 * d->thresh * avg_edge_chroma / MAGIC_THRESHOLD_COEFF);
330 }
331 else
332 {
333 // this fixed value will later be changed when doing local averaging, or kept as-is in "static" mode
334 avg_edge_chroma = MAGIC_THRESHOLD_COEFF;
335 thresh = fmax(0.1f, d->thresh);
336 }
337
338#ifdef _OPENMP
339// dynamically/guided scheduled due to possible uneven edge-chroma distribution (thanks to rawtherapee code
340// for this hint!)
341#pragma omp parallel for default(firstprivate) \
342 schedule(dynamic,3)
343#endif
344 for(int v = 0; v < height; v++)
345 {
346 const size_t row_above = (size_t)MAX(0, (v-1)) * width * ch;
347 const size_t curr_row = (size_t)v * width * ch;
348 const size_t row_below = (size_t)MIN((height-1), (v+1)) * width * ch;
349 for(int t = 0; t < width; t++)
350 {
351 const size_t index = ch * ((size_t)v * width + t);
352 float local_thresh = thresh;
353 // think of compiler setting "-funswitch-loops" to maybe improve these things:
354 if(MODE_LOCAL_AVERAGE == d->op_mode && out[index + 3] > thresh)
355 {
356 float local_avg = 0.0;
357 // use some and not all values from the neighbourhood to speed things up:
358 for(int u = 0; u < samples_avg; u++)
359 {
360 const int dx = xy_avg[2*u];
361 const int dy = xy_avg[2*u+1];
362 const int x = CLAMP(t + dx, 0, width - 1);
363 const int y = CLAMP(v + dy, 0, height - 1);
364 local_avg += out[((size_t)y * width + x) * ch + 3];
365 }
366 avg_edge_chroma = fmax(0.01f, (float)local_avg / samples_avg);
367 local_thresh = fmax(0.1f, 4.0 * d->thresh * avg_edge_chroma / MAGIC_THRESHOLD_COEFF);
368 }
369
370 if(out[index + 3] > local_thresh
371 // reduces artifacts ("region growing by 1 pixel"):
372 || out[row_above + MAX(0, (t - 1)) * ch + 3] > local_thresh
373 || out[row_above + t * ch + 3] > local_thresh
374 || out[row_above + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh
375 || out[curr_row + MAX(0, (t - 1)) * ch + 3] > local_thresh
376 || out[curr_row + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh
377 || out[row_below + MAX(0, (t - 1)) * ch + 3] > local_thresh
378 || out[row_below + t * ch + 3] > local_thresh
379 || out[row_below + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh)
380 {
381 float atot = 0, btot = 0;
382 float norm = 0;
383 float weight;
384 // it seems better to use only some pixels from a larger window instead of all pixels from a smaller
385 // window
386 // we use a fibonacci lattice for that, samples amount need to be a fibonacci number, this can then be
387 // scaled to
388 // a certain radius
389
390 // use some neighbourhood pixels for lowest chroma average
391 for(int u = 0; u < samples_small; u++)
392 {
393 const int dx = xy_small[2*u];
394 const int dy = xy_small[2*u+1];
395 const int x = CLAMP(t + dx, 0, width - 1);
396 const int y = CLAMP(v + dy, 0, height - 1);
397 const size_t idx = ch * ((size_t)y * width + x);
398 // inverse chroma weighted average of neighbouring pixels inside window
399 // also taking average edge chromaticity into account (either global or local average)
400 weight = 1.0 / (out[idx + 3] + avg_edge_chroma);
401 atot += weight * in[idx + 1];
402 btot += weight * in[idx + 2];
403 norm += weight;
404 }
405 // here we could try using a "balance" between original and changed value, this could be used to
406 // reduce artifacts
407 // but on first tries, results weren't very convincing, and there are blend settings available anyway
408 // in dt
409 // float balance = (out[v*width*ch +t*ch +3]-thresh)/out[v*width*ch +t*ch +3];
410 double a = (atot / norm); // *balance + in[v*width*ch + t*ch +1]*(1.0-balance);
411 double b = (btot / norm); // *balance + in[v*width*ch + t*ch +2]*(1.0-balance);
412 out[index] = in[index];
413 out[index + 1] = a;
414 out[index + 2] = b;
415 }
416 else
417 {
418 __OMP_SIMD__(aligned(in, out))
419 // we can't copy the alpha channel here because it contains info needed by neighboring pixels!
420 for(int c = 0; c < 3; c++)
421 {
422 out[index+c] = in[index+c];
423 }
424 }
425 }
426 }
427
429 dt_iop_alpha_copy(i, o, roi_out->width, roi_out->height);
430
431 goto FINISH_PROCESS;
432
433ERROR_EXIT:
434 dt_iop_image_copy_by_size(o, i, roi_out->width, roi_out->height, ch);
435
436FINISH_PROCESS:
437 dt_free(xy_small);
438 dt_free(xy_avg);
439 return err;
440}
441
443{
445
446 g->mode_select = dt_bauhaus_combobox_from_params(self, "op_mode");
447 gtk_widget_set_tooltip_text(g->mode_select,
448 _("method for color protection:\n - global average: fast, might show slightly wrong previews in high "
449 "magnification; might sometimes protect saturation too much or too low in comparison to local "
450 "average\n - local average: slower, might protect saturation better than global average by using "
451 "near pixels as color reference, so it can still allow for more desaturation where required\n - "
452 "static: fast, only uses the threshold as a static limit"));
453
454 g->radius_scale = dt_bauhaus_slider_from_params(self, "radius");
455 gtk_widget_set_tooltip_text(g->radius_scale, _("radius for detecting fringe"));
456
457 g->thresh_scale = dt_bauhaus_slider_from_params(self, "thresh");
458 gtk_widget_set_tooltip_text(g->thresh_scale, _("threshold for defringe, higher values mean less defringing"));
459}
460
462{
465 dt_bauhaus_combobox_set(g->mode_select, p->op_mode);
466 dt_bauhaus_slider_set(g->radius_scale, p->radius);
467 dt_bauhaus_slider_set(g->thresh_scale, p->thresh);
468}
469
470// clang-format off
471// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
472// vim: shiftwidth=2 expandtab tabstop=2 cindent
473// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
474// clang-format on
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_LAB
static const float x
const int t
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
const char ** description(struct dt_iop_module_t *self)
Definition defringe.c:102
int default_group()
Definition defringe.c:111
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o)
Definition defringe.c:206
static void fib_latt(int *const x, int *const y, float radius, int step, int idx)
Definition defringe.c:161
dt_iop_defringe_mode_t
Definition defringe.c:62
@ MODE_LOCAL_AVERAGE
Definition defringe.c:64
@ MODE_GLOBAL_AVERAGE
Definition defringe.c:63
@ MODE_STATIC
Definition defringe.c:65
const char * aliases()
Definition defringe.c:97
void gui_update(dt_iop_module_t *module)
Definition defringe.c:461
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition defringe.c:193
dt_iop_defringe_params_t dt_iop_defringe_data_t
Definition defringe.c:75
const char * name()
Definition defringe.c:92
void gui_init(dt_iop_module_t *self)
Definition defringe.c:442
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition defringe.c:127
int flags()
Definition defringe.c:116
static const float fib[]
Definition defringe.c:158
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition defringe.c:199
const char * deprecated_msg()
Definition defringe.c:122
#define MAGIC_THRESHOLD_COEFF
Definition defringe.c:178
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:335
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:330
dt_gaussian_t * dt_gaussian_init(const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:127
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_GROUP_REPAIR
Definition imageop.h:159
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
const float sigma
struct dt_iop_module_t *void * data
dt_iop_defringe_mode_t op_mode
Definition defringe.c:72
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29