Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
hazeremoval.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2017-2020 Heiko Bauke.
4 Copyright (C) 2017 luzpaz.
5 Copyright (C) 2017, 2019 Tobias Ellinghaus.
6 Copyright (C) 2018, 2020, 2023, 2025-2026 Aurélien PIERRE.
7 Copyright (C) 2018 Edgardo Hoszowski.
8 Copyright (C) 2018 Maurizio Paglia.
9 Copyright (C) 2018, 2020, 2022 Pascal Obry.
10 Copyright (C) 2018 rawfiner.
11 Copyright (C) 2019 Andreas Schneider.
12 Copyright (C) 2019 Diederik ter Rahe.
13 Copyright (C) 2020 Aldric Renaudin.
14 Copyright (C) 2020, 2022 Diederik Ter Rahe.
15 Copyright (C) 2020, 2022 Ralf Brown.
16 Copyright (C) 2022 Hanno Schwalm.
17 Copyright (C) 2022 Martin Bařinka.
18 Copyright (C) 2022 Philipp Lutz.
19 Copyright (C) 2024 Alban Gruin.
20 Copyright (C) 2024 Alynx Zhou.
21
22 darktable is free software: you can redistribute it and/or modify
23 it under the terms of the GNU General Public License as published by
24 the Free Software Foundation, either version 3 of the License, or
25 (at your option) any later version.
26
27 darktable is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with darktable. If not, see <http://www.gnu.org/licenses/>.
34*/
35
36/*
37 This module implements automatic single-image haze removal as
38 described by K. He et al. in
39
40 * Kaiming He, Jian Sun, and Xiaoou Tang, "Single Image Haze
41 Removal Using Dark Channel Prior," IEEE Transactions on Pattern
42 Analysis and Machine Intelligence, vol. 33, no. 12,
43 pp. 2341-2353, Dec. 2011. DOI: 10.1109/TPAMI.2010.168
44
45 * K. He, J. Sun, and X. Tang, "Guided Image Filtering," Lecture
46 Notes in Computer Science, pp. 1-14, 2010. DOI:
47 10.1007/978-3-642-15549-9_1
48*/
49
50
51#ifdef HAVE_CONFIG_H
52#include "config.h"
53#endif
54
55#include "widgets/bauhaus.h"
56#include "pixel/box_filters.h"
57#include "system/macros.h"
58#include "system/openmp.h"
60#include "system/mem_alloc.h"
61#include "common/logging.h"
64#include "pixel/guided_filter.h"
65#include "control/signal.h"
66#include "develop/imageop.h"
68#include "develop/imageop_gui.h"
69
70#include "develop/tiling.h"
71#include "iop/iop_api.h"
72
73#ifdef HAVE_OPENCL
74#include "common/opencl.h"
75#endif
76
77#include <float.h>
78#include <gtk/gtk.h>
79#include <math.h>
80#include <stdlib.h>
81#include <string.h>
82
83//----------------------------------------------------------------------
84// implement the module api
85//----------------------------------------------------------------------
86
88
89typedef float rgb_pixel[3];
90
92{
93 float strength; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.2
94 float distance; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.2
96
97// types dt_iop_hazeremoval_params_t and dt_iop_hazeremoval_data_t are
98// equal, thus no commit_params function needs to be implemented
100
103{
104 memcpy(piece->data, params, self->params_size);
105 piece->cache_output_on_ram = TRUE;
106}
107
117
127
128
129const char *name()
130{
131 return _("haze removal");
132}
133
134
135const char *aliases()
136{
137 return _("dehaze|defog|smoke|smog");
138}
139
140const char **description(struct dt_iop_module_t *self)
141{
142 return dt_iop_set_description(self, _("remove fog and atmospheric hazing from pictures"),
143 _("corrective"),
144 _("linear, RGB, scene-referred"),
145 _("frequential, RGB"),
146 _("linear, RGB, scene-referred"));
147}
148
153
154
156{
157 return IOP_GROUP_REPAIR;
158}
159
160
162{
163 return IOP_CS_RGB;
164}
165
166
168{
170 piece->data_size = sizeof(dt_iop_hazeremoval_data_t);
171}
172
173
175{
176 dt_free_align(piece->data);
177 piece->data = NULL;
178}
179
180
182{
183 dt_iop_hazeremoval_global_data_t *gd = malloc(sizeof(*gd));
184 const int program = 27; // hazeremoval.cl, from programs.conf
185 gd->kernel_hazeremoval_transision_map = dt_opencl_create_kernel(program, "hazeremoval_transision_map");
186 gd->kernel_hazeremoval_box_min_x = dt_opencl_create_kernel(program, "hazeremoval_box_min_x");
187 gd->kernel_hazeremoval_box_min_y = dt_opencl_create_kernel(program, "hazeremoval_box_min_y");
188 gd->kernel_hazeremoval_box_max_x = dt_opencl_create_kernel(program, "hazeremoval_box_max_x");
189 gd->kernel_hazeremoval_box_max_y = dt_opencl_create_kernel(program, "hazeremoval_box_max_y");
190 gd->kernel_hazeremoval_dehaze = dt_opencl_create_kernel(program, "hazeremoval_dehaze");
191 self->data = gd;
192}
193
194
206
207
208void gui_update(struct dt_iop_module_t *self)
209{
211
213 g->distance_max = NAN;
214 g->A0[0] = NAN;
215 g->A0[1] = NAN;
216 g->A0[2] = NAN;
217 g->expected_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
220}
221
223{
225
227 if(IS_NULL_PTR(piece) || !piece->enabled || piece->roi_in.width <= 0 || piece->roi_in.height <= 0)
229
230 return piece->global_hash;
231}
232
233static void _history_resync_callback(gpointer instance, gpointer user_data)
234{
235 (void)instance;
236 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
238 if(IS_NULL_PTR(g)) return;
239
240 const uint64_t preview_hash = _current_preview_hash(self);
242 g->expected_preview_hash = preview_hash;
244}
245
246
248{
250
251 g->distance_max = NAN;
252 g->A0[0] = NAN;
253 g->A0[1] = NAN;
254 g->A0[2] = NAN;
255 g->expected_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
257
258 g->strength = dt_bauhaus_slider_from_params(self, N_("strength"));
259 gtk_widget_set_tooltip_text(g->strength, _("amount of haze reduction"));
260
261 g->distance = dt_bauhaus_slider_from_params(self, N_("distance"));
262 dt_bauhaus_slider_set_digits(g->distance, 3);
263 gtk_widget_set_tooltip_text(g->distance, _("limit haze removal up to a specific spatial depth"));
264
266 G_CALLBACK(_history_resync_callback), self);
267}
268
269
275
276//----------------------------------------------------------------------
277// module local functions and structures required by process function
278//----------------------------------------------------------------------
279
280typedef struct tile
281{
284
285
286typedef struct rgb_image
287{
288 float *data;
291
292
293typedef struct const_rgb_image
294{
295 const float *data;
298
299
300
301
302// swap the two floats that the pointers point to
303static inline void pointer_swap_f(float *a, float *b)
304{
305 float t = *a;
306 *a = *b;
307 *b = t;
308}
309
310
311// calculate the dark channel (minimal color component over a box of size (2*w+1) x (2*w+1) )
313static int dark_channel(const const_rgb_image img1, const gray_image img2, const int w)
314{
315 const size_t size = (size_t)img1.height * img1.width;
317 for(size_t i = 0; i < size; i++)
318 {
319 const float *pixel = img1.data + i * img1.stride;
320 float m = pixel[0];
321 m = fminf(pixel[1], m);
322 m = fminf(pixel[2], m);
323 img2.data[i] = m;
324 }
325 if(dt_box_min(img2.data, img2.height, img2.width, 1, w) != 0)
326 {
327 return 1;
328 }
329 return 0;
330}
331
332
333// calculate the transition map
335static int transition_map(const const_rgb_image img1, const gray_image img2, const int w, const float *const A0,
336 const float strength)
337{
338 const size_t size = (size_t)img1.height * img1.width;
340 for(size_t i = 0; i < size; i++)
341 {
342 const float *pixel = img1.data + i * img1.stride;
343 float m = pixel[0] / A0[0];
344 m = fminf(pixel[1] / A0[1], m);
345 m = fminf(pixel[2] / A0[2], m);
346 img2.data[i] = 1.f - m * strength;
347 }
348 if(dt_box_max(img2.data, img2.height, img2.width, 1, w) != 0)
349 {
350 return 1;
351 }
352 return 0;
353}
354
355
356// partition the array [first, last) using the pivot value val, i.e.,
357// reorder the elements in the range [first, last) in such a way that
358// all elements that are less than the pivot precede the elements
359// which are larger or equal the pivot
360static float *partition(float *first, float *last, float val)
361{
362 for(; first != last; ++first)
363 {
364 if(!((*first) < val)) break;
365 }
366 if(first == last) return first;
367 for(float *i = first + 1; i != last; ++i)
368 {
369 if((*i) < val)
370 {
371 pointer_swap_f(i, first);
372 ++first;
373 }
374 }
375 return first;
376}
377
378
379// quick select algorithm, arranges the range [first, last) such that
380// the element pointed to by nth is the same as the element that would
381// be in that position if the entire range [first, last) had been
382// sorted, additionally, none of the elements in the range [nth, last)
383// is less than any of the elements in the range [first, nth)
385void quick_select(float *first, float *nth, float *last)
386{
387 if(first == last) return;
388 for(;;)
389 {
390 // select pivot by median of three heuristic for better performance
391 float *p1 = first;
392 float *pivot = first + (last - first) / 2;
393 float *p3 = last - 1;
394 if(!(*p1 < *pivot)) pointer_swap_f(p1, pivot);
395 if(!(*p1 < *p3)) pointer_swap_f(p1, p3);
396 if(!(*pivot < *p3)) pointer_swap_f(pivot, p3);
397 pointer_swap_f(pivot, last - 1); // move pivot to end
398 partition(first, last - 1, *(last - 1));
399 pointer_swap_f(last - 1, pivot); // move pivot to its final place
400 if(nth == pivot)
401 break;
402 else if(nth < pivot)
403 last = pivot;
404 else
405 first = pivot + 1;
406 }
407}
408
409
410// calculate diffusive ambient light and the maximal depth in the image
411// depth is estimated by the local amount of haze and given in units of the
412// characteristic haze depth, i.e., the distance over which object light is
413// reduced by the factor exp(-1)
415static int ambient_light(const const_rgb_image img, int w1, rgb_pixel *pA0, float *max_depth_out)
416{
417 const float dark_channel_quantil = 0.95f; // quantil for determining the most hazy pixels
418 const float bright_quantil = 0.95f; // quantil for determining the brightest pixels among the most hazy pixels
419 const int width = img.width;
420 const int height = img.height;
421 const size_t size = (size_t)width * height;
422 // calculate dark channel, which is an estimate for local amount of haze
423 gray_image dark_ch = { 0 };
424 gray_image bright_hazy = { 0 };
425 if(new_gray_image(&dark_ch, width, height)) return 1;
426 if(dark_channel(img, dark_ch, w1)) goto error;
427 // determine the brightest pixels among the most hazy pixels
428 if(new_gray_image(&bright_hazy, width, height)) goto error;
429 // first determine the most hazy pixels
430 copy_gray_image(dark_ch, bright_hazy);
431 size_t p = (size_t)(size * dark_channel_quantil);
432 quick_select(bright_hazy.data, bright_hazy.data + p, bright_hazy.data + size);
433 const float crit_haze_level = bright_hazy.data[p];
434 size_t N_most_hazy = 0;
435 for(size_t i = 0; i < size; i++)
436 if(dark_ch.data[i] >= crit_haze_level)
437 {
438 const float *pixel_in = img.data + i * img.stride;
439 // next line prevents parallelization via OpenMP
440 bright_hazy.data[N_most_hazy] = pixel_in[0] + pixel_in[1] + pixel_in[2];
441 N_most_hazy++;
442 }
443 p = (size_t)(N_most_hazy * bright_quantil);
444 quick_select(bright_hazy.data, bright_hazy.data + p, bright_hazy.data + N_most_hazy);
445 const float crit_brightness = bright_hazy.data[p];
446 free_gray_image(&bright_hazy);
447 // average over the brightest pixels among the most hazy pixels to
448 // estimate the diffusive ambient light
449 float A0_r = 0, A0_g = 0, A0_b = 0;
450 size_t N_bright_hazy = 0;
451 const float *const data = dark_ch.data;
452 __OMP_PARALLEL_FOR__(reduction(+ : N_bright_hazy, A0_r, A0_g, A0_b))
453 for(size_t i = 0; i < size; i++)
454 {
455 const float *pixel_in = img.data + i * img.stride;
456 if((data[i] >= crit_haze_level) && (pixel_in[0] + pixel_in[1] + pixel_in[2] >= crit_brightness))
457 {
458 A0_r += pixel_in[0];
459 A0_g += pixel_in[1];
460 A0_b += pixel_in[2];
461 N_bright_hazy++;
462 }
463 }
464 if(N_bright_hazy > 0)
465 {
466 A0_r /= N_bright_hazy;
467 A0_g /= N_bright_hazy;
468 A0_b /= N_bright_hazy;
469 }
470 (*pA0)[0] = A0_r;
471 (*pA0)[1] = A0_g;
472 (*pA0)[2] = A0_b;
473 free_gray_image(&dark_ch);
474 // for almost haze free images it may happen that crit_haze_level=0, this means
475 // there is a very large image depth, in this case a large number is returned, that
476 // is small enough to avoid overflow in later processing
477 // the critical haze level is at dark_channel_quantil (not 100%) to be insensitive
478 // to extreme outliners, compensate for that by some factor slightly larger than
479 // unity when calculating the maximal image depth
480 if(max_depth_out)
481 *max_depth_out = crit_haze_level > 0 ? -1.125f * logf(crit_haze_level) : logf(FLT_MAX) / 2;
482 return 0;
483
484error:
485 if(bright_hazy.data) free_gray_image(&bright_hazy);
486 if(dark_ch.data) free_gray_image(&dark_ch);
487 return 1;
488}
489
490
492int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
493 void *const ovoid)
494{
495 const dt_iop_roi_t *const roi_in = &piece->roi_in;
496 const dt_iop_roi_t *const roi_out = &piece->roi_out;
499 int err = 0;
500 gray_image trans_map = (gray_image){ 0 };
501 gray_image trans_map_filtered = (gray_image){ 0 };
502
503 const int ch = piece->dsc_in.channels;
504 const int width = roi_in->width;
505 const int height = roi_in->height;
506 const size_t size = (size_t)width * height;
507 const int w1 = 6; // window size (positive integer) for determining the dark channel and the transition map
508 const int w2 = 9; // window size (positive integer) for the guided filter
509
510 // module parameters
511 const float strength = d->strength; // strength of haze removal
512 const float distance = d->distance; // maximal distance from camera to remove haze
513 const float eps = sqrtf(0.025f); // regularization parameter for guided filter
514
515 const const_rgb_image img_in = (const_rgb_image){ ivoid, width, height, ch };
516 const rgb_image img_out = (rgb_image){ ovoid, width, height, ch };
517
518 // estimate diffusive ambient light and image depth
519 rgb_pixel A0;
520 A0[0] = NAN;
521 A0[1] = NAN;
522 A0[2] = NAN;
523 float distance_max = NAN;
524
525 // hazeremoval module needs the color and the haziness (which yields
526 // distance_max) of the most hazy region of the image. In pixelpipe
527 // FULL we can not reliably get this value as the pixelpipe might
528 // only see part of the image (region of interest). Therefore, we
529 // try to get A0 and distance_max from the PREVIEW pixelpipe which
530 // luckily stores it for us.
531 if(self->dev->gui_attached && !IS_NULL_PTR(g) && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
532 {
534 const uint64_t hash = g->hash;
535 const uint64_t expected_preview_hash = g->expected_preview_hash;
537 /* Full-pipe dehazing needs the preview statistics only when they belong to the
538 * currently resynchronized preview graph. HISTORY_RESYNC publishes that expected
539 * preview hash before either pipe starts processing, so a mismatch here means
540 * preview has not produced the new full-image reading yet and we must recompute
541 * locally instead of reusing stale GUI state. */
543 && hash == expected_preview_hash)
544 {
546 A0[0] = g->A0[0];
547 A0[1] = g->A0[1];
548 A0[2] = g->A0[2];
549 distance_max = g->distance_max;
551 }
552 }
553 // In all other cases we calculate distance_max and A0 here.
554 if(isnan(distance_max))
555 {
556 if(ambient_light(img_in, w1, &A0, &distance_max) != 0)
557 {
558 err = 1;
559 goto error;
560 }
561 }
562 // PREVIEW pixelpipe stores values.
563 if(self->dev->gui_attached && !IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
564 {
565 uint64_t hash = piece->global_hash;
567 g->A0[0] = A0[0];
568 g->A0[1] = A0[1];
569 g->A0[2] = A0[2];
570 g->distance_max = distance_max;
571 g->expected_preview_hash = hash;
572 g->hash = hash;
574 }
575
576 // calculate the transition map
577 if(new_gray_image(&trans_map, width, height))
578 {
579 err = 1;
580 goto error;
581 }
582 if(transition_map(img_in, trans_map, w1, A0, strength))
583 {
584 err = 1;
585 goto error;
586 }
587
588 // refine the transition map
589 if(dt_box_min(trans_map.data, trans_map.height, trans_map.width, 1, w1))
590 {
591 err = 1;
592 goto error;
593 }
594 if(new_gray_image(&trans_map_filtered, width, height))
595 {
596 err = 1;
597 goto error;
598 }
599 // apply guided filter with no clipping
600 if(guided_filter(img_in.data, trans_map.data, trans_map_filtered.data, width, height, ch, w2, eps, 1.f, -FLT_MAX,
601 FLT_MAX))
602 {
603 err = 1;
604 goto error;
605 }
606
607 // finally, calculate the haze-free image
608 const float t_min
609 = fminf(fmaxf(expf(-distance * distance_max), 1.f / 1024), 1.f); // minimum allowed value for transition map
610 const float *const c_A0 = A0;
611 const gray_image c_trans_map_filtered = trans_map_filtered;
613 for(size_t i = 0; i < size; i++)
614 {
615 float t = fmaxf(c_trans_map_filtered.data[i], t_min);
616 const float *pixel_in = img_in.data + i * img_in.stride;
617 float *pixel_out = img_out.data + i * img_out.stride;
618 pixel_out[0] = (pixel_in[0] - c_A0[0]) / t + c_A0[0];
619 pixel_out[1] = (pixel_in[1] - c_A0[1]) / t + c_A0[1];
620 pixel_out[2] = (pixel_in[2] - c_A0[2]) / t + c_A0[2];
621 }
622
623 free_gray_image(&trans_map);
624 free_gray_image(&trans_map_filtered);
625
627 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
628 return 0;
629
630error:
631 if(trans_map.data) free_gray_image(&trans_map);
632 if(trans_map_filtered.data) free_gray_image(&trans_map_filtered);
633 return err;
634}
635
636#ifdef HAVE_OPENCL
637
638// calculate diffusive ambient light and the maximal depth in the image
639// depth is estimated by the local amount of haze and given in units of the
640// characteristic haze depth, i.e., the distance over which object light is
641// reduced by the factor exp(-1)
642// some parts of the calculation are not suitable for a parallel implementation,
643// thus we copy data to host memory fall back to a cpu routine
644static int ambient_light_cl(struct dt_iop_module_t *self, int devid, cl_mem img, int w1, rgb_pixel *pA0,
645 float *max_depth_out)
646{
647 const int width = dt_opencl_get_image_width(img);
648 const int height = dt_opencl_get_image_height(img);
649 const int element_size = dt_opencl_get_image_element_size(img);
651 (size_t)width * height * element_size,
652 0);
653 if(IS_NULL_PTR(in)) goto error;
654
655 int err = dt_opencl_read_host_from_device(devid, in, img, width, height, element_size);
656 if(err != CL_SUCCESS) goto error;
657 const const_rgb_image img_in = (const_rgb_image){ in, width, height, element_size / sizeof(float) };
658 if(ambient_light(img_in, w1, pA0, max_depth_out) != 0)
659 {
660 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
661 goto error;
662 }
664 return 0;
665
666error:
668 return 1;
669}
670
671
672static int box_min_cl(struct dt_iop_module_t *self, int devid, cl_mem in, cl_mem out, const int w)
673{
675 const int width = dt_opencl_get_image_width(in);
676 const int height = dt_opencl_get_image_height(in);
677 void *temp = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
678
679 const int kernel_x = gd->kernel_hazeremoval_box_min_x;
680 dt_opencl_set_kernel_arg(devid, kernel_x, 0, sizeof(int), &width);
681 dt_opencl_set_kernel_arg(devid, kernel_x, 1, sizeof(int), &height);
682 dt_opencl_set_kernel_arg(devid, kernel_x, 2, sizeof(cl_mem), &in);
683 dt_opencl_set_kernel_arg(devid, kernel_x, 3, sizeof(cl_mem), &temp);
684 dt_opencl_set_kernel_arg(devid, kernel_x, 4, sizeof(int), &w);
685 const size_t sizes_x[] = { 1, ROUNDUPDHT(height, devid), 1 };
686 int err = dt_opencl_enqueue_kernel_2d(devid, kernel_x, sizes_x);
687 if(err != CL_SUCCESS) goto error;
688
689 const int kernel_y = gd->kernel_hazeremoval_box_min_y;
690 dt_opencl_set_kernel_arg(devid, kernel_y, 0, sizeof(int), &width);
691 dt_opencl_set_kernel_arg(devid, kernel_y, 1, sizeof(int), &height);
692 dt_opencl_set_kernel_arg(devid, kernel_y, 2, sizeof(cl_mem), &temp);
693 dt_opencl_set_kernel_arg(devid, kernel_y, 3, sizeof(cl_mem), &out);
694 dt_opencl_set_kernel_arg(devid, kernel_y, 4, sizeof(int), &w);
695 const size_t sizes_y[] = { ROUNDUPDWD(width, devid), 1, 1 };
696 err = dt_opencl_enqueue_kernel_2d(devid, kernel_y, sizes_y);
697
698error:
699 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[hazeremoval, box_min_cl] unknown error: %d\n", err);
701 return err;
702}
703
704
705static int box_max_cl(struct dt_iop_module_t *self, int devid, cl_mem in, cl_mem out, const int w)
706{
708 const int width = dt_opencl_get_image_width(in);
709 const int height = dt_opencl_get_image_height(in);
710 void *temp = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
711
712 const int kernel_x = gd->kernel_hazeremoval_box_max_x;
713 dt_opencl_set_kernel_arg(devid, kernel_x, 0, sizeof(int), &width);
714 dt_opencl_set_kernel_arg(devid, kernel_x, 1, sizeof(int), &height);
715 dt_opencl_set_kernel_arg(devid, kernel_x, 2, sizeof(cl_mem), &in);
716 dt_opencl_set_kernel_arg(devid, kernel_x, 3, sizeof(cl_mem), &temp);
717 dt_opencl_set_kernel_arg(devid, kernel_x, 4, sizeof(int), &w);
718 const size_t sizes_x[] = { 1, ROUNDUPDHT(height, devid), 1 };
719 int err = dt_opencl_enqueue_kernel_2d(devid, kernel_x, sizes_x);
720 if(err != CL_SUCCESS) goto error;
721
722 const int kernel_y = gd->kernel_hazeremoval_box_max_y;
723 dt_opencl_set_kernel_arg(devid, kernel_y, 0, sizeof(int), &width);
724 dt_opencl_set_kernel_arg(devid, kernel_y, 1, sizeof(int), &height);
725 dt_opencl_set_kernel_arg(devid, kernel_y, 2, sizeof(cl_mem), &temp);
726 dt_opencl_set_kernel_arg(devid, kernel_y, 3, sizeof(cl_mem), &out);
727 dt_opencl_set_kernel_arg(devid, kernel_y, 4, sizeof(int), &w);
728 const size_t sizes_y[] = { ROUNDUPDWD(width, devid), 1, 1 };
729 err = dt_opencl_enqueue_kernel_2d(devid, kernel_y, sizes_y);
730
731error:
732 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[hazeremoval, box_max_cl] unknown error: %d\n", err);
734 return err;
735}
736
737
738static int transition_map_cl(struct dt_iop_module_t *self, int devid, cl_mem img1, cl_mem img2, const int w1,
739 const float strength, const float *const A0)
740{
742 const int width = dt_opencl_get_image_width(img1);
743 const int height = dt_opencl_get_image_height(img1);
744
746 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(int), &width);
747 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), &height);
748 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &img1);
749 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &img2);
750 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), &strength);
751 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &A0[0]);
752 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &A0[1]);
753 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &A0[2]);
754 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
755 int err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
756 if(err != CL_SUCCESS)
757 {
758 dt_print(DT_DEBUG_OPENCL, "[hazeremoval, transition_map_cl] unknown error: %d\n", err);
759 return err;
760 }
761 err = box_max_cl(self, devid, img2, img2, w1);
762
763 return err;
764}
765
766
767static int dehaze_cl(struct dt_iop_module_t *self, int devid, cl_mem img_in, cl_mem trans_map, cl_mem img_out,
768 const float t_min, const float *const A0)
769{
771 const int width = dt_opencl_get_image_width(img_in);
772 const int height = dt_opencl_get_image_height(img_in);
773
774 const int kernel = gd->kernel_hazeremoval_dehaze;
775 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(int), &width);
776 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), &height);
777 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &img_in);
778 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &trans_map);
779 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &img_out);
780 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &t_min);
781 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &A0[0]);
782 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &A0[1]);
783 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &A0[2]);
784 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
785 int err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
786 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[hazeremoval, dehaze_cl] unknown error: %d\n", err);
787 return err;
788}
789
790void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
791{
792 tiling->factor = 2.5f; // in + out + two single-channel temp buffers
793 tiling->factor_cl = 5.0f;
794 tiling->maxbuf = 1.0f;
795 tiling->maxbuf_cl = 1.0f;
796 tiling->overhead = 0;
797 tiling->overlap = 0;
798 tiling->xalign = 1;
799 tiling->yalign = 1;
800}
801
802int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem img_in, cl_mem img_out)
803{
804 const dt_iop_roi_t *const roi_in = &piece->roi_in;
805 const dt_iop_roi_t *const roi_out = &piece->roi_out;
808
809 const int ch = piece->dsc_in.channels;
810 const int devid = pipe->devid;
811 const int width = roi_in->width;
812 const int height = roi_in->height;
813 const int w1 = 6; // window size (positive integer) for determining the dark channel and the transition map
814 const int w2 = 9; // window size (positive integer) for the guided filter
815
816 // module parameters
817 const float strength = d->strength; // strength of haze removal
818 const float distance = d->distance; // maximal distance from camera to remove haze
819 const float eps = sqrtf(0.025f); // regularization parameter for guided filter
820
821 // estimate diffusive ambient light and image depth
822 rgb_pixel A0;
823 A0[0] = NAN;
824 A0[1] = NAN;
825 A0[2] = NAN;
826 float distance_max = NAN;
827
828 // hazeremoval module needs the color and the haziness (which yields
829 // distance_max) of the most hazy region of the image. In pixelpipe
830 // FULL we can not reliably get this value as the pixelpipe might
831 // only see part of the image (region of interest). Therefore, we
832 // try to get A0 and distance_max from the PREVIEW pixelpipe which
833 // luckily stores it for us.
834 if(self->dev->gui_attached && g && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
835 {
837 const uint64_t hash = g->hash;
838 const uint64_t expected_preview_hash = g->expected_preview_hash;
841 && hash == expected_preview_hash)
842 {
844 A0[0] = g->A0[0];
845 A0[1] = g->A0[1];
846 A0[2] = g->A0[2];
847 distance_max = g->distance_max;
849 }
850 }
851 // In all other cases we calculate distance_max and A0 here.
852 if(isnan(distance_max))
853 {
854 float max_depth = 0.f;
855 if(ambient_light_cl(self, devid, img_in, w1, &A0, &max_depth)) return FALSE;
856 distance_max = max_depth;
857 }
858 // PREVIEW pixelpipe stores values.
859 if(self->dev->gui_attached && g && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
860 {
861 uint64_t hash = piece->global_hash;
863 g->A0[0] = A0[0];
864 g->A0[1] = A0[1];
865 g->A0[2] = A0[2];
866 g->distance_max = distance_max;
867 g->expected_preview_hash = hash;
868 g->hash = hash;
870 }
871
872 // calculate the transition map
873 void *trans_map = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
874 transition_map_cl(self, devid, img_in, trans_map, w1, strength, A0);
875 // refine the transition map
876 box_min_cl(self, devid, trans_map, trans_map, w1);
877 void *trans_map_filtered = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
878 // apply guided filter with no clipping
879 if(guided_filter_cl(devid, img_in, trans_map, trans_map_filtered, width, height, ch, w2, eps, 1.f, -CL_FLT_MAX,
880 CL_FLT_MAX) != 0)
881 {
883 dt_opencl_release_mem_object(trans_map_filtered);
884 return FALSE;
885 }
886
887 // finally, calculate the haze-free image
888 const float t_min
889 = fminf(fmaxf(expf(-distance * distance_max), 1.f / 1024), 1.f); // minimum allowed value for transition map
890 dehaze_cl(self, devid, img_in, trans_map_filtered, img_out, t_min, A0);
891
893 dt_opencl_release_mem_object(trans_map_filtered);
894
895 return TRUE;
896}
897#endif
898
899// clang-format off
900// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
901// vim: shiftwidth=2 expandtab tabstop=2 cindent
902// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
903// 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
#define m
Definition basecurve.c:282
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3338
int dt_box_max(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
int dt_box_min(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
const int t
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
static float strength(float value, float strength)
Definition colorzones.c:429
void dt_iop_params_t
Definition dev_history.h:43
dt_dev_pixelpipe_iop_t * dt_dev_distort_get_iop_pipe(struct dt_dev_pixelpipe_t *pipe, struct dt_iop_module_t *module)
Definition develop.c:1667
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:406
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:120
int guided_filter_cl(int devid, cl_mem guide, cl_mem in, cl_mem out, const int width, const int height, const int ch, const int w, const float sqrt_eps, const float guide_weight, const float min, const float max)
__DT_CLONE_TARGETS__ int guided_filter(const float *const guide, const float *const in, float *const out, const int width, const int height, const int ch, const int w, const float sqrt_eps, const float guide_weight, const float min, const float max)
static void copy_gray_image(gray_image img1, gray_image img2)
static int new_gray_image(gray_image *img, int width, int height)
static void free_gray_image(gray_image *img_p)
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem img_in, cl_mem img_out)
static __DT_CLONE_TARGETS__ int ambient_light(const const_rgb_image img, int w1, rgb_pixel *pA0, float *max_depth_out)
const char ** description(struct dt_iop_module_t *self)
int default_group()
__DT_CLONE_TARGETS__ 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)
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static int dehaze_cl(struct dt_iop_module_t *self, int devid, cl_mem img_in, cl_mem trans_map, cl_mem img_out, const float t_min, const float *const A0)
static int box_max_cl(struct dt_iop_module_t *self, int devid, cl_mem in, cl_mem out, const int w)
static uint64_t _current_preview_hash(dt_iop_module_t *self)
const char * aliases()
static float * partition(float *first, float *last, float val)
static __DT_CLONE_TARGETS__ int transition_map(const const_rgb_image img1, const gray_image img2, const int w, const float *const A0, const float strength)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static int box_min_cl(struct dt_iop_module_t *self, int devid, cl_mem in, cl_mem out, const int w)
const char * name()
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
void cleanup_global(dt_iop_module_so_t *self)
void gui_init(dt_iop_module_t *self)
static __DT_CLONE_TARGETS__ int dark_channel(const const_rgb_image img1, const gray_image img2, const int w)
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
void gui_cleanup(dt_iop_module_t *self)
static int transition_map_cl(struct dt_iop_module_t *self, int devid, cl_mem img1, cl_mem img2, const int w1, const float strength, const float *const A0)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
struct tile tile
static void pointer_swap_f(float *a, float *b)
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static int ambient_light_cl(struct dt_iop_module_t *self, int devid, cl_mem img, int w1, rgb_pixel *pA0, float *max_depth_out)
float rgb_pixel[3]
Definition hazeremoval.c:89
static void _history_resync_callback(gpointer instance, gpointer user_data)
void init_global(dt_iop_module_so_t *self)
dt_iop_hazeremoval_params_t dt_iop_hazeremoval_data_t
Definition hazeremoval.c:99
__DT_CLONE_TARGETS__ void quick_select(float *first, float *nth, float *last)
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:3243
#define IOP_GUI_FREE
Definition imageop.h:608
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:420
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:180
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:181
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:426
@ IOP_GROUP_REPAIR
Definition imageop.h:154
#define IOP_GUI_ALLOC(module)
Definition imageop.h:605
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:79
void *const ovoid
static float kernel(const float *x, const float *y)
#define w2
Definition lmmse.c:60
#define w1
Definition lmmse.c:59
@ DT_DEBUG_OPENCL
Definition logging.h:43
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
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:65
#define dt_free_align(ptr)
Definition mem_alloc.h:122
static void * dt_calloc_align(size_t size)
Definition mem_alloc.h:129
#define dt_free(ptr)
Definition mem_alloc.h:97
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2276
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2616
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2170
int dt_opencl_get_image_height(cl_mem mem)
Definition opencl.c:2746
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2213
int dt_opencl_get_image_width(cl_mem mem)
Definition opencl.c:2735
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2267
int dt_opencl_read_host_from_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2309
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2527
int dt_opencl_get_image_element_size(cl_mem mem)
Definition opencl.c:2757
#define ROUNDUPDHT(a, b)
Definition opencl.h:83
#define ROUNDUPDWD(a, b)
Definition opencl.h:82
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:60
#define DT_PIXELPIPE_CACHE_HASH_INVALID
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
#define eps
Definition rcd.c:81
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:403
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:628
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:212
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:392
unsigned __int64 uint64_t
Definition strptime.c:75
const float * data
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:164
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:249
unsigned int channels
Definition format.h:83
dt_iop_global_data_t * data
Definition imageop.h:233
struct dt_develop_t * dev
Definition imageop.h:303
dt_iop_gui_data_t * gui_data
Definition imageop.h:318
dt_iop_global_data_t * global_data
Definition imageop.h:321
int32_t params_size
Definition imageop.h:316
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
float * data
float * data
int lower
int left
int right
int upper
#define __DT_CLONE_TARGETS__