Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
cacorrectrgb.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2021 rawfiner.
4 Copyright (C) 2021, 2023, 2025-2026 Aurélien PIERRE.
5 Copyright (C) 2021 luzpaz.
6 Copyright (C) 2021-2022 Pascal Obry.
7 Copyright (C) 2021 Ralf Brown.
8 Copyright (C) 2022 Diederik Ter Rahe.
9 Copyright (C) 2022 Martin Bařinka.
10 Copyright (C) 2022 Philipp Lutz.
11 Copyright (C) 2022 Sakari Kapanen.
12
13 darktable is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 darktable is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with darktable. If not, see <http://www.gnu.org/licenses/>.
25*/
26
27#ifdef HAVE_CONFIG_H
28#include "config.h"
29#endif
30
31#include "bauhaus/bauhaus.h"
32#include "develop/imageop.h"
33#include "develop/imageop_gui.h"
35#include "gui/gtk.h"
36#include "iop/iop_api.h"
37#include "common/gaussian.h"
39
40#include <gtk/gtk.h>
41#include <stdlib.h>
42
44
45
141{
142 DT_CACORRECT_RGB_R = 0, // $DESCRIPTION: "red"
143 DT_CACORRECT_RGB_G = 1, // $DESCRIPTION: "green"
144 DT_CACORRECT_RGB_B = 2 // $DESCRIPTION: "blue"
146
148{
149 DT_CACORRECT_MODE_STANDARD = 0, // $DESCRIPTION: "standard"
150 DT_CACORRECT_MODE_DARKEN = 1, // $DESCRIPTION: "darken only"
151 DT_CACORRECT_MODE_BRIGHTEN = 2 // $DESCRIPTION: "brighten only"
153
155{
156 dt_iop_cacorrectrgb_guide_channel_t guide_channel; // $DEFAULT: DT_CACORRECT_RGB_G $DESCRIPTION: "guide"
157 float radius; // $MIN: 1 $MAX: 500 $DEFAULT: 5 $DESCRIPTION: "radius"
158 float strength; // $MIN: 0 $MAX: 4 $DEFAULT: 0.5 $DESCRIPTION: "strength"
159 dt_iop_cacorrectrgb_mode_t mode; // $DEFAULT: DT_CACORRECT_MODE_STANDARD $DESCRIPTION: "correction mode"
160 gboolean refine_manifolds; // $MIN: FALSE $MAX: TRUE $DEFAULT: FALSE $DESCRIPTION: "very large chromatic aberration"
162
167
168const char *name()
169{
170 return _("chromatic a_berrations");
171}
172
173const char **description(struct dt_iop_module_t *self)
174{
175 return dt_iop_set_description(self, _("correct chromatic aberrations"),
176 _("corrective"),
177 _("linear, raw, scene-referred"),
178 _("linear, raw"),
179 _("linear, raw, scene-referred"));
180}
181
186
188{
189 return IOP_GROUP_REPAIR;
190}
191
193{
194 return IOP_CS_RGB;
195}
196
198{
199 memcpy(piece->data, p1, self->params_size);
200}
201
203static void normalize_manifolds(const float *const restrict blurred_in, float *const restrict blurred_manifold_lower, float *const restrict blurred_manifold_higher, const size_t width, const size_t height, const dt_iop_cacorrectrgb_guide_channel_t guide)
204{
206 for(size_t k = 0; k < width * height; k++)
207 {
208 const float weighth = fmaxf(blurred_manifold_higher[k * 4 + 3], 1E-2f);
209 const float weightl = fmaxf(blurred_manifold_lower[k * 4 + 3], 1E-2f);
210
211 // normalize guide
212 const float highg = blurred_manifold_higher[k * 4 + guide] / weighth;
213 const float lowg = blurred_manifold_lower[k * 4 + guide] / weightl;
214
215 blurred_manifold_higher[k * 4 + guide] = highg;
216 blurred_manifold_lower[k * 4 + guide] = lowg;
217
218 // normalize and unlog other channels
219 for(size_t kc = 0; kc <= 1; kc++)
220 {
221 const size_t c = (kc + guide + 1) % 3;
222 const float highc = blurred_manifold_higher[k * 4 + c] / weighth;
223 const float lowc = blurred_manifold_lower[k * 4 + c] / weightl;
224 blurred_manifold_higher[k * 4 + c] = exp2f(highc) * highg;
225 blurred_manifold_lower[k * 4 + c] = exp2f(lowc) * lowg;
226 }
227
228 // replace by average if weight is too small
229 if(weighth < 0.05f)
230 {
231 // we make a smooth transition between full manifold at
232 // weighth = 0.05f to full average at weighth = 0.01f
233 const float w = (weighth - 0.01f) / (0.05f - 0.01f);
234 for_each_channel(c,aligned(blurred_manifold_higher,blurred_in))
235 {
236 blurred_manifold_higher[k * 4 + c] = w * blurred_manifold_higher[k * 4 + c]
237 + (1.0f - w) * blurred_in[k * 4 + c];
238 }
239 }
240 if(weightl < 0.05f)
241 {
242 // we make a smooth transition between full manifold at
243 // weightl = 0.05f to full average at weightl = 0.01f
244 const float w = (weightl - 0.01f) / (0.05f - 0.01f);
245 for_each_channel(c,aligned(blurred_manifold_lower,blurred_in))
246 {
247 blurred_manifold_lower[k * 4 + c] = w * blurred_manifold_lower[k * 4 + c]
248 + (1.0f - w) * blurred_in[k * 4 + c];
249 }
250 }
251 }
252}
253
254#define DT_CACORRECTRGB_MAX_EV_DIFF 2.0f
256static int get_manifolds(const float* const restrict in, const size_t width, const size_t height,
257 const float sigma, const float sigma2,
259 float* const restrict manifolds, gboolean refine_manifolds)
260{
261 int err = 0;
262 float *const restrict blurred_in = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
263 float *const restrict manifold_higher = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
264 float *const restrict manifold_lower = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
265 float *const restrict blurred_manifold_higher = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
266 float *const restrict blurred_manifold_lower = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
267
268 if(IS_NULL_PTR(blurred_in) || IS_NULL_PTR(manifold_higher) || IS_NULL_PTR(manifold_lower) ||
269 IS_NULL_PTR(blurred_manifold_higher) || IS_NULL_PTR(blurred_manifold_lower))
270 {
271 err = 1;
272 goto error;
273 }
274
275 dt_aligned_pixel_t max = {INFINITY, INFINITY, INFINITY, INFINITY};
276 dt_aligned_pixel_t min = {-INFINITY, -INFINITY, -INFINITY, 0.0f};
277 // start with a larger blur to estimate the manifolds if we refine them
278 // later on
279 const float blur_size = refine_manifolds ? sigma2 : sigma;
280 dt_gaussian_t *g = dt_gaussian_init(width, height, 4, max, min, blur_size, 0);
281 if(IS_NULL_PTR(g))
282 {
283 err = 1;
284 goto error;
285 }
286 dt_gaussian_blur_4c(g, in, blurred_in);
287
288 // construct the manifolds
289 // higher manifold is the blur of all pixels that are above average,
290 // lower manifold is the blur of all pixels that are below average
291 // we use the guide channel to categorize the pixels as above or below average
293 for(size_t k = 0; k < width * height; k++)
294 {
295 const float pixelg = fmaxf(in[k * 4 + guide], 1E-6f);
296 const float avg = blurred_in[k * 4 + guide];
297 float weighth = (pixelg >= avg);
298 float weightl = (pixelg <= avg);
299 float logdiffs[2];
300 for(size_t kc = 0; kc <= 1; kc++)
301 {
302 const size_t c = (kc + guide + 1) % 3;
303 const float pixel = fmaxf(in[k * 4 + c], 1E-6f);
304 const float log_diff = log2f(pixel / pixelg);
305 logdiffs[kc] = log_diff;
306 }
307 // regularization of logdiff to avoid too many problems with noise:
308 // we lower the weights of pixels with too high logdiff
309 const float maxlogdiff = fmaxf(fabsf(logdiffs[0]), fabsf(logdiffs[1]));
310 if(maxlogdiff > DT_CACORRECTRGB_MAX_EV_DIFF)
311 {
312 const float correction_weight = DT_CACORRECTRGB_MAX_EV_DIFF / maxlogdiff;
313 weightl *= correction_weight;
314 weighth *= correction_weight;
315 }
316 for(size_t kc = 0; kc <= 1; kc++)
317 {
318 const size_t c = (kc + guide + 1) % 3;
319 manifold_higher[k * 4 + c] = logdiffs[kc] * weighth;
320 manifold_lower[k * 4 + c] = logdiffs[kc] * weightl;
321 }
322 manifold_higher[k * 4 + guide] = pixelg * weighth;
323 manifold_lower[k * 4 + guide] = pixelg * weightl;
324 manifold_higher[k * 4 + 3] = weighth;
325 manifold_lower[k * 4 + 3] = weightl;
326 }
327
328 dt_gaussian_blur_4c(g, manifold_higher, blurred_manifold_higher);
329 dt_gaussian_blur_4c(g, manifold_lower, blurred_manifold_lower);
331
332 normalize_manifolds(blurred_in, blurred_manifold_lower, blurred_manifold_higher, width, height, guide);
333
334 // note that manifolds were constructed based on the value and average
335 // of the guide channel ONLY.
336 // this implies that the "higher" manifold in the channel c may be
337 // actually lower than the "lower" manifold of that channel.
338 // This happens in the following example:
339 // guide: 1_____
340 // |_____0
341 // guided: _____1
342 // 0_____|
343 // here the higher manifold of guide is equal to 1, its lower manifold is
344 // equal to 0. The higher manifold of the guided channel is equal to 0
345 // as it is the average of the values where the guide is higher than its
346 // average, and the lower manifold of the guided channel is equal to 1.
347
348 if(refine_manifolds)
349 {
351 if(IS_NULL_PTR(g))
352 {
353 err = 1;
354 goto error;
355 }
356 dt_gaussian_blur_4c(g, in, blurred_in);
357
358 // refine the manifolds
359 // improve result especially on very degraded images
360 // we use a blur of normal size for this step
362 for(size_t k = 0; k < width * height; k++)
363 {
364 // in order to refine the manifolds, we will compute weights
365 // for which all channels will have a contribution.
366 // this will allow to avoid taking too much into account pixels
367 // that have wrong values due to the chromatic aberration
368 //
369 // for example, here:
370 // guide: 1_____
371 // |_____0
372 // guided: 1______
373 // |____0
374 // ^ this pixel makes the estimated lower manifold erroneous
375 // here, the higher and lower manifolds values computed are:
376 // _______|_higher_|________lower_________|
377 // guide | 1 | 0 |
378 // guided | 1 |(1 + 4 * 0) / 5 = 0.2 |
379 //
380 // the lower manifold of the guided is 0.2 if we consider only the guide
381 //
382 // at this step of the algorithm, we know estimates of manifolds
383 //
384 // we can refine the manifolds by computing weights that reduce the influence
385 // of pixels that are probably suffering from chromatic aberrations
386 const float pixelg = log2f(fmaxf(in[k * 4 + guide], 1E-6f));
387 const float highg = log2f(fmaxf(blurred_manifold_higher[k * 4 + guide], 1E-6f));
388 const float lowg = log2f(fmaxf(blurred_manifold_lower[k * 4 + guide], 1E-6f));
389 const float avgg = log2f(fmaxf(blurred_in[k * 4 + guide], 1E-6f));
390
391 float w = 1.0f;
392 for(size_t kc = 0; kc <= 1; kc++)
393 {
394 const size_t c = (guide + kc + 1) % 3;
395 // weight by considering how close pixel is for a manifold,
396 // and how close the log difference between the channels is
397 // close to the wrong log difference between the channels.
398
399 const float pixel = log2f(fmaxf(in[k * 4 + c], 1E-6f));
400 const float highc = log2f(fmaxf(blurred_manifold_higher[k * 4 + c], 1E-6f));
401 const float lowc = log2f(fmaxf(blurred_manifold_lower[k * 4 + c], 1E-6f));
402
403 // find how likely the pixel is part of a chromatic aberration
404 // (lowc, lowg) and (highc, highg) are valid points
405 // (lowc, highg) and (highc, lowg) are chromatic aberrations
406 const float dist_to_ll = fabsf(pixelg - lowg - pixel + lowc);
407 const float dist_to_hh = fabsf(pixelg - highg - pixel + highc);
408 const float dist_to_lh = fabsf((pixelg - pixel) - (highg - lowc));
409 const float dist_to_hl = fabsf((pixelg - pixel) - (lowg - highc));
410
411 float dist_to_good = 1.0f;
412 if(fabsf(pixelg - lowg) < fabsf(pixelg - highg))
413 dist_to_good = dist_to_ll;
414 else
415 dist_to_good = dist_to_hh;
416
417 float dist_to_bad = 1.0f;
418 if(fabsf(pixelg - lowg) < fabsf(pixelg - highg))
419 dist_to_bad = dist_to_hl;
420 else
421 dist_to_bad = dist_to_lh;
422
423 // make w higher if close to good, and smaller if close to bad.
424 w *= 1.0f * (0.2f + 1.0f / fmaxf(dist_to_good, 0.1f)) / (0.2f + 1.0f / fmaxf(dist_to_bad, 0.1f));
425 }
426
427 if(pixelg > avgg)
428 {
429 float logdiffs[2];
430 for(size_t kc = 0; kc <= 1; kc++)
431 {
432 const size_t c = (guide + kc + 1) % 3;
433 const float pixel = fmaxf(in[k * 4 + c], 1E-6f);
434 const float log_diff = log2f(pixel) - pixelg;
435 logdiffs[kc] = log_diff;
436 }
437 // regularization of logdiff to avoid too many problems with noise:
438 // we lower the weights of pixels with too high logdiff
439 const float maxlogdiff = fmaxf(fabsf(logdiffs[0]), fabsf(logdiffs[1]));
440 if(maxlogdiff > DT_CACORRECTRGB_MAX_EV_DIFF)
441 {
442 const float correction_weight = DT_CACORRECTRGB_MAX_EV_DIFF / maxlogdiff;
443 w *= correction_weight;
444 }
445 for(size_t kc = 0; kc <= 1; kc++)
446 {
447 const size_t c = (kc + guide + 1) % 3;
448 manifold_higher[k * 4 + c] = logdiffs[kc] * w;
449 }
450 manifold_higher[k * 4 + guide] = fmaxf(in[k * 4 + guide], 0.0f) * w;
451 manifold_higher[k * 4 + 3] = w;
452 // manifold_lower still contains the values from first iteration
453 // -> reset it.
455 {
456 manifold_lower[k * 4 + c] = 0.0f;
457 }
458 }
459 else
460 {
461 float logdiffs[2];
462 for(size_t kc = 0; kc <= 1; kc++)
463 {
464 const size_t c = (guide + kc + 1) % 3;
465 const float pixel = fmaxf(in[k * 4 + c], 1E-6f);
466 const float log_diff = log2f(pixel) - pixelg;
467 logdiffs[kc] = log_diff;
468 }
469 // regularization of logdiff to avoid too many problems with noise:
470 // we lower the weights of pixels with too high logdiff
471 const float maxlogdiff = fmaxf(fabsf(logdiffs[0]), fabsf(logdiffs[1]));
472 if(maxlogdiff > DT_CACORRECTRGB_MAX_EV_DIFF)
473 {
474 const float correction_weight = DT_CACORRECTRGB_MAX_EV_DIFF / maxlogdiff;
475 w *= correction_weight;
476 }
477 for(size_t kc = 0; kc <= 1; kc++)
478 {
479 const size_t c = (kc + guide + 1) % 3;
480 manifold_lower[k * 4 + c] = logdiffs[kc] * w;
481 }
482 manifold_lower[k * 4 + guide] = fmaxf(in[k * 4 + guide], 0.0f) * w;
483 manifold_lower[k * 4 + 3] = w;
484 // manifold_higher still contains the values from first iteration
485 // -> reset it.
486 for(size_t c = 0; c < 4; c++)
487 {
488 manifold_higher[k * 4 + c] = 0.0f;
489 }
490 }
491 }
492
493 dt_gaussian_blur_4c(g, manifold_higher, blurred_manifold_higher);
494 dt_gaussian_blur_4c(g, manifold_lower, blurred_manifold_lower);
495 normalize_manifolds(blurred_in, blurred_manifold_lower, blurred_manifold_higher, width, height, guide);
497 }
498
499 // store all manifolds in the same structure to make upscaling faster
500 __OMP_PARALLEL_FOR_SIMD__(aligned(manifolds, blurred_manifold_lower, blurred_manifold_higher:64))
501 for(size_t k = 0; k < width * height; k++)
502 {
503 for(size_t c = 0; c < 3; c++)
504 {
505 manifolds[k * 6 + c] = blurred_manifold_higher[k * 4 + c];
506 manifolds[k * 6 + 3 + c] = blurred_manifold_lower[k * 4 + c];
507 }
508 }
509
510error:;
511 dt_pixelpipe_cache_free_align(manifold_lower);
512 dt_pixelpipe_cache_free_align(manifold_higher);
514 dt_pixelpipe_cache_free_align(blurred_manifold_lower);
515 dt_pixelpipe_cache_free_align(blurred_manifold_higher);
516 return err;
517}
518#undef DT_CACORRECTRGB_MAX_EV_DIFF
519
521static void apply_correction(const float* const restrict in,
522 const float* const restrict manifolds,
523 const size_t width, const size_t height, const float sigma,
526 float* const restrict out)
527
528{
530 for(size_t k = 0; k < width * height; k++)
531 {
532 const float high_guide = fmaxf(manifolds[k * 6 + guide], 1E-6f);
533 const float low_guide = fmaxf(manifolds[k * 6 + 3 + guide], 1E-6f);
534 const float log_high = log2f(high_guide);
535 const float log_low = log2f(low_guide);
536 const float dist_low_high = log_high - log_low;
537 const float pixelg = fmaxf(in[k * 4 + guide], 0.0f);
538 const float log_pixg = log2f(fminf(fmaxf(pixelg, low_guide), high_guide));
539
540 // determine how close our pixel is from the low manifold compared to the
541 // high manifold.
542 // if pixel value is lower or equal to the low manifold, weight_low = 1.0f
543 // if pixel value is higher or equal to the high manifold, weight_low = 0.0f
544 float weight_low = fabsf(log_high - log_pixg) / fmaxf(dist_low_high, 1E-6f);
545 // if the manifolds are very close, we are likely to introduce discontinuities
546 // and to have a meaningless "weight_low".
547 // thus in these cases make dist closer to 0.5.
548 // we set a threshold of 0.25f EV min.
549 const float threshold_dist_low_high = 0.25f;
550 if(dist_low_high < threshold_dist_low_high)
551 {
552 const float weight = dist_low_high / threshold_dist_low_high;
553 // dist_low_high = threshold_dist_low_high => dist
554 // dist_low_high = 0.0 => 0.5f
555 weight_low = weight_low * weight + 0.5f * (1.0f - weight);
556 }
557 const float weight_high = fmaxf(1.0f - weight_low, 0.0f);
558
559 for(size_t kc = 0; kc <= 1; kc++)
560 {
561 const size_t c = (guide + kc + 1) % 3;
562 const float pixelc = fmaxf(in[k * 4 + c], 0.0f);
563
564 const float ratio_high_manifolds = manifolds[k * 6 + c] / high_guide;
565 const float ratio_low_manifolds = manifolds[k * 6 + 3 + c] / low_guide;
566 // weighted geometric mean between the ratios.
567 const float ratio = powf(ratio_low_manifolds, weight_low) * powf(ratio_high_manifolds, weight_high);
568
569 const float outp = pixelg * ratio;
570
571 switch(mode)
572 {
574 out[k * 4 + c] = outp;
575 break;
577 out[k * 4 + c] = fminf(outp, pixelc);
578 break;
580 out[k * 4 + c] = fmaxf(outp, pixelc);
581 break;
582 }
583 }
584
585 out[k * 4 + guide] = pixelg;
586 out[k * 4 + 3] = in[k * 4 + 3];
587 }
588}
589
591static int reduce_artifacts(const float* const restrict in,
592 const size_t width, const size_t height, const float sigma,
594 const float safety,
595 float* const restrict out)
596
597{
598 int err = 0;
599 dt_gaussian_t *g = NULL;
600
601 // in_out contains the 2 guided channels of in, and the 2 guided channels of out
602 // it allows to blur all channels in one 4-channel gaussian blur instead of 2
603 float *const restrict DT_ALIGNED_PIXEL in_out = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
604 float *const restrict blurred_in_out = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
605 if(IS_NULL_PTR(blurred_in_out) || IS_NULL_PTR(in_out))
606 {
607 err = 1;
608 goto error;
609 }
611 for(size_t k = 0; k < width * height; k++)
612 {
613 for(size_t kc = 0; kc <= 1; kc++)
614 {
615 const size_t c = (guide + kc + 1) % 3;
616 in_out[k * 4 + kc * 2 + 0] = in[k * 4 + c];
617 in_out[k * 4 + kc * 2 + 1] = out[k * 4 + c];
618 }
619 }
620
621
622 dt_aligned_pixel_t max = { INFINITY, INFINITY, INFINITY, INFINITY };
623 dt_aligned_pixel_t min = {0.0f, 0.0f, 0.0f, 0.0f};
625 if(IS_NULL_PTR(g))
626 {
627 err = 1;
628 goto error;
629 }
630 dt_gaussian_blur_4c(g, in_out, blurred_in_out);
631
632 // we consider that even with chromatic aberration, local average should
633 // be close to be accurate.
634 // thus, the local average of output should be similar to the one of the input
635 // if they are not, the algorithm probably washed out colors too much or
636 // may have produced artifacts.
637 // we do a weighted average between input and output, keeping more input if
638 // the local averages are very different.
639 // we use the same weight for all channels, as using different weights
640 // introduces artifacts in practice.
642 for(size_t k = 0; k < width * height; k++)
643 {
644 float w = 1.0f;
645 for(size_t kc = 0; kc <= 1; kc++)
646 {
647 const float avg_in = log2f(fmaxf(blurred_in_out[k * 4 + kc * 2 + 0], 1E-6f));
648 const float avg_out = log2f(fmaxf(blurred_in_out[k * 4 + kc * 2 + 1], 1E-6f));
649 w *= expf(-fmaxf(fabsf(avg_out - avg_in), 0.01f) * safety);
650 }
651 for(size_t kc = 0; kc <= 1; kc++)
652 {
653 const size_t c = (guide + kc + 1) % 3;
654 out[k * 4 + c] = fmaxf(1.0f - w, 0.0f) * fmaxf(in[k * 4 + c], 0.0f) + w * fmaxf(out[k * 4 + c], 0.0f);
655 }
656 }
657
658error:;
659 dt_pixelpipe_cache_free_align(blurred_in_out);
660 if(g) dt_gaussian_free(g);
662 return err;
663}
664
665static inline __attribute__((always_inline)) int reduce_chromatic_aberrations(const float* const restrict in,
666 const size_t width, const size_t height,
667 const size_t ch, const float sigma, const float sigma2,
670 const gboolean refine_manifolds,
671 const float safety,
672 float* const restrict out)
673
674{
675 int err = 0;
676 const float downsize = fminf(3.0f, sigma);
677 const size_t ds_width = width / downsize;
678 const size_t ds_height = height / downsize;
679 float *const restrict ds_in = dt_pixelpipe_cache_alloc_align_float_cache(ds_width * ds_height * 4, 0);
680 float *const restrict manifolds = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 6, 0);
681
682 // we use only one variable for both higher and lower manifolds in order
683 // to save time by doing only one bilinear interpolation instead of 2.
684 float *const restrict ds_manifolds = dt_pixelpipe_cache_alloc_align_float_cache(ds_width * ds_height * 6, 0);
685 if(IS_NULL_PTR(ds_manifolds) || IS_NULL_PTR(ds_in) || IS_NULL_PTR(manifolds))
686 {
687 err = 1;
688 goto error;
689 }
690
691 // Downsample the image for speed-up
692 interpolate_bilinear(in, width, height, ds_in, ds_width, ds_height, 4);
693
694 // Compute manifolds
695 if(get_manifolds(ds_in, ds_width, ds_height, sigma / downsize, sigma2 / downsize, guide, ds_manifolds, refine_manifolds))
696 {
697 err = 1;
698 goto error;
699 }
700
701 // upscale manifolds
702 interpolate_bilinear(ds_manifolds, ds_width, ds_height, manifolds, width, height, 6);
703 apply_correction(in, manifolds, width, height, sigma, guide, mode, out);
704 if(reduce_artifacts(in, width, height, sigma, guide, safety, out))
705 {
706 err = 1;
707 goto error;
708 }
709
710error:;
713 dt_pixelpipe_cache_free_align(ds_manifolds);
714
715 return err;
716}
717
718int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
719{
720 const dt_iop_roi_t *const roi_in = &piece->roi_in;
721 const dt_iop_roi_t *const roi_out = &piece->roi_out;
722
724 // used to adjuste blur level depending on size. Don't amplify noise if magnified > 100%
725 const float scale = fmaxf(dt_dev_get_module_scale(pipe, roi_in), 1.f);
726 const int ch = piece->dsc_in.channels;
727 const size_t width = roi_out->width;
728 const size_t height = roi_out->height;
729 const float* in = (float*)ivoid;
730 float* out = (float*)ovoid;
731 const float sigma = fmaxf(d->radius / scale, 1.0f);
732 const float sigma2 = fmaxf(d->radius * d->radius / scale, 1.0f);
733
734 // whether to be very conservative in preserving the original image, or to
735 // keep algorithm result even if it overshoots
736 const float safety = powf(20.0f, 1.0f - d->strength);
737 return reduce_chromatic_aberrations(in, width, height, ch, sigma, sigma2, d->guide_channel, d->mode, d->refine_manifolds, safety, out);
738}
739
741{
744
745 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->refine_manifolds), p->refine_manifolds);
746}
747
749{
751
752 d->guide_channel = DT_CACORRECT_RGB_G;
753 d->radius = 5.0f;
754 d->strength = 0.5f;
756 d->refine_manifolds = FALSE;
757
759 if(!IS_NULL_PTR(g))
760 {
761 dt_bauhaus_combobox_set_default(g->guide_channel, d->guide_channel);
762 dt_bauhaus_slider_set_default(g->radius, d->radius);
763 dt_bauhaus_slider_set_soft_range(g->radius, 1.0, 20.0);
764 dt_bauhaus_slider_set_default(g->strength, d->strength);
765 dt_bauhaus_combobox_set_default(g->mode, d->mode);
766 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->refine_manifolds), d->refine_manifolds);
767 }
768}
769
771{
773 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
774 g->guide_channel = dt_bauhaus_combobox_from_params(self, "guide_channel");
775 gtk_widget_set_tooltip_text(g->guide_channel, _("channel used as a reference to\n"
776 "correct the other channels.\n"
777 "use sharpest channel if some\n"
778 "channels are blurry.\n"
779 "try changing guide channel if you\n"
780 "have artifacts."));
781 g->radius = dt_bauhaus_slider_from_params(self, "radius");
782 gtk_widget_set_tooltip_text(g->radius, _("increase for stronger correction"));
783 g->strength = dt_bauhaus_slider_from_params(self, "strength");
784 gtk_widget_set_tooltip_text(g->strength, _("balance between smoothing colors\n"
785 "and preserving them.\n"
786 "high values can lead to overshooting\n"
787 "and edge bleeding."));
788
789 gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_section_label_new(_("advanced parameters")), TRUE, TRUE, 0);
790 g->mode = dt_bauhaus_combobox_from_params(self, "mode");
791 gtk_widget_set_tooltip_text(g->mode, _("correction mode to use.\n"
792 "can help with multiple\n"
793 "instances for very damaged\n"
794 "images.\n"
795 "darken only is particularly\n"
796 "efficient to correct blue\n"
797 "chromatic aberration."));
798 g->refine_manifolds = dt_bauhaus_toggle_from_params(self, "refine_manifolds");
799 gtk_widget_set_tooltip_text(g->refine_manifolds, _("runs an iterative approach\n"
800 "with several radii.\n"
801 "improves result on images\n"
802 "with very large chromatic\n"
803 "aberrations, but can smooth\n"
804 "colors too much on other\n"
805 "images."));
806}
807// clang-format off
808// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
809// vim: shiftwidth=2 expandtab tabstop=2 cindent
810// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
811// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1640
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1551
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
const char ** description(struct dt_iop_module_t *self)
int default_group()
void reload_defaults(dt_iop_module_t *module)
dt_iop_cacorrectrgb_mode_t
@ DT_CACORRECT_MODE_DARKEN
@ DT_CACORRECT_MODE_STANDARD
@ DT_CACORRECT_MODE_BRIGHTEN
dt_iop_cacorrectrgb_guide_channel_t
@ DT_CACORRECT_RGB_B
@ DT_CACORRECT_RGB_R
@ DT_CACORRECT_RGB_G
void gui_update(dt_iop_module_t *self)
static __DT_CLONE_TARGETS__ void apply_correction(const float *const restrict in, const float *const restrict manifolds, const size_t width, const size_t height, const float sigma, const dt_iop_cacorrectrgb_guide_channel_t guide, const dt_iop_cacorrectrgb_mode_t mode, float *const restrict out)
#define DT_CACORRECTRGB_MAX_EV_DIFF
static __DT_CLONE_TARGETS__ void normalize_manifolds(const float *const restrict blurred_in, float *const restrict blurred_manifold_lower, float *const restrict blurred_manifold_higher, const size_t width, const size_t height, const dt_iop_cacorrectrgb_guide_channel_t guide)
const char * name()
void gui_init(dt_iop_module_t *self)
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static __DT_CLONE_TARGETS__ int get_manifolds(const float *const restrict in, const size_t width, const size_t height, const float sigma, const float sigma2, const dt_iop_cacorrectrgb_guide_channel_t guide, float *const restrict manifolds, gboolean refine_manifolds)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
static __DT_CLONE_TARGETS__ int reduce_artifacts(const float *const restrict in, const size_t width, const size_t height, const float sigma, const dt_iop_cacorrectrgb_guide_channel_t guide, const float safety, float *const restrict out)
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_RGB
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
#define DT_ALIGNED_PIXEL
Definition darktable.h:389
#define for_each_channel(_var,...)
Definition darktable.h:662
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define for_four_channels(_var,...)
Definition darktable.h:664
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
#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 darktable.h:281
void dt_iop_params_t
Definition dev_history.h:41
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
static __DT_CLONE_TARGETS__ void interpolate_bilinear(const float *const restrict in, const size_t width_in, const size_t height_in, float *const restrict out, const size_t width_out, const size_t height_out, const size_t ch)
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:330
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:325
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:122
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:451
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
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:3141
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:131
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_GROUP_REPAIR
Definition imageop.h:140
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
float dt_aligned_pixel_t[4]
struct _GtkWidget GtkWidget
Definition splash.h:29
const float sigma
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
unsigned int channels
Definition format.h:54
dt_iop_cacorrectrgb_mode_t mode
dt_iop_cacorrectrgb_guide_channel_t guide_channel
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
int32_t params_size
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72