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 "bauhaus/bauhaus.h"
56#include "common/box_filters.h"
57#include "common/darktable.h"
59#include "control/control.h"
60#include "control/signal.h"
61#include "develop/imageop.h"
63#include "develop/imageop_gui.h"
64#include "gui/gtk.h"
65
66#include "develop/tiling.h"
67#include "iop/iop_api.h"
68
69#ifdef HAVE_OPENCL
70#include "common/opencl.h"
71#endif
72
73#include <float.h>
74#include <gtk/gtk.h>
75#include <math.h>
76#include <stdlib.h>
77#include <string.h>
78
79//----------------------------------------------------------------------
80// implement the module api
81//----------------------------------------------------------------------
82
84
85typedef float rgb_pixel[3];
86
88{
89 float strength; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.2
90 float distance; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.2
92
93// types dt_iop_hazeremoval_params_t and dt_iop_hazeremoval_data_t are
94// equal, thus no commit_params function needs to be implemented
96
99{
100 memcpy(piece->data, params, self->params_size);
101 piece->cache_output_on_ram = TRUE;
102}
103
113
123
124
125const char *name()
126{
127 return _("haze removal");
128}
129
130
131const char *aliases()
132{
133 return _("dehaze|defog|smoke|smog");
134}
135
136const char **description(struct dt_iop_module_t *self)
137{
138 return dt_iop_set_description(self, _("remove fog and atmospheric hazing from pictures"),
139 _("corrective"),
140 _("linear, RGB, scene-referred"),
141 _("frequential, RGB"),
142 _("linear, RGB, scene-referred"));
143}
144
149
150
152{
153 return IOP_GROUP_REPAIR;
154}
155
156
158{
159 return IOP_CS_RGB;
160}
161
162
164{
166 piece->data_size = sizeof(dt_iop_hazeremoval_data_t);
167}
168
169
171{
172 dt_free_align(piece->data);
173 piece->data = NULL;
174}
175
176
178{
179 dt_iop_hazeremoval_global_data_t *gd = malloc(sizeof(*gd));
180 const int program = 27; // hazeremoval.cl, from programs.conf
181 gd->kernel_hazeremoval_transision_map = dt_opencl_create_kernel(program, "hazeremoval_transision_map");
182 gd->kernel_hazeremoval_box_min_x = dt_opencl_create_kernel(program, "hazeremoval_box_min_x");
183 gd->kernel_hazeremoval_box_min_y = dt_opencl_create_kernel(program, "hazeremoval_box_min_y");
184 gd->kernel_hazeremoval_box_max_x = dt_opencl_create_kernel(program, "hazeremoval_box_max_x");
185 gd->kernel_hazeremoval_box_max_y = dt_opencl_create_kernel(program, "hazeremoval_box_max_y");
186 gd->kernel_hazeremoval_dehaze = dt_opencl_create_kernel(program, "hazeremoval_dehaze");
187 self->data = gd;
188}
189
190
202
203
204void gui_update(struct dt_iop_module_t *self)
205{
207
209 g->distance_max = NAN;
210 g->A0[0] = NAN;
211 g->A0[1] = NAN;
212 g->A0[2] = NAN;
213 g->expected_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
216}
217
219{
221
223 if(IS_NULL_PTR(piece) || !piece->enabled || piece->roi_in.width <= 0 || piece->roi_in.height <= 0)
225
226 return piece->global_hash;
227}
228
229static void _history_resync_callback(gpointer instance, gpointer user_data)
230{
231 (void)instance;
232 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
234 if(IS_NULL_PTR(g)) return;
235
236 const uint64_t preview_hash = _current_preview_hash(self);
238 g->expected_preview_hash = preview_hash;
240}
241
242
244{
246
247 g->distance_max = NAN;
248 g->A0[0] = NAN;
249 g->A0[1] = NAN;
250 g->A0[2] = NAN;
251 g->expected_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
253
254 g->strength = dt_bauhaus_slider_from_params(self, N_("strength"));
255 gtk_widget_set_tooltip_text(g->strength, _("amount of haze reduction"));
256
257 g->distance = dt_bauhaus_slider_from_params(self, N_("distance"));
258 dt_bauhaus_slider_set_digits(g->distance, 3);
259 gtk_widget_set_tooltip_text(g->distance, _("limit haze removal up to a specific spatial depth"));
260
262 G_CALLBACK(_history_resync_callback), self);
263}
264
265
271
272//----------------------------------------------------------------------
273// module local functions and structures required by process function
274//----------------------------------------------------------------------
275
276typedef struct tile
277{
278 int left, right, lower, upper;
280
281
282typedef struct rgb_image
283{
284 float *data;
287
288
289typedef struct const_rgb_image
290{
291 const float *data;
294
295
296
297
298// swap the two floats that the pointers point to
299static inline void pointer_swap_f(float *a, float *b)
300{
301 float t = *a;
302 *a = *b;
303 *b = t;
304}
305
306
307// calculate the dark channel (minimal color component over a box of size (2*w+1) x (2*w+1) )
309static int dark_channel(const const_rgb_image img1, const gray_image img2, const int w)
310{
311 const size_t size = (size_t)img1.height * img1.width;
313 for(size_t i = 0; i < size; i++)
314 {
315 const float *pixel = img1.data + i * img1.stride;
316 float m = pixel[0];
317 m = fminf(pixel[1], m);
318 m = fminf(pixel[2], m);
319 img2.data[i] = m;
320 }
321 if(dt_box_min(img2.data, img2.height, img2.width, 1, w) != 0)
322 {
323 return 1;
324 }
325 return 0;
326}
327
328
329// calculate the transition map
331static int transition_map(const const_rgb_image img1, const gray_image img2, const int w, const float *const A0,
332 const float strength)
333{
334 const size_t size = (size_t)img1.height * img1.width;
336 for(size_t i = 0; i < size; i++)
337 {
338 const float *pixel = img1.data + i * img1.stride;
339 float m = pixel[0] / A0[0];
340 m = fminf(pixel[1] / A0[1], m);
341 m = fminf(pixel[2] / A0[2], m);
342 img2.data[i] = 1.f - m * strength;
343 }
344 if(dt_box_max(img2.data, img2.height, img2.width, 1, w) != 0)
345 {
346 return 1;
347 }
348 return 0;
349}
350
351
352// partition the array [first, last) using the pivot value val, i.e.,
353// reorder the elements in the range [first, last) in such a way that
354// all elements that are less than the pivot precede the elements
355// which are larger or equal the pivot
356static float *partition(float *first, float *last, float val)
357{
358 for(; first != last; ++first)
359 {
360 if(!((*first) < val)) break;
361 }
362 if(first == last) return first;
363 for(float *i = first + 1; i != last; ++i)
364 {
365 if((*i) < val)
366 {
367 pointer_swap_f(i, first);
368 ++first;
369 }
370 }
371 return first;
372}
373
374
375// quick select algorithm, arranges the range [first, last) such that
376// the element pointed to by nth is the same as the element that would
377// be in that position if the entire range [first, last) had been
378// sorted, additionally, none of the elements in the range [nth, last)
379// is less than any of the elements in the range [first, nth)
381void quick_select(float *first, float *nth, float *last)
382{
383 if(first == last) return;
384 for(;;)
385 {
386 // select pivot by median of three heuristic for better performance
387 float *p1 = first;
388 float *pivot = first + (last - first) / 2;
389 float *p3 = last - 1;
390 if(!(*p1 < *pivot)) pointer_swap_f(p1, pivot);
391 if(!(*p1 < *p3)) pointer_swap_f(p1, p3);
392 if(!(*pivot < *p3)) pointer_swap_f(pivot, p3);
393 pointer_swap_f(pivot, last - 1); // move pivot to end
394 partition(first, last - 1, *(last - 1));
395 pointer_swap_f(last - 1, pivot); // move pivot to its final place
396 if(nth == pivot)
397 break;
398 else if(nth < pivot)
399 last = pivot;
400 else
401 first = pivot + 1;
402 }
403}
404
405
406// calculate diffusive ambient light and the maximal depth in the image
407// depth is estimated by the local amount of haze and given in units of the
408// characteristic haze depth, i.e., the distance over which object light is
409// reduced by the factor exp(-1)
411static int ambient_light(const const_rgb_image img, int w1, rgb_pixel *pA0, float *max_depth_out)
412{
413 const float dark_channel_quantil = 0.95f; // quantil for determining the most hazy pixels
414 const float bright_quantil = 0.95f; // quantil for determining the brightest pixels among the most hazy pixels
415 const int width = img.width;
416 const int height = img.height;
417 const size_t size = (size_t)width * height;
418 // calculate dark channel, which is an estimate for local amount of haze
419 gray_image dark_ch = { 0 };
420 gray_image bright_hazy = { 0 };
421 if(new_gray_image(&dark_ch, width, height)) return 1;
422 if(dark_channel(img, dark_ch, w1)) goto error;
423 // determine the brightest pixels among the most hazy pixels
424 if(new_gray_image(&bright_hazy, width, height)) goto error;
425 // first determine the most hazy pixels
426 copy_gray_image(dark_ch, bright_hazy);
427 size_t p = (size_t)(size * dark_channel_quantil);
428 quick_select(bright_hazy.data, bright_hazy.data + p, bright_hazy.data + size);
429 const float crit_haze_level = bright_hazy.data[p];
430 size_t N_most_hazy = 0;
431 for(size_t i = 0; i < size; i++)
432 if(dark_ch.data[i] >= crit_haze_level)
433 {
434 const float *pixel_in = img.data + i * img.stride;
435 // next line prevents parallelization via OpenMP
436 bright_hazy.data[N_most_hazy] = pixel_in[0] + pixel_in[1] + pixel_in[2];
437 N_most_hazy++;
438 }
439 p = (size_t)(N_most_hazy * bright_quantil);
440 quick_select(bright_hazy.data, bright_hazy.data + p, bright_hazy.data + N_most_hazy);
441 const float crit_brightness = bright_hazy.data[p];
442 free_gray_image(&bright_hazy);
443 // average over the brightest pixels among the most hazy pixels to
444 // estimate the diffusive ambient light
445 float A0_r = 0, A0_g = 0, A0_b = 0;
446 size_t N_bright_hazy = 0;
447 const float *const data = dark_ch.data;
448 __OMP_PARALLEL_FOR__(reduction(+ : N_bright_hazy, A0_r, A0_g, A0_b))
449 for(size_t i = 0; i < size; i++)
450 {
451 const float *pixel_in = img.data + i * img.stride;
452 if((data[i] >= crit_haze_level) && (pixel_in[0] + pixel_in[1] + pixel_in[2] >= crit_brightness))
453 {
454 A0_r += pixel_in[0];
455 A0_g += pixel_in[1];
456 A0_b += pixel_in[2];
457 N_bright_hazy++;
458 }
459 }
460 if(N_bright_hazy > 0)
461 {
462 A0_r /= N_bright_hazy;
463 A0_g /= N_bright_hazy;
464 A0_b /= N_bright_hazy;
465 }
466 (*pA0)[0] = A0_r;
467 (*pA0)[1] = A0_g;
468 (*pA0)[2] = A0_b;
469 free_gray_image(&dark_ch);
470 // for almost haze free images it may happen that crit_haze_level=0, this means
471 // there is a very large image depth, in this case a large number is returned, that
472 // is small enough to avoid overflow in later processing
473 // the critical haze level is at dark_channel_quantil (not 100%) to be insensitive
474 // to extreme outliners, compensate for that by some factor slightly larger than
475 // unity when calculating the maximal image depth
476 if(max_depth_out)
477 *max_depth_out = crit_haze_level > 0 ? -1.125f * logf(crit_haze_level) : logf(FLT_MAX) / 2;
478 return 0;
479
480error:
481 if(bright_hazy.data) free_gray_image(&bright_hazy);
482 if(dark_ch.data) free_gray_image(&dark_ch);
483 return 1;
484}
485
486
488int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
489 void *const ovoid)
490{
491 const dt_iop_roi_t *const roi_in = &piece->roi_in;
492 const dt_iop_roi_t *const roi_out = &piece->roi_out;
495 int err = 0;
496 gray_image trans_map = (gray_image){ 0 };
497 gray_image trans_map_filtered = (gray_image){ 0 };
498
499 const int ch = piece->dsc_in.channels;
500 const int width = roi_in->width;
501 const int height = roi_in->height;
502 const size_t size = (size_t)width * height;
503 const int w1 = 6; // window size (positive integer) for determining the dark channel and the transition map
504 const int w2 = 9; // window size (positive integer) for the guided filter
505
506 // module parameters
507 const float strength = d->strength; // strength of haze removal
508 const float distance = d->distance; // maximal distance from camera to remove haze
509 const float eps = sqrtf(0.025f); // regularization parameter for guided filter
510
511 const const_rgb_image img_in = (const_rgb_image){ ivoid, width, height, ch };
512 const rgb_image img_out = (rgb_image){ ovoid, width, height, ch };
513
514 // estimate diffusive ambient light and image depth
515 rgb_pixel A0;
516 A0[0] = NAN;
517 A0[1] = NAN;
518 A0[2] = NAN;
519 float distance_max = NAN;
520
521 // hazeremoval module needs the color and the haziness (which yields
522 // distance_max) of the most hazy region of the image. In pixelpipe
523 // FULL we can not reliably get this value as the pixelpipe might
524 // only see part of the image (region of interest). Therefore, we
525 // try to get A0 and distance_max from the PREVIEW pixelpipe which
526 // luckily stores it for us.
527 if(self->dev->gui_attached && !IS_NULL_PTR(g) && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
528 {
530 const uint64_t hash = g->hash;
531 const uint64_t expected_preview_hash = g->expected_preview_hash;
533 /* Full-pipe dehazing needs the preview statistics only when they belong to the
534 * currently resynchronized preview graph. HISTORY_RESYNC publishes that expected
535 * preview hash before either pipe starts processing, so a mismatch here means
536 * preview has not produced the new full-image reading yet and we must recompute
537 * locally instead of reusing stale GUI state. */
539 && hash == expected_preview_hash)
540 {
542 A0[0] = g->A0[0];
543 A0[1] = g->A0[1];
544 A0[2] = g->A0[2];
545 distance_max = g->distance_max;
547 }
548 }
549 // In all other cases we calculate distance_max and A0 here.
550 if(isnan(distance_max))
551 {
552 if(ambient_light(img_in, w1, &A0, &distance_max) != 0)
553 {
554 err = 1;
555 goto error;
556 }
557 }
558 // PREVIEW pixelpipe stores values.
559 if(self->dev->gui_attached && !IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
560 {
561 uint64_t hash = piece->global_hash;
563 g->A0[0] = A0[0];
564 g->A0[1] = A0[1];
565 g->A0[2] = A0[2];
566 g->distance_max = distance_max;
567 g->expected_preview_hash = hash;
568 g->hash = hash;
570 }
571
572 // calculate the transition map
573 if(new_gray_image(&trans_map, width, height))
574 {
575 err = 1;
576 goto error;
577 }
578 if(transition_map(img_in, trans_map, w1, A0, strength))
579 {
580 err = 1;
581 goto error;
582 }
583
584 // refine the transition map
585 if(dt_box_min(trans_map.data, trans_map.height, trans_map.width, 1, w1))
586 {
587 err = 1;
588 goto error;
589 }
590 if(new_gray_image(&trans_map_filtered, width, height))
591 {
592 err = 1;
593 goto error;
594 }
595 // apply guided filter with no clipping
596 if(guided_filter(img_in.data, trans_map.data, trans_map_filtered.data, width, height, ch, w2, eps, 1.f, -FLT_MAX,
597 FLT_MAX))
598 {
599 err = 1;
600 goto error;
601 }
602
603 // finally, calculate the haze-free image
604 const float t_min
605 = fminf(fmaxf(expf(-distance * distance_max), 1.f / 1024), 1.f); // minimum allowed value for transition map
606 const float *const c_A0 = A0;
607 const gray_image c_trans_map_filtered = trans_map_filtered;
609 for(size_t i = 0; i < size; i++)
610 {
611 float t = fmaxf(c_trans_map_filtered.data[i], t_min);
612 const float *pixel_in = img_in.data + i * img_in.stride;
613 float *pixel_out = img_out.data + i * img_out.stride;
614 pixel_out[0] = (pixel_in[0] - c_A0[0]) / t + c_A0[0];
615 pixel_out[1] = (pixel_in[1] - c_A0[1]) / t + c_A0[1];
616 pixel_out[2] = (pixel_in[2] - c_A0[2]) / t + c_A0[2];
617 }
618
619 free_gray_image(&trans_map);
620 free_gray_image(&trans_map_filtered);
621
623 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
624 return 0;
625
626error:
627 if(trans_map.data) free_gray_image(&trans_map);
628 if(trans_map_filtered.data) free_gray_image(&trans_map_filtered);
629 return err;
630}
631
632#ifdef HAVE_OPENCL
633
634// calculate diffusive ambient light and the maximal depth in the image
635// depth is estimated by the local amount of haze and given in units of the
636// characteristic haze depth, i.e., the distance over which object light is
637// reduced by the factor exp(-1)
638// some parts of the calculation are not suitable for a parallel implementation,
639// thus we copy data to host memory fall back to a cpu routine
640static int ambient_light_cl(struct dt_iop_module_t *self, int devid, cl_mem img, int w1, rgb_pixel *pA0,
641 float *max_depth_out)
642{
643 const int width = dt_opencl_get_image_width(img);
644 const int height = dt_opencl_get_image_height(img);
645 const int element_size = dt_opencl_get_image_element_size(img);
647 (size_t)width * height * element_size,
648 0);
649 if(IS_NULL_PTR(in)) goto error;
650
651 int err = dt_opencl_read_host_from_device(devid, in, img, width, height, element_size);
652 if(err != CL_SUCCESS) goto error;
653 const const_rgb_image img_in = (const_rgb_image){ in, width, height, element_size / sizeof(float) };
654 if(ambient_light(img_in, w1, pA0, max_depth_out) != 0)
655 {
656 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
657 goto error;
658 }
660 return 0;
661
662error:
664 return 1;
665}
666
667
668static int box_min_cl(struct dt_iop_module_t *self, int devid, cl_mem in, cl_mem out, const int w)
669{
671 const int width = dt_opencl_get_image_width(in);
672 const int height = dt_opencl_get_image_height(in);
673 void *temp = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
674
675 const int kernel_x = gd->kernel_hazeremoval_box_min_x;
676 dt_opencl_set_kernel_arg(devid, kernel_x, 0, sizeof(int), &width);
677 dt_opencl_set_kernel_arg(devid, kernel_x, 1, sizeof(int), &height);
678 dt_opencl_set_kernel_arg(devid, kernel_x, 2, sizeof(cl_mem), &in);
679 dt_opencl_set_kernel_arg(devid, kernel_x, 3, sizeof(cl_mem), &temp);
680 dt_opencl_set_kernel_arg(devid, kernel_x, 4, sizeof(int), &w);
681 const size_t sizes_x[] = { 1, ROUNDUPDHT(height, devid), 1 };
682 int err = dt_opencl_enqueue_kernel_2d(devid, kernel_x, sizes_x);
683 if(err != CL_SUCCESS) goto error;
684
685 const int kernel_y = gd->kernel_hazeremoval_box_min_y;
686 dt_opencl_set_kernel_arg(devid, kernel_y, 0, sizeof(int), &width);
687 dt_opencl_set_kernel_arg(devid, kernel_y, 1, sizeof(int), &height);
688 dt_opencl_set_kernel_arg(devid, kernel_y, 2, sizeof(cl_mem), &temp);
689 dt_opencl_set_kernel_arg(devid, kernel_y, 3, sizeof(cl_mem), &out);
690 dt_opencl_set_kernel_arg(devid, kernel_y, 4, sizeof(int), &w);
691 const size_t sizes_y[] = { ROUNDUPDWD(width, devid), 1, 1 };
692 err = dt_opencl_enqueue_kernel_2d(devid, kernel_y, sizes_y);
693
694error:
695 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[hazeremoval, box_min_cl] unknown error: %d\n", err);
697 return err;
698}
699
700
701static int box_max_cl(struct dt_iop_module_t *self, int devid, cl_mem in, cl_mem out, const int w)
702{
704 const int width = dt_opencl_get_image_width(in);
705 const int height = dt_opencl_get_image_height(in);
706 void *temp = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
707
708 const int kernel_x = gd->kernel_hazeremoval_box_max_x;
709 dt_opencl_set_kernel_arg(devid, kernel_x, 0, sizeof(int), &width);
710 dt_opencl_set_kernel_arg(devid, kernel_x, 1, sizeof(int), &height);
711 dt_opencl_set_kernel_arg(devid, kernel_x, 2, sizeof(cl_mem), &in);
712 dt_opencl_set_kernel_arg(devid, kernel_x, 3, sizeof(cl_mem), &temp);
713 dt_opencl_set_kernel_arg(devid, kernel_x, 4, sizeof(int), &w);
714 const size_t sizes_x[] = { 1, ROUNDUPDHT(height, devid), 1 };
715 int err = dt_opencl_enqueue_kernel_2d(devid, kernel_x, sizes_x);
716 if(err != CL_SUCCESS) goto error;
717
718 const int kernel_y = gd->kernel_hazeremoval_box_max_y;
719 dt_opencl_set_kernel_arg(devid, kernel_y, 0, sizeof(int), &width);
720 dt_opencl_set_kernel_arg(devid, kernel_y, 1, sizeof(int), &height);
721 dt_opencl_set_kernel_arg(devid, kernel_y, 2, sizeof(cl_mem), &temp);
722 dt_opencl_set_kernel_arg(devid, kernel_y, 3, sizeof(cl_mem), &out);
723 dt_opencl_set_kernel_arg(devid, kernel_y, 4, sizeof(int), &w);
724 const size_t sizes_y[] = { ROUNDUPDWD(width, devid), 1, 1 };
725 err = dt_opencl_enqueue_kernel_2d(devid, kernel_y, sizes_y);
726
727error:
728 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[hazeremoval, box_max_cl] unknown error: %d\n", err);
730 return err;
731}
732
733
734static int transition_map_cl(struct dt_iop_module_t *self, int devid, cl_mem img1, cl_mem img2, const int w1,
735 const float strength, const float *const A0)
736{
738 const int width = dt_opencl_get_image_width(img1);
739 const int height = dt_opencl_get_image_height(img1);
740
742 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(int), &width);
743 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), &height);
744 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &img1);
745 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &img2);
746 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), &strength);
747 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &A0[0]);
748 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &A0[1]);
749 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &A0[2]);
750 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
751 int err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
752 if(err != CL_SUCCESS)
753 {
754 dt_print(DT_DEBUG_OPENCL, "[hazeremoval, transition_map_cl] unknown error: %d\n", err);
755 return err;
756 }
757 err = box_max_cl(self, devid, img2, img2, w1);
758
759 return err;
760}
761
762
763static int dehaze_cl(struct dt_iop_module_t *self, int devid, cl_mem img_in, cl_mem trans_map, cl_mem img_out,
764 const float t_min, const float *const A0)
765{
767 const int width = dt_opencl_get_image_width(img_in);
768 const int height = dt_opencl_get_image_height(img_in);
769
770 const int kernel = gd->kernel_hazeremoval_dehaze;
771 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(int), &width);
772 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), &height);
773 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &img_in);
774 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &trans_map);
775 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &img_out);
776 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &t_min);
777 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &A0[0]);
778 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &A0[1]);
779 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &A0[2]);
780 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
781 int err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
782 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[hazeremoval, dehaze_cl] unknown error: %d\n", err);
783 return err;
784}
785
786void 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)
787{
788 tiling->factor = 2.5f; // in + out + two single-channel temp buffers
789 tiling->factor_cl = 5.0f;
790 tiling->maxbuf = 1.0f;
791 tiling->maxbuf_cl = 1.0f;
792 tiling->overhead = 0;
793 tiling->overlap = 0;
794 tiling->xalign = 1;
795 tiling->yalign = 1;
796}
797
798int 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)
799{
800 const dt_iop_roi_t *const roi_in = &piece->roi_in;
801 const dt_iop_roi_t *const roi_out = &piece->roi_out;
804
805 const int ch = piece->dsc_in.channels;
806 const int devid = pipe->devid;
807 const int width = roi_in->width;
808 const int height = roi_in->height;
809 const int w1 = 6; // window size (positive integer) for determining the dark channel and the transition map
810 const int w2 = 9; // window size (positive integer) for the guided filter
811
812 // module parameters
813 const float strength = d->strength; // strength of haze removal
814 const float distance = d->distance; // maximal distance from camera to remove haze
815 const float eps = sqrtf(0.025f); // regularization parameter for guided filter
816
817 // estimate diffusive ambient light and image depth
818 rgb_pixel A0;
819 A0[0] = NAN;
820 A0[1] = NAN;
821 A0[2] = NAN;
822 float distance_max = NAN;
823
824 // hazeremoval module needs the color and the haziness (which yields
825 // distance_max) of the most hazy region of the image. In pixelpipe
826 // FULL we can not reliably get this value as the pixelpipe might
827 // only see part of the image (region of interest). Therefore, we
828 // try to get A0 and distance_max from the PREVIEW pixelpipe which
829 // luckily stores it for us.
830 if(self->dev->gui_attached && g && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
831 {
833 const uint64_t hash = g->hash;
834 const uint64_t expected_preview_hash = g->expected_preview_hash;
837 && hash == expected_preview_hash)
838 {
840 A0[0] = g->A0[0];
841 A0[1] = g->A0[1];
842 A0[2] = g->A0[2];
843 distance_max = g->distance_max;
845 }
846 }
847 // In all other cases we calculate distance_max and A0 here.
848 if(isnan(distance_max))
849 {
850 float max_depth = 0.f;
851 if(ambient_light_cl(self, devid, img_in, w1, &A0, &max_depth)) return FALSE;
852 distance_max = max_depth;
853 }
854 // PREVIEW pixelpipe stores values.
855 if(self->dev->gui_attached && g && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
856 {
857 uint64_t hash = piece->global_hash;
859 g->A0[0] = A0[0];
860 g->A0[1] = A0[1];
861 g->A0[2] = A0[2];
862 g->distance_max = distance_max;
863 g->expected_preview_hash = hash;
864 g->hash = hash;
866 }
867
868 // calculate the transition map
869 void *trans_map = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
870 transition_map_cl(self, devid, img_in, trans_map, w1, strength, A0);
871 // refine the transition map
872 box_min_cl(self, devid, trans_map, trans_map, w1);
873 void *trans_map_filtered = dt_opencl_alloc_device(devid, width, height, (int)sizeof(float));
874 // apply guided filter with no clipping
875 if(guided_filter_cl(devid, img_in, trans_map, trans_map_filtered, width, height, ch, w2, eps, 1.f, -CL_FLT_MAX,
876 CL_FLT_MAX) != 0)
877 {
879 dt_opencl_release_mem_object(trans_map_filtered);
880 return FALSE;
881 }
882
883 // finally, calculate the haze-free image
884 const float t_min
885 = fminf(fmaxf(expf(-distance * distance_max), 1.f / 1024), 1.f); // minimum allowed value for transition map
886 dehaze_cl(self, devid, img_in, trans_map_filtered, img_out, t_min, A0);
887
889 dt_opencl_release_mem_object(trans_map_filtered);
890
891 return TRUE;
892}
893#endif
894
895// clang-format off
896// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
897// vim: shiftwidth=2 expandtab tabstop=2 cindent
898// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
899// 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:278
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
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)
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_RGB
const dt_colormatrix_t dt_aligned_pixel_t out
static float strength(float value, float strength)
Definition colorzones.c:420
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
@ DT_DEBUG_OPENCL
Definition darktable.h:722
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
#define dt_free(ptr)
Definition darktable.h:456
#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 __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#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
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:1593
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:367
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
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)
Definition hazeremoval.c:97
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()
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:85
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:95
__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:3141
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ IOP_GROUP_REPAIR
Definition imageop.h:140
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
void *const ovoid
static float kernel(const float *x, const float *y)
const int t
#define w2
Definition lmmse.c:60
#define w1
Definition lmmse.c:59
float *const restrict const size_t const size_t ch
size_t size
Definition mipmap_cache.c:3
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2136
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2471
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
int dt_opencl_get_image_height(cl_mem mem)
Definition opencl.c:2598
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
int dt_opencl_get_image_width(cl_mem mem)
Definition opencl.c:2587
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:2127
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:2169
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
int dt_opencl_get_image_element_size(cl_mem mem)
Definition opencl.c:2609
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
#define DT_PIXELPIPE_CACHE_HASH_INVALID
#define eps
Definition rcd.c:81
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:209
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
unsigned __int64 uint64_t
Definition strptime.c:75
const float * data
struct dt_control_signal_t * signals
Definition darktable.h:774
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:162
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
unsigned int channels
Definition format.h:54
dt_iop_global_data_t * data
Definition imageop.h:233
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
int32_t params_size
Definition imageop.h:309
Region of interest passed through the pixelpipe.
Definition imageop.h:72
float * data
float * data
int lower
int left
int right
int upper