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"
30#endif
31
32#include "widgets/bauhaus.h"
35#include "develop/imageop.h"
36#include "develop/imageop_gui.h"
37#include "iop/iop_api.h"
38#include "pixel/gaussian.h"
40
41#include <gtk/gtk.h>
42#include <stdlib.h>
43#include "widgets/label.h"
44
46
47
143{
144 DT_CACORRECT_RGB_R = 0, // $DESCRIPTION: "red"
145 DT_CACORRECT_RGB_G = 1, // $DESCRIPTION: "green"
146 DT_CACORRECT_RGB_B = 2 // $DESCRIPTION: "blue"
148
150{
151 DT_CACORRECT_MODE_STANDARD = 0, // $DESCRIPTION: "standard"
152 DT_CACORRECT_MODE_DARKEN = 1, // $DESCRIPTION: "darken only"
153 DT_CACORRECT_MODE_BRIGHTEN = 2 // $DESCRIPTION: "brighten only"
155
157{
158 dt_iop_cacorrectrgb_guide_channel_t guide_channel; // $DEFAULT: DT_CACORRECT_RGB_G $DESCRIPTION: "guide"
159 float radius; // $MIN: 1 $MAX: 500 $DEFAULT: 5 $DESCRIPTION: "radius"
160 float strength; // $MIN: 0 $MAX: 4 $DEFAULT: 0.5 $DESCRIPTION: "strength"
161 dt_iop_cacorrectrgb_mode_t mode; // $DEFAULT: DT_CACORRECT_MODE_STANDARD $DESCRIPTION: "correction mode"
162 gboolean refine_manifolds; // $MIN: FALSE $MAX: TRUE $DEFAULT: FALSE $DESCRIPTION: "very large chromatic aberration"
164
169
170const char *name()
171{
172 return _("chromatic a_berrations");
173}
174
175const char **description(struct dt_iop_module_t *self)
176{
177 return dt_iop_set_description(self, _("correct chromatic aberrations"),
178 _("corrective"),
179 _("linear, raw, scene-referred"),
180 _("linear, raw"),
181 _("linear, raw, scene-referred"));
182}
183
188
190{
191 return IOP_GROUP_REPAIR;
192}
193
195{
196 return IOP_CS_RGB;
197}
198
200{
201 memcpy(piece->data, p1, self->params_size);
202}
203
205static 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)
206{
208 for(size_t k = 0; k < width * height; k++)
209 {
210 const float weighth = fmaxf(blurred_manifold_higher[k * 4 + 3], 1E-2f);
211 const float weightl = fmaxf(blurred_manifold_lower[k * 4 + 3], 1E-2f);
212
213 // normalize guide
214 const float highg = blurred_manifold_higher[k * 4 + guide] / weighth;
215 const float lowg = blurred_manifold_lower[k * 4 + guide] / weightl;
216
217 blurred_manifold_higher[k * 4 + guide] = highg;
218 blurred_manifold_lower[k * 4 + guide] = lowg;
219
220 // normalize and unlog other channels
221 for(size_t kc = 0; kc <= 1; kc++)
222 {
223 const size_t c = (kc + guide + 1) % 3;
224 const float highc = blurred_manifold_higher[k * 4 + c] / weighth;
225 const float lowc = blurred_manifold_lower[k * 4 + c] / weightl;
226 blurred_manifold_higher[k * 4 + c] = exp2f(highc) * highg;
227 blurred_manifold_lower[k * 4 + c] = exp2f(lowc) * lowg;
228 }
229
230 // replace by average if weight is too small
231 if(weighth < 0.05f)
232 {
233 // we make a smooth transition between full manifold at
234 // weighth = 0.05f to full average at weighth = 0.01f
235 const float w = (weighth - 0.01f) / (0.05f - 0.01f);
236 for_each_channel(c,aligned(blurred_manifold_higher,blurred_in))
237 {
238 blurred_manifold_higher[k * 4 + c] = w * blurred_manifold_higher[k * 4 + c]
239 + (1.0f - w) * blurred_in[k * 4 + c];
240 }
241 }
242 if(weightl < 0.05f)
243 {
244 // we make a smooth transition between full manifold at
245 // weightl = 0.05f to full average at weightl = 0.01f
246 const float w = (weightl - 0.01f) / (0.05f - 0.01f);
247 for_each_channel(c,aligned(blurred_manifold_lower,blurred_in))
248 {
249 blurred_manifold_lower[k * 4 + c] = w * blurred_manifold_lower[k * 4 + c]
250 + (1.0f - w) * blurred_in[k * 4 + c];
251 }
252 }
253 }
254}
255
256#define DT_CACORRECTRGB_MAX_EV_DIFF 2.0f
258static int get_manifolds(const float* const restrict in, const size_t width, const size_t height,
259 const float sigma, const float sigma2,
261 float* const restrict manifolds, gboolean refine_manifolds)
262{
263 int err = 0;
264 float *const restrict blurred_in = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
265 float *const restrict manifold_higher = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
266 float *const restrict manifold_lower = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
267 float *const restrict blurred_manifold_higher = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
268 float *const restrict blurred_manifold_lower = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
269
270 if(IS_NULL_PTR(blurred_in) || IS_NULL_PTR(manifold_higher) || IS_NULL_PTR(manifold_lower) ||
271 IS_NULL_PTR(blurred_manifold_higher) || IS_NULL_PTR(blurred_manifold_lower))
272 {
273 err = 1;
274 goto error;
275 }
276
277 dt_aligned_pixel_t max = {INFINITY, INFINITY, INFINITY, INFINITY};
278 dt_aligned_pixel_t min = {-INFINITY, -INFINITY, -INFINITY, 0.0f};
279 // start with a larger blur to estimate the manifolds if we refine them
280 // later on
281 const float blur_size = refine_manifolds ? sigma2 : sigma;
282 dt_gaussian_t *g = dt_gaussian_init(width, height, 4, max, min, blur_size, 0);
283 if(IS_NULL_PTR(g))
284 {
285 err = 1;
286 goto error;
287 }
288 dt_gaussian_blur_4c(g, in, blurred_in);
289
290 // construct the manifolds
291 // higher manifold is the blur of all pixels that are above average,
292 // lower manifold is the blur of all pixels that are below average
293 // we use the guide channel to categorize the pixels as above or below average
295 for(size_t k = 0; k < width * height; k++)
296 {
297 const float pixelg = fmaxf(in[k * 4 + guide], 1E-6f);
298 const float avg = blurred_in[k * 4 + guide];
299 float weighth = (pixelg >= avg);
300 float weightl = (pixelg <= avg);
301 float logdiffs[2];
302 for(size_t kc = 0; kc <= 1; kc++)
303 {
304 const size_t c = (kc + guide + 1) % 3;
305 const float pixel = fmaxf(in[k * 4 + c], 1E-6f);
306 const float log_diff = log2f(pixel / pixelg);
307 logdiffs[kc] = log_diff;
308 }
309 // regularization of logdiff to avoid too many problems with noise:
310 // we lower the weights of pixels with too high logdiff
311 const float maxlogdiff = fmaxf(fabsf(logdiffs[0]), fabsf(logdiffs[1]));
312 if(maxlogdiff > DT_CACORRECTRGB_MAX_EV_DIFF)
313 {
314 const float correction_weight = DT_CACORRECTRGB_MAX_EV_DIFF / maxlogdiff;
315 weightl *= correction_weight;
316 weighth *= correction_weight;
317 }
318 for(size_t kc = 0; kc <= 1; kc++)
319 {
320 const size_t c = (kc + guide + 1) % 3;
321 manifold_higher[k * 4 + c] = logdiffs[kc] * weighth;
322 manifold_lower[k * 4 + c] = logdiffs[kc] * weightl;
323 }
324 manifold_higher[k * 4 + guide] = pixelg * weighth;
325 manifold_lower[k * 4 + guide] = pixelg * weightl;
326 manifold_higher[k * 4 + 3] = weighth;
327 manifold_lower[k * 4 + 3] = weightl;
328 }
329
330 dt_gaussian_blur_4c(g, manifold_higher, blurred_manifold_higher);
331 dt_gaussian_blur_4c(g, manifold_lower, blurred_manifold_lower);
333
334 normalize_manifolds(blurred_in, blurred_manifold_lower, blurred_manifold_higher, width, height, guide);
335
336 // note that manifolds were constructed based on the value and average
337 // of the guide channel ONLY.
338 // this implies that the "higher" manifold in the channel c may be
339 // actually lower than the "lower" manifold of that channel.
340 // This happens in the following example:
341 // guide: 1_____
342 // |_____0
343 // guided: _____1
344 // 0_____|
345 // here the higher manifold of guide is equal to 1, its lower manifold is
346 // equal to 0. The higher manifold of the guided channel is equal to 0
347 // as it is the average of the values where the guide is higher than its
348 // average, and the lower manifold of the guided channel is equal to 1.
349
350 if(refine_manifolds)
351 {
353 if(IS_NULL_PTR(g))
354 {
355 err = 1;
356 goto error;
357 }
358 dt_gaussian_blur_4c(g, in, blurred_in);
359
360 // refine the manifolds
361 // improve result especially on very degraded images
362 // we use a blur of normal size for this step
364 for(size_t k = 0; k < width * height; k++)
365 {
366 // in order to refine the manifolds, we will compute weights
367 // for which all channels will have a contribution.
368 // this will allow to avoid taking too much into account pixels
369 // that have wrong values due to the chromatic aberration
370 //
371 // for example, here:
372 // guide: 1_____
373 // |_____0
374 // guided: 1______
375 // |____0
376 // ^ this pixel makes the estimated lower manifold erroneous
377 // here, the higher and lower manifolds values computed are:
378 // _______|_higher_|________lower_________|
379 // guide | 1 | 0 |
380 // guided | 1 |(1 + 4 * 0) / 5 = 0.2 |
381 //
382 // the lower manifold of the guided is 0.2 if we consider only the guide
383 //
384 // at this step of the algorithm, we know estimates of manifolds
385 //
386 // we can refine the manifolds by computing weights that reduce the influence
387 // of pixels that are probably suffering from chromatic aberrations
388 const float pixelg = log2f(fmaxf(in[k * 4 + guide], 1E-6f));
389 const float highg = log2f(fmaxf(blurred_manifold_higher[k * 4 + guide], 1E-6f));
390 const float lowg = log2f(fmaxf(blurred_manifold_lower[k * 4 + guide], 1E-6f));
391 const float avgg = log2f(fmaxf(blurred_in[k * 4 + guide], 1E-6f));
392
393 float w = 1.0f;
394 for(size_t kc = 0; kc <= 1; kc++)
395 {
396 const size_t c = (guide + kc + 1) % 3;
397 // weight by considering how close pixel is for a manifold,
398 // and how close the log difference between the channels is
399 // close to the wrong log difference between the channels.
400
401 const float pixel = log2f(fmaxf(in[k * 4 + c], 1E-6f));
402 const float highc = log2f(fmaxf(blurred_manifold_higher[k * 4 + c], 1E-6f));
403 const float lowc = log2f(fmaxf(blurred_manifold_lower[k * 4 + c], 1E-6f));
404
405 // find how likely the pixel is part of a chromatic aberration
406 // (lowc, lowg) and (highc, highg) are valid points
407 // (lowc, highg) and (highc, lowg) are chromatic aberrations
408 const float dist_to_ll = fabsf(pixelg - lowg - pixel + lowc);
409 const float dist_to_hh = fabsf(pixelg - highg - pixel + highc);
410 const float dist_to_lh = fabsf((pixelg - pixel) - (highg - lowc));
411 const float dist_to_hl = fabsf((pixelg - pixel) - (lowg - highc));
412
413 float dist_to_good = 1.0f;
414 if(fabsf(pixelg - lowg) < fabsf(pixelg - highg))
415 dist_to_good = dist_to_ll;
416 else
417 dist_to_good = dist_to_hh;
418
419 float dist_to_bad = 1.0f;
420 if(fabsf(pixelg - lowg) < fabsf(pixelg - highg))
421 dist_to_bad = dist_to_hl;
422 else
423 dist_to_bad = dist_to_lh;
424
425 // make w higher if close to good, and smaller if close to bad.
426 w *= 1.0f * (0.2f + 1.0f / fmaxf(dist_to_good, 0.1f)) / (0.2f + 1.0f / fmaxf(dist_to_bad, 0.1f));
427 }
428
429 if(pixelg > avgg)
430 {
431 float logdiffs[2];
432 for(size_t kc = 0; kc <= 1; kc++)
433 {
434 const size_t c = (guide + kc + 1) % 3;
435 const float pixel = fmaxf(in[k * 4 + c], 1E-6f);
436 const float log_diff = log2f(pixel) - pixelg;
437 logdiffs[kc] = log_diff;
438 }
439 // regularization of logdiff to avoid too many problems with noise:
440 // we lower the weights of pixels with too high logdiff
441 const float maxlogdiff = fmaxf(fabsf(logdiffs[0]), fabsf(logdiffs[1]));
442 if(maxlogdiff > DT_CACORRECTRGB_MAX_EV_DIFF)
443 {
444 const float correction_weight = DT_CACORRECTRGB_MAX_EV_DIFF / maxlogdiff;
445 w *= correction_weight;
446 }
447 for(size_t kc = 0; kc <= 1; kc++)
448 {
449 const size_t c = (kc + guide + 1) % 3;
450 manifold_higher[k * 4 + c] = logdiffs[kc] * w;
451 }
452 manifold_higher[k * 4 + guide] = fmaxf(in[k * 4 + guide], 0.0f) * w;
453 manifold_higher[k * 4 + 3] = w;
454 // manifold_lower still contains the values from first iteration
455 // -> reset it.
457 {
458 manifold_lower[k * 4 + c] = 0.0f;
459 }
460 }
461 else
462 {
463 float logdiffs[2];
464 for(size_t kc = 0; kc <= 1; kc++)
465 {
466 const size_t c = (guide + kc + 1) % 3;
467 const float pixel = fmaxf(in[k * 4 + c], 1E-6f);
468 const float log_diff = log2f(pixel) - pixelg;
469 logdiffs[kc] = log_diff;
470 }
471 // regularization of logdiff to avoid too many problems with noise:
472 // we lower the weights of pixels with too high logdiff
473 const float maxlogdiff = fmaxf(fabsf(logdiffs[0]), fabsf(logdiffs[1]));
474 if(maxlogdiff > DT_CACORRECTRGB_MAX_EV_DIFF)
475 {
476 const float correction_weight = DT_CACORRECTRGB_MAX_EV_DIFF / maxlogdiff;
477 w *= correction_weight;
478 }
479 for(size_t kc = 0; kc <= 1; kc++)
480 {
481 const size_t c = (kc + guide + 1) % 3;
482 manifold_lower[k * 4 + c] = logdiffs[kc] * w;
483 }
484 manifold_lower[k * 4 + guide] = fmaxf(in[k * 4 + guide], 0.0f) * w;
485 manifold_lower[k * 4 + 3] = w;
486 // manifold_higher still contains the values from first iteration
487 // -> reset it.
488 for(size_t c = 0; c < 4; c++)
489 {
490 manifold_higher[k * 4 + c] = 0.0f;
491 }
492 }
493 }
494
495 dt_gaussian_blur_4c(g, manifold_higher, blurred_manifold_higher);
496 dt_gaussian_blur_4c(g, manifold_lower, blurred_manifold_lower);
497 normalize_manifolds(blurred_in, blurred_manifold_lower, blurred_manifold_higher, width, height, guide);
499 }
500
501 // store all manifolds in the same structure to make upscaling faster
502 __OMP_PARALLEL_FOR_SIMD__(aligned(manifolds, blurred_manifold_lower, blurred_manifold_higher:64))
503 for(size_t k = 0; k < width * height; k++)
504 {
505 for(size_t c = 0; c < 3; c++)
506 {
507 manifolds[k * 6 + c] = blurred_manifold_higher[k * 4 + c];
508 manifolds[k * 6 + 3 + c] = blurred_manifold_lower[k * 4 + c];
509 }
510 }
511
512error:;
513 dt_pixelpipe_cache_free_align(manifold_lower);
514 dt_pixelpipe_cache_free_align(manifold_higher);
516 dt_pixelpipe_cache_free_align(blurred_manifold_lower);
517 dt_pixelpipe_cache_free_align(blurred_manifold_higher);
518 return err;
519}
520#undef DT_CACORRECTRGB_MAX_EV_DIFF
521
523static void apply_correction(const float* const restrict in,
524 const float* const restrict manifolds,
525 const size_t width, const size_t height, const float sigma,
528 float* const restrict out)
529
530{
532 for(size_t k = 0; k < width * height; k++)
533 {
534 const float high_guide = fmaxf(manifolds[k * 6 + guide], 1E-6f);
535 const float low_guide = fmaxf(manifolds[k * 6 + 3 + guide], 1E-6f);
536 const float log_high = log2f(high_guide);
537 const float log_low = log2f(low_guide);
538 const float dist_low_high = log_high - log_low;
539 const float pixelg = fmaxf(in[k * 4 + guide], 0.0f);
540 const float log_pixg = log2f(fminf(fmaxf(pixelg, low_guide), high_guide));
541
542 // determine how close our pixel is from the low manifold compared to the
543 // high manifold.
544 // if pixel value is lower or equal to the low manifold, weight_low = 1.0f
545 // if pixel value is higher or equal to the high manifold, weight_low = 0.0f
546 float weight_low = fabsf(log_high - log_pixg) / fmaxf(dist_low_high, 1E-6f);
547 // if the manifolds are very close, we are likely to introduce discontinuities
548 // and to have a meaningless "weight_low".
549 // thus in these cases make dist closer to 0.5.
550 // we set a threshold of 0.25f EV min.
551 const float threshold_dist_low_high = 0.25f;
552 if(dist_low_high < threshold_dist_low_high)
553 {
554 const float weight = dist_low_high / threshold_dist_low_high;
555 // dist_low_high = threshold_dist_low_high => dist
556 // dist_low_high = 0.0 => 0.5f
557 weight_low = weight_low * weight + 0.5f * (1.0f - weight);
558 }
559 const float weight_high = fmaxf(1.0f - weight_low, 0.0f);
560
561 for(size_t kc = 0; kc <= 1; kc++)
562 {
563 const size_t c = (guide + kc + 1) % 3;
564 const float pixelc = fmaxf(in[k * 4 + c], 0.0f);
565
566 const float ratio_high_manifolds = manifolds[k * 6 + c] / high_guide;
567 const float ratio_low_manifolds = manifolds[k * 6 + 3 + c] / low_guide;
568 // weighted geometric mean between the ratios.
569 const float ratio = powf(ratio_low_manifolds, weight_low) * powf(ratio_high_manifolds, weight_high);
570
571 const float outp = pixelg * ratio;
572
573 switch(mode)
574 {
576 out[k * 4 + c] = outp;
577 break;
579 out[k * 4 + c] = fminf(outp, pixelc);
580 break;
582 out[k * 4 + c] = fmaxf(outp, pixelc);
583 break;
584 }
585 }
586
587 out[k * 4 + guide] = pixelg;
588 out[k * 4 + 3] = in[k * 4 + 3];
589 }
590}
591
593static int reduce_artifacts(const float* const restrict in,
594 const size_t width, const size_t height, const float sigma,
596 const float safety,
597 float* const restrict out)
598
599{
600 int err = 0;
601 dt_gaussian_t *g = NULL;
602
603 // in_out contains the 2 guided channels of in, and the 2 guided channels of out
604 // it allows to blur all channels in one 4-channel gaussian blur instead of 2
605 float *const restrict DT_ALIGNED_PIXEL in_out = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
606 float *const restrict blurred_in_out = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 4, 0);
607 if(IS_NULL_PTR(blurred_in_out) || IS_NULL_PTR(in_out))
608 {
609 err = 1;
610 goto error;
611 }
613 for(size_t k = 0; k < width * height; k++)
614 {
615 for(size_t kc = 0; kc <= 1; kc++)
616 {
617 const size_t c = (guide + kc + 1) % 3;
618 in_out[k * 4 + kc * 2 + 0] = in[k * 4 + c];
619 in_out[k * 4 + kc * 2 + 1] = out[k * 4 + c];
620 }
621 }
622
623
624 dt_aligned_pixel_t max = { INFINITY, INFINITY, INFINITY, INFINITY };
625 dt_aligned_pixel_t min = {0.0f, 0.0f, 0.0f, 0.0f};
627 if(IS_NULL_PTR(g))
628 {
629 err = 1;
630 goto error;
631 }
632 dt_gaussian_blur_4c(g, in_out, blurred_in_out);
633
634 // we consider that even with chromatic aberration, local average should
635 // be close to be accurate.
636 // thus, the local average of output should be similar to the one of the input
637 // if they are not, the algorithm probably washed out colors too much or
638 // may have produced artifacts.
639 // we do a weighted average between input and output, keeping more input if
640 // the local averages are very different.
641 // we use the same weight for all channels, as using different weights
642 // introduces artifacts in practice.
644 for(size_t k = 0; k < width * height; k++)
645 {
646 float w = 1.0f;
647 for(size_t kc = 0; kc <= 1; kc++)
648 {
649 const float avg_in = log2f(fmaxf(blurred_in_out[k * 4 + kc * 2 + 0], 1E-6f));
650 const float avg_out = log2f(fmaxf(blurred_in_out[k * 4 + kc * 2 + 1], 1E-6f));
651 w *= expf(-fmaxf(fabsf(avg_out - avg_in), 0.01f) * safety);
652 }
653 for(size_t kc = 0; kc <= 1; kc++)
654 {
655 const size_t c = (guide + kc + 1) % 3;
656 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);
657 }
658 }
659
660error:;
661 dt_pixelpipe_cache_free_align(blurred_in_out);
662 if(g) dt_gaussian_free(g);
664 return err;
665}
666
667static inline __attribute__((always_inline)) int reduce_chromatic_aberrations(const float* const restrict in,
668 const size_t width, const size_t height,
669 const size_t ch, const float sigma, const float sigma2,
672 const gboolean refine_manifolds,
673 const float safety,
674 float* const restrict out)
675
676{
677 int err = 0;
678 const float downsize = fminf(3.0f, sigma);
679 const size_t ds_width = width / downsize;
680 const size_t ds_height = height / downsize;
681 float *const restrict ds_in = dt_pixelpipe_cache_alloc_align_float_cache(ds_width * ds_height * 4, 0);
682 float *const restrict manifolds = dt_pixelpipe_cache_alloc_align_float_cache(width * height * 6, 0);
683
684 // we use only one variable for both higher and lower manifolds in order
685 // to save time by doing only one bilinear interpolation instead of 2.
686 float *const restrict ds_manifolds = dt_pixelpipe_cache_alloc_align_float_cache(ds_width * ds_height * 6, 0);
687 if(IS_NULL_PTR(ds_manifolds) || IS_NULL_PTR(ds_in) || IS_NULL_PTR(manifolds))
688 {
689 err = 1;
690 goto error;
691 }
692
693 // Downsample the image for speed-up
694 interpolate_bilinear(in, width, height, ds_in, ds_width, ds_height, 4);
695
696 // Compute manifolds
697 if(get_manifolds(ds_in, ds_width, ds_height, sigma / downsize, sigma2 / downsize, guide, ds_manifolds, refine_manifolds))
698 {
699 err = 1;
700 goto error;
701 }
702
703 // upscale manifolds
704 interpolate_bilinear(ds_manifolds, ds_width, ds_height, manifolds, width, height, 6);
705 apply_correction(in, manifolds, width, height, sigma, guide, mode, out);
706 if(reduce_artifacts(in, width, height, sigma, guide, safety, out))
707 {
708 err = 1;
709 goto error;
710 }
711
712error:;
715 dt_pixelpipe_cache_free_align(ds_manifolds);
716
717 return err;
718}
719
720int 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)
721{
722 const dt_iop_roi_t *const roi_in = &piece->roi_in;
723 const dt_iop_roi_t *const roi_out = &piece->roi_out;
724
726 // used to adjuste blur level depending on size. Don't amplify noise if magnified > 100%
727 const float scale = fmaxf(dt_dev_get_module_scale(pipe, roi_in), 1.f);
728 const int ch = piece->dsc_in.channels;
729 const size_t width = roi_out->width;
730 const size_t height = roi_out->height;
731 const float* in = (float*)ivoid;
732 float* out = (float*)ovoid;
733 const float sigma = fmaxf(d->radius / scale, 1.0f);
734 const float sigma2 = fmaxf(d->radius * d->radius / scale, 1.0f);
735
736 // whether to be very conservative in preserving the original image, or to
737 // keep algorithm result even if it overshoots
738 const float safety = powf(20.0f, 1.0f - d->strength);
739 return reduce_chromatic_aberrations(in, width, height, ch, sigma, sigma2, d->guide_channel, d->mode, d->refine_manifolds, safety, out);
740}
741
743{
747
748 // Widget defaults / soft ranges, formerly set in reload_defaults(). They belong here on the GUI
749 // thread (widgets known to exist); reload_defaults() also runs on export/thumbnail devs with no gui.
750 dt_bauhaus_combobox_set_default(g->guide_channel, d->guide_channel);
751 dt_bauhaus_slider_set_default(g->radius, d->radius);
752 dt_bauhaus_slider_set_soft_range(g->radius, 1.0, 20.0);
753 dt_bauhaus_slider_set_default(g->strength, d->strength);
754 dt_bauhaus_combobox_set_default(g->mode, d->mode);
755
756 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->refine_manifolds), p->refine_manifolds);
757}
758
760{
762
763 d->guide_channel = DT_CACORRECT_RGB_G;
764 d->radius = 5.0f;
765 d->strength = 0.5f;
767 d->refine_manifolds = FALSE;
768
769 // No widget access here: bauhaus defaults/ranges live in gui_update() (see above).
770}
771
773{
775 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
776 g->guide_channel = dt_bauhaus_combobox_from_params(self, "guide_channel");
777 gtk_widget_set_tooltip_text(g->guide_channel, _("channel used as a reference to\n"
778 "correct the other channels.\n"
779 "use sharpest channel if some\n"
780 "channels are blurry.\n"
781 "try changing guide channel if you\n"
782 "have artifacts."));
783 g->radius = dt_bauhaus_slider_from_params(self, "radius");
784 gtk_widget_set_tooltip_text(g->radius, _("increase for stronger correction"));
785 g->strength = dt_bauhaus_slider_from_params(self, "strength");
786 gtk_widget_set_tooltip_text(g->strength, _("balance between smoothing colors\n"
787 "and preserving them.\n"
788 "high values can lead to overshooting\n"
789 "and edge bleeding."));
790
791 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("advanced parameters")), TRUE, TRUE, 0);
792 g->mode = dt_bauhaus_combobox_from_params(self, "mode");
793 gtk_widget_set_tooltip_text(g->mode, _("correction mode to use.\n"
794 "can help with multiple\n"
795 "instances for very damaged\n"
796 "images.\n"
797 "darken only is particularly\n"
798 "efficient to correct blue\n"
799 "chromatic aberration."));
800 g->refine_manifolds = dt_bauhaus_toggle_from_params(self, "refine_manifolds");
801 gtk_widget_set_tooltip_text(g->refine_manifolds, _("runs an iterative approach\n"
802 "with several radii.\n"
803 "improves result on images\n"
804 "with very large chromatic\n"
805 "aberrations, but can smooth\n"
806 "colors too much on other\n"
807 "images."));
808}
809// clang-format off
810// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
811// vim: shiftwidth=2 expandtab tabstop=2 cindent
812// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
813// 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:1498
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1491
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1400
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)
@ IOP_CS_RGB
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_iop_params_t
Definition dev_history.h:43
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
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: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
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
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:134
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_GROUP_REPAIR
Definition imageop.h:159
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)
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
void *const ovoid
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
float *const restrict const size_t k
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_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
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_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
#define for_four_channels(_var,...)
Definition simd.h:89
const float sigma
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
unsigned int channels
Definition format.h:83
dt_iop_cacorrectrgb_mode_t mode
dt_iop_cacorrectrgb_guide_channel_t guide_channel
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
int32_t params_size
Definition imageop.h:335
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define DT_GUI_BOX_SPACING