Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
highlights.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Bruce Guenter.
4 Copyright (C) 2010-2011 Henrik Andersson.
5 Copyright (C) 2010-2014, 2016 johannes hanika.
6 Copyright (C) 2010 Stuart Henderson.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Robert Bieber.
9 Copyright (C) 2011-2014, 2016, 2019 Tobias Ellinghaus.
10 Copyright (C) 2011-2012, 2014, 2016-2017 Ulrich Pegelow.
11 Copyright (C) 2012, 2015 Edouard Gomez.
12 Copyright (C) 2012 Jérémy Rosen.
13 Copyright (C) 2012 Richard Wonka.
14 Copyright (C) 2013, 2020 Aldric Renaudin.
15 Copyright (C) 2014, 2016 Dan Torop.
16 Copyright (C) 2014-2016 Roman Lebedev.
17 Copyright (C) 2015-2016 Pedro Côrte-Real.
18 Copyright (C) 2017 Heiko Bauke.
19 Copyright (C) 2017 luzpaz.
20 Copyright (C) 2018, 2020-2026 Aurélien PIERRE.
21 Copyright (C) 2018 Edgardo Hoszowski.
22 Copyright (C) 2018 Maurizio Paglia.
23 Copyright (C) 2018-2020, 2022 Pascal Obry.
24 Copyright (C) 2018 rawfiner.
25 Copyright (C) 2019 Andreas Schneider.
26 Copyright (C) 2019 Diederik ter Rahe.
27 Copyright (C) 2019-2020, 2022 Hanno Schwalm.
28 Copyright (C) 2020 Chris Elston.
29 Copyright (C) 2020, 2022 Diederik Ter Rahe.
30 Copyright (C) 2020-2021 Ralf Brown.
31 Copyright (C) 2021 Hubert Kowalski.
32 Copyright (C) 2022 Martin Bařinka.
33 Copyright (C) 2022 Philipp Lutz.
34 Copyright (C) 2022 Victor Forsiuk.
35 Copyright (C) 2023 Alynx Zhou.
36 Copyright (C) 2023 Guillaume Stutin.
37 Copyright (C) 2023 Luca Zulberti.
38
39 darktable is free software: you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation, either version 3 of the License, or
42 (at your option) any later version.
43
44 darktable is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
48
49 You should have received a copy of the GNU General Public License
50 along with darktable. If not, see <http://www.gnu.org/licenses/>.
51 */
52#ifdef HAVE_CONFIG_H
53#include "common/darktable.h"
54#include "config.h"
55#endif
56#include <assert.h>
57#include <math.h>
58#include <stdlib.h>
59#include <string.h>
60#include "bauhaus/bauhaus.h"
61#include "common/box_filters.h"
62#include "common/bspline.h"
63#include "common/opencl.h"
64#include "common/imagebuf.h"
66#include "control/control.h"
67#include "develop/develop.h"
68#include "develop/imageop.h"
70#include "develop/imageop_gui.h"
72#include "develop/tiling.h"
73
74#include "gui/gtk.h"
75#include "iop/iop_api.h"
76
77#include <gtk/gtk.h>
78#include <inttypes.h>
79
80#define MAX_NUM_SCALES 12
81#define REDUCESIZE 64
82
83// Downsampling factor for guided-laplacian
84#define DS_FACTOR 4
85
86// Set to one to output intermediate image steps as PFM in /tmp
87#define DEBUG_DUMP_PFM 0
88
89#if DEBUG_DUMP_PFM
91static void dump_PFM(const char *filename, const float* out, const uint32_t w, const uint32_t h)
92{
93 FILE *f = g_fopen(filename, "wb");
94 fprintf(f, "PF\n%d %d\n-1.0\n", w, h);
95 for(int j = h - 1 ; j >= 0 ; j--)
96 for(int i = 0 ; i < w ; i++)
97 for(int c = 0 ; c < 3 ; c++)
98 fwrite(out + (j * w + i) * 4 + c, 1, sizeof(float), f);
99 fclose(f);
100}
101#endif
102
104
106{
107 DT_IOP_HIGHLIGHTS_CLIP = 0, // $DESCRIPTION: "clip highlights"
108 DT_IOP_HIGHLIGHTS_LCH = 1, // $DESCRIPTION: "reconstruct in LCh"
109 DT_IOP_HIGHLIGHTS_INPAINT = 2, // $DESCRIPTION: "reconstruct color"
110 DT_IOP_HIGHLIGHTS_LAPLACIAN = 3, //$DESCRIPTION: "guided laplacians"
112
114{
115 WAVELETS_1_SCALE = 0, // $DESCRIPTION: "2 px"
116 WAVELETS_2_SCALE = 1, // $DESCRIPTION: "4 px"
117 WAVELETS_3_SCALE = 2, // $DESCRIPTION: "8 px"
118 WAVELETS_4_SCALE = 3, // $DESCRIPTION: "16 px"
119 WAVELETS_5_SCALE = 4, // $DESCRIPTION: "32 px"
120 WAVELETS_6_SCALE = 5, // $DESCRIPTION: "64 px"
121 WAVELETS_7_SCALE = 6, // $DESCRIPTION: "128 px (slow)"
122 WAVELETS_8_SCALE = 7, // $DESCRIPTION: "256 px (slow)"
123 WAVELETS_9_SCALE = 8, // $DESCRIPTION: "512 px (very slow)"
124 WAVELETS_10_SCALE = 9, // $DESCRIPTION: "1024 px (very slow)"
125 WAVELETS_11_SCALE = 10, // $DESCRIPTION: "2048 px (insanely slow)"
126 WAVELETS_12_SCALE = 11, // $DESCRIPTION: "4096 px (insanely slow)"
128
130{
131 // params of v1
132 dt_iop_highlights_mode_t mode; // $DEFAULT: DT_IOP_HIGHLIGHTS_CLIP $DESCRIPTION: "method"
133 float blendL; // unused $DEFAULT: 1.0
134 float blendC; // unused $DEFAULT: 0.0
135 float blendh; // unused $DEFAULT: 0.0
136 // params of v2
137 float clip; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 1.0 $DESCRIPTION: "clipping threshold"
138 // params of v3
139 float noise_level; // $MIN: 0. $MAX: 1.0 $DEFAULT: 0.00 $DESCRIPTION: "noise level"
140 int iterations; // $MIN: 1 $MAX: 512 $DEFAULT: 30 $DESCRIPTION: "iterations"
141 dt_atrous_wavelets_scales_t scales; // $DEFAULT: 8 $DESCRIPTION: "diameter of reconstruction"
142 float reconstructing; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.4 $DESCRIPTION: "cast balance"
143 float combine; // $MIN: 0.0 $MAX: 10.0 $DEFAULT: 2.0 $DESCRIPTION: "combine segments"
145 // params of v4
146 float solid_color; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "inpaint a flat color"
148
159
161
187
188
189const char *name()
190{
191 return _("_highlight reconstruction");
192}
193
194const char **description(struct dt_iop_module_t *self)
195{
196 return dt_iop_set_description(self, _("avoid magenta highlights and try to recover highlights colors"),
197 _("corrective"),
198 _("linear, raw, scene-referred"),
199 _("reconstruction, raw"),
200 _("linear, raw, scene-referred"));
201}
202
204{
205 return IOP_GROUP_REPAIR;
206}
207
212
214{
215 if(piece && piece->dsc_in.cst != IOP_CS_RAW)
216 return IOP_CS_RGB;
217 return IOP_CS_RAW;
218}
219
222{
223 default_output_format(self, pipe, piece, dsc);
224}
225
226void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
227 const struct dt_dev_pixelpipe_iop_t *piece, const void *input)
228{
230 const dt_iop_roi_t *const roi_out = &piece->roi_out;
231 const float *const restrict in = (const float *)input;
232 float max_RGB[3] = { 0.0f };
233
234 __OMP_PARALLEL_FOR__(reduction(max:max_RGB[:3]) collapse(2))
235 for(size_t i = 0; i < roi_out->height; i++)
236 for(size_t j = 0; j < roi_out->width; j++)
237 {
238 const size_t channel =
239 (piece->dsc_in.filters == 9u)
240 ? FCxtrans(i, j, roi_out, piece->dsc_in.xtrans)
241 : FC(i + roi_out->y, j + roi_out->x, piece->dsc_in.filters);
242 const float pixel_max = in[i * roi_out->width + j] / piece->dsc_in.processed_maximum[channel];
243 max_RGB[channel] = MAX(max_RGB[channel], pixel_max);
244 }
245
246 p->clip = MIN(MIN(max_RGB[0], max_RGB[1]), max_RGB[2]);
247}
248
249
250int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
251 void *new_params, const int new_version)
252{
253 if(old_version == 1 && new_version == 4)
254 {
255 /*
256 params of v2 :
257 float clip
258 + params of v3
259 + params of v4
260 */
261 memcpy(new_params, old_params, sizeof(dt_iop_highlights_params_t) - 5 * sizeof(float) - 2 * sizeof(int) - sizeof(dt_atrous_wavelets_scales_t));
263 n->clip = 1.0f;
264 n->noise_level = 0.0f;
265 n->reconstructing = 0.4f;
266 n->combine = 2.f;
267 n->debugmode = 0;
268 n->iterations = 1;
269 n->scales = 5;
270 n->solid_color = 0.f;
271 return 0;
272 }
273 if(old_version == 2 && new_version == 4)
274 {
275 /*
276 params of v3 :
277 float noise_level;
278 int iterations;
279 dt_atrous_wavelets_scales_t scales;
280 float reconstructing;
281 float combine;
282 int debugmode;
283 + params of v4
284 */
285 memcpy(new_params, old_params, sizeof(dt_iop_highlights_params_t) - 4 * sizeof(float) - 2 * sizeof(int) - sizeof(dt_atrous_wavelets_scales_t));
287 n->noise_level = 0.0f;
288 n->reconstructing = 0.4f;
289 n->combine = 2.f;
290 n->debugmode = 0;
291 n->iterations = 1;
292 n->scales = 5;
293 n->solid_color = 0.f;
294 return 0;
295 }
296 if(old_version == 3 && new_version == 4)
297 {
298 /*
299 params of v4 :
300 float solid_color;
301 */
302 memcpy(new_params, old_params, sizeof(dt_iop_highlights_params_t) - sizeof(float));
304 n->solid_color = 0.f;
305 return 0;
306 }
307
308 return 1;
309}
310
311#ifdef HAVE_OPENCL
312static cl_int process_laplacian_bayer_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
313 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
314 const dt_iop_roi_t *const roi_in,
315 const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips);
316static cl_int process_laplacian_xtrans_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
317 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
318 const dt_iop_roi_t *const roi_in,
319 const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips);
320
321int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
322{
323 const dt_iop_roi_t *const roi_in = &piece->roi_in;
324 const dt_iop_roi_t *const roi_out = &piece->roi_out;
328
329 const uint32_t filters = piece->dsc_in.filters;
330 const int devid = pipe->devid;
331 const int width = roi_in->width;
332 const int height = roi_in->height;
333
334 /* This transient preview belongs to the central darkroom view. Do not infer
335 * its owner from ROI geometry: at zoom-to-fit the main and navigation pipes
336 * can produce identical dimensions while both still need distinct outputs. */
337 const gboolean visualizing = !IS_NULL_PTR(g) && g->show_visualize
338 && self->dev->gui_attached && pipe == self->dev->pipe;
339
340 cl_int err = DT_OPENCL_DEFAULT_ERROR;
341 cl_mem dev_xtrans = NULL;
342
343 // this works for bayer and X-Trans sensors
344 if(visualizing)
345 {
346 float clips[4] = { 0.995f * d->clip * piece->dsc_in.processed_maximum[0],
347 0.995f * d->clip * piece->dsc_in.processed_maximum[1],
348 0.995f * d->clip * piece->dsc_in.processed_maximum[2],
349 d->clip};
350
351
352 cl_mem dev_clips = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), clips);
353 if(IS_NULL_PTR(dev_clips)) goto error;
354
355 // bayer sensor raws with LCH mode
356 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
357 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 0, sizeof(cl_mem), (void *)&dev_in);
358 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 1, sizeof(cl_mem), (void *)&dev_out);
359 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 2, sizeof(int), (void *)&width);
360 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 3, sizeof(int), (void *)&height);
361 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 4, sizeof(int), (void *)&roi_out->x);
362 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 5, sizeof(int), (void *)&roi_out->y);
363 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 6, sizeof(int), (void *)&filters);
364 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_false_color, 7, sizeof(cl_mem), (void *)&dev_clips);
365
367 if(err != CL_SUCCESS) goto error;
368
369 /* The clipping preview is the final output of this module. Blending would
370 * interpret PASSTHRU as a channel-display request and replace RAW output
371 * with zeroes before the downstream demosaic stage can display it. */
373 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
375 return TRUE;
376 }
377
378 const float clip = d->clip
379 * fminf(piece->dsc_in.processed_maximum[0],
380 fminf(piece->dsc_in.processed_maximum[1], piece->dsc_in.processed_maximum[2]));
381
382 if(!filters)
383 {
384 // non-raw images use dedicated kernel which just clips
385 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
386 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_4f_clip, 0, sizeof(cl_mem), (void *)&dev_in);
387 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_4f_clip, 1, sizeof(cl_mem), (void *)&dev_out);
388 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_4f_clip, 2, sizeof(int), (void *)&width);
389 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_4f_clip, 3, sizeof(int), (void *)&height);
390 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_4f_clip, 4, sizeof(int), (void *)&d->mode);
391 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_4f_clip, 5, sizeof(float), (void *)&clip);
393 if(err != CL_SUCCESS) goto error;
394 }
395 else if(d->mode == DT_IOP_HIGHLIGHTS_CLIP || d->mode > DT_IOP_HIGHLIGHTS_LAPLACIAN)
396 {
397 // raw images with clip mode (both bayer and xtrans)
398 // This is also the fallback if d->mode is set with something invalid
399 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
400 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 0, sizeof(cl_mem), (void *)&dev_in);
401 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 1, sizeof(cl_mem), (void *)&dev_out);
402 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 2, sizeof(int), (void *)&width);
403 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 3, sizeof(int), (void *)&height);
404 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 4, sizeof(float), (void *)&clip);
405 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 5, sizeof(int), (void *)&roi_out->x);
406 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 6, sizeof(int), (void *)&roi_out->y);
407 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_clip, 7, sizeof(int), (void *)&filters);
409 if(err != CL_SUCCESS) goto error;
410 }
411 else if(d->mode == DT_IOP_HIGHLIGHTS_LCH && filters != 9u)
412 {
413 // bayer sensor raws with LCH mode
414 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
415 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 0, sizeof(cl_mem), (void *)&dev_in);
416 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 1, sizeof(cl_mem), (void *)&dev_out);
417 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 2, sizeof(int), (void *)&width);
418 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 3, sizeof(int), (void *)&height);
419 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 4, sizeof(float), (void *)&clip);
420 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 5, sizeof(int), (void *)&roi_out->x);
421 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 6, sizeof(int), (void *)&roi_out->y);
422 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_bayer, 7, sizeof(int), (void *)&filters);
424 if(err != CL_SUCCESS) goto error;
425 }
426 else if(d->mode == DT_IOP_HIGHLIGHTS_LCH && filters == 9u)
427 {
428 // xtrans sensor raws with LCH mode
429 int blocksizex, blocksizey;
430
432 = (dt_opencl_local_buffer_t){ .xoffset = 2 * 2, .xfactor = 1, .yoffset = 2 * 2, .yfactor = 1,
433 .cellsize = sizeof(float), .overhead = 0,
434 .sizex = 1 << 8, .sizey = 1 << 8 };
435
437 {
438 blocksizex = locopt.sizex;
439 blocksizey = locopt.sizey;
440 }
441 else
442 blocksizex = blocksizey = 1;
443
444 dev_xtrans
445 = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->dsc_in.xtrans), (void *)piece->dsc_in.xtrans);
446 if(IS_NULL_PTR(dev_xtrans)) goto error;
447
448 size_t sizes[] = { ROUNDUP(width, blocksizex), ROUNDUP(height, blocksizey), 1 };
449 size_t local[] = { blocksizex, blocksizey, 1 };
450 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 0, sizeof(cl_mem), (void *)&dev_in);
451 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 1, sizeof(cl_mem), (void *)&dev_out);
452 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 2, sizeof(int), (void *)&width);
453 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 3, sizeof(int), (void *)&height);
454 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 4, sizeof(float), (void *)&clip);
455 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 5, sizeof(int), (void *)&roi_out->x);
456 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 6, sizeof(int), (void *)&roi_out->y);
457 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_1f_lch_xtrans, 7, sizeof(cl_mem), (void *)&dev_xtrans);
459 sizeof(float) * (blocksizex + 4) * (blocksizey + 4), NULL);
460
462 if(err != CL_SUCCESS) goto error;
463 }
464 else if(d->mode == DT_IOP_HIGHLIGHTS_LAPLACIAN)
465 {
466 const dt_aligned_pixel_t clips = { 0.995f * d->clip * piece->dsc_in.processed_maximum[0],
467 0.995f * d->clip * piece->dsc_in.processed_maximum[1],
468 0.995f * d->clip * piece->dsc_in.processed_maximum[2], clip };
469 err = (filters == 9u)
470 ? process_laplacian_xtrans_cl(self, pipe, piece, dev_in, dev_out, roi_in, roi_out, clips)
471 : process_laplacian_bayer_cl(self, pipe, piece, dev_in, dev_out, roi_in, roi_out, clips);
472 if(err != CL_SUCCESS) goto error;
473 }
474
476 return TRUE;
477
478error:
480 dt_print(DT_DEBUG_OPENCL, "[opencl_highlights] couldn't enqueue kernel! %i\n", err);
481 return FALSE;
482}
483#endif
484
485void 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)
486{
487 const dt_iop_roi_t *const roi_in = &piece->roi_in;
489 const uint32_t filters = piece->dsc_in.filters;
490
491 if(d->mode == DT_IOP_HIGHLIGHTS_LAPLACIAN && filters)
492 {
493 // Mosaic CFA and guided laplacian method: prepare for wavelets decomposition.
494 const float scale = DS_FACTOR * dt_dev_get_module_scale(pipe, roi_in);
495 const float final_radius = (float)((int)(1 << d->scales)) / scale;
496 const int scales = CLAMP((int)ceilf(log2f(final_radius)), 1, MAX_NUM_SCALES);
497 const int max_filter_radius = (1 << scales);
498
499 // Warning: in and out are single-channel in RAW mode.
500 // in + out + interpolated + ds_interpolated + ds_tmp + 2 * ds_LF + ds_HF + mask + ds_mask
501 tiling->factor = 2.f + 2.f * 4 + 6.f * 4 / DS_FACTOR;
502 // OpenCL adds a downsampled scratch accumulator to keep the guided-laplacian read/write images distinct.
503 // in + out + interpolated + temp + mask + ds_interpolated + reconstructed_scratch + 2 * ds_LF + ds_HF + ds_mask
504 tiling->factor_cl = 2.f + 3.f * 4 + 6.f * 4 / DS_FACTOR;
505
506 // The wavelets decomposition uses a temp buffer of size 4 x ds_width
507 tiling->maxbuf = 1.f / roi_in->height * 4.f / DS_FACTOR;
508
509 // No temp buffer on GPU
510 tiling->maxbuf_cl = 1.0f;
511 tiling->overhead = 0;
512
513 // Note : if we were not doing anything iterative,
514 // max_filter_radius would not need to be factored more.
515 // Since we are iterating within tiles, we need more padding.
516 // The clean way of doing it would be an internal tiling mechanism
517 // where we restitch the tiles between each new iteration.
518 tiling->overlap = max_filter_radius * 1.5f / DS_FACTOR;
519 tiling->xalign = (filters == 9u) ? 6 : 2;
520 tiling->yalign = (filters == 9u) ? 6 : 2;
521
522 return;
523 }
524
525 tiling->factor = 2.0f; // in + out
526 tiling->maxbuf = 1.0f;
527 tiling->overhead = 0;
528
529 if(filters == 9u)
530 {
531 // xtrans
532 tiling->xalign = 6;
533 tiling->yalign = 6;
534 tiling->overlap = (d->mode == DT_IOP_HIGHLIGHTS_LCH) ? 2 : 0;
535 }
536 else if(filters)
537 {
538 // bayer
539 tiling->xalign = 2;
540 tiling->yalign = 2;
541 tiling->overlap = (d->mode == DT_IOP_HIGHLIGHTS_LCH) ? 1 : 0;
542 }
543 else
544 {
545 // non-raw
546 tiling->xalign = 1;
547 tiling->yalign = 1;
548 tiling->overlap = 0;
549 }
550}
551
552/* interpolate value for a pixel, ideal via ratio to nearby pixel */
553static inline float interp_pix_xtrans(const int ratio_next,
554 const ssize_t offset_next,
555 const float clip0, const float clip_next,
556 const float *const in,
557 const float *const ratios)
558{
559 assert(ratio_next != 0);
560 // it's OK to exceed clipping of current pixel's color based on a
561 // neighbor -- that is the purpose of interpolating highlight
562 // colors
563 const float clip_val = fmaxf(clip0, clip_next);
564 if(in[offset_next] >= clip_next - 1e-5f)
565 {
566 // next pixel is also clipped
567 return clip_val;
568 }
569 else
570 {
571 // set this pixel in ratio to the next
572 assert(ratio_next != 0);
573 if (ratio_next > 0)
574 return fminf(in[offset_next] / ratios[ratio_next], clip_val);
575 else
576 return fminf(in[offset_next] * ratios[-ratio_next], clip_val);
577 }
578}
579
580static inline void interpolate_color_xtrans(const void *const ivoid, void *const ovoid,
581 const dt_iop_roi_t *const roi_in,
582 const dt_iop_roi_t *const roi_out,
583 int dim, int dir, int other,
584 const float *const clip,
585 const uint8_t (*const xtrans)[6],
586 const int pass)
587{
588 // In Bayer each row/col has only green/red or green/blue
589 // transitions, hence can reconstruct color by single ratio per
590 // row. In x-trans there can be transitions between arbitrary colors
591 // in a row/col (and 2x2 green blocks which provide no color
592 // transition information). Hence calculate multiple color ratios
593 // for each row/col.
594
595 // Lookup for color ratios, e.g. red -> blue is roff[0][2] and blue
596 // -> red is roff[2][0]. Returned value is an index into ratios. If
597 // negative, then need to invert the ratio. Identity color
598 // transitions aren't used.
599 const int roff[3][3] = {{ 0, -1, -2},
600 { 1, 0, -3},
601 { 2, 3, 0}};
602 // record ratios of color transitions 0:unused, 1:RG, 2:RB, and 3:GB
603 dt_aligned_pixel_t ratios = {1.0f, 1.0f, 1.0f, 1.0f};
604
605 // passes are 0:+x, 1:-x, 2:+y, 3:-y
606 // dims are 0:traverse a row, 1:traverse a column
607 // dir is 1:left to right, -1: right to left
608 int i = (dim == 0) ? 0 : other;
609 int j = (dim == 0) ? other : 0;
610 const ssize_t offs = (ssize_t)(dim ? roi_out->width : 1) * ((dir < 0) ? -1 : 1);
611 const ssize_t offl = offs - (dim ? 1 : roi_out->width);
612 const ssize_t offr = offs + (dim ? 1 : roi_out->width);
613 int beg, end;
614 if(dir == 1)
615 {
616 beg = 0;
617 end = (dim == 0) ? roi_out->width : roi_out->height;
618 }
619 else
620 {
621 beg = ((dim == 0) ? roi_out->width : roi_out->height) - 1;
622 end = -1;
623 }
624
625 float *in, *out;
626 if(dim == 1)
627 {
628 out = (float *)ovoid + (size_t)i + (size_t)beg * roi_out->width;
629 in = (float *)ivoid + (size_t)i + (size_t)beg * roi_in->width;
630 }
631 else
632 {
633 out = (float *)ovoid + (size_t)beg + (size_t)j * roi_out->width;
634 in = (float *)ivoid + (size_t)beg + (size_t)j * roi_in->width;
635 }
636
637 for(int k = beg; k != end; k += dir)
638 {
639 if(dim == 1)
640 j = k;
641 else
642 i = k;
643
644 const uint8_t f0 = FCxtrans(j, i, roi_in, xtrans);
645 const uint8_t f1 = FCxtrans(dim ? (j + dir) : j, dim ? i : (i + dir), roi_in, xtrans);
646 const uint8_t fl = FCxtrans(dim ? (j + dir) : (j - 1), dim ? (i - 1) : (i + dir), roi_in, xtrans);
647 const uint8_t fr = FCxtrans(dim ? (j + dir) : (j + 1), dim ? (i + 1) : (i + dir), roi_in, xtrans);
648 const float clip0 = clip[f0];
649 const float clip1 = clip[f1];
650 const float clipl = clip[fl];
651 const float clipr = clip[fr];
652 const float clip_max = fmaxf(fmaxf(clip[0], clip[1]), clip[2]);
653
654 if(i == 0 || i == roi_out->width - 1 || j == 0 || j == roi_out->height - 1)
655 {
656 if(pass == 3) out[0] = fminf(clip_max, in[0]);
657 }
658 else
659 {
660 // ratio to next pixel if this & next are unclamped and not in
661 // 2x2 green block
662 if ((f0 != f1) &&
663 (in[0] < clip0 && in[0] > 1e-5f) &&
664 (in[offs] < clip1 && in[offs] > 1e-5f))
665 {
666 const int r = roff[f0][f1];
667 assert(r != 0);
668 if (r > 0)
669 ratios[r] = (3.f * ratios[r] + (in[offs] / in[0])) / 4.f;
670 else
671 ratios[-r] = (3.f * ratios[-r] + (in[0] / in[offs])) / 4.f;
672 }
673
674 if(in[0] >= clip0 - 1e-5f)
675 {
676 // interplate color for clipped pixel
677 float add;
678 if(f0 != f1)
679 // next pixel is different color
680 add =
681 interp_pix_xtrans(roff[f0][f1], offs, clip0, clip1, in, ratios);
682 else
683 // at start of 2x2 green block, look diagonally
684 add = (fl != f0) ?
685 interp_pix_xtrans(roff[f0][fl], offl, clip0, clipl, in, ratios) :
686 interp_pix_xtrans(roff[f0][fr], offr, clip0, clipr, in, ratios);
687
688 if(pass == 0)
689 out[0] = add;
690 else if(pass == 3)
691 out[0] = fminf(clip_max, (out[0] + add) / 4.0f);
692 else
693 out[0] += add;
694 }
695 else
696 {
697 // pixel is not clipped
698 if(pass == 3) out[0] = in[0];
699 }
700 }
701 out += offs;
702 in += offs;
703 }
704}
705
706static inline void interpolate_color(const void *const ivoid, void *const ovoid,
707 const dt_iop_roi_t *const roi_out, int dim, int dir, int other,
708 const float *clip, const uint32_t filters, const int pass)
709{
710 float ratio = 1.0f;
711 float *in, *out;
712
713 int i = 0, j = 0;
714 if(dim == 0)
715 j = other;
716 else
717 i = other;
718 ssize_t offs = dim ? roi_out->width : 1;
719 if(dir < 0) offs = -offs;
720 int beg, end;
721 if(dim == 0 && dir == 1)
722 {
723 beg = 0;
724 end = roi_out->width;
725 }
726 else if(dim == 0 && dir == -1)
727 {
728 beg = roi_out->width - 1;
729 end = -1;
730 }
731 else if(dim == 1 && dir == 1)
732 {
733 beg = 0;
734 end = roi_out->height;
735 }
736 else if(dim == 1 && dir == -1)
737 {
738 beg = roi_out->height - 1;
739 end = -1;
740 }
741 else
742 return;
743
744 if(dim == 1)
745 {
746 out = (float *)ovoid + i + (size_t)beg * roi_out->width;
747 in = (float *)ivoid + i + (size_t)beg * roi_out->width;
748 }
749 else
750 {
751 out = (float *)ovoid + beg + (size_t)j * roi_out->width;
752 in = (float *)ivoid + beg + (size_t)j * roi_out->width;
753 }
754 for(int k = beg; k != end; k += dir)
755 {
756 if(dim == 1)
757 j = k;
758 else
759 i = k;
760 const float clip0 = clip[FC(j, i, filters)];
761 const float clip1 = clip[FC(dim ? (j + 1) : j, dim ? i : (i + 1), filters)];
762 if(i == 0 || i == roi_out->width - 1 || j == 0 || j == roi_out->height - 1)
763 {
764 if(pass == 3) out[0] = in[0];
765 }
766 else
767 {
768 if(in[0] < clip0 && in[0] > 1e-5f)
769 { // both are not clipped
770 if(in[offs] < clip1 && in[offs] > 1e-5f)
771 { // update ratio, exponential decay. ratio = in[odd]/in[even]
772 if(k & 1)
773 ratio = (3.0f * ratio + in[0] / in[offs]) / 4.0f;
774 else
775 ratio = (3.0f * ratio + in[offs] / in[0]) / 4.0f;
776 }
777 }
778
779 if(in[0] >= clip0 - 1e-5f)
780 { // in[0] is clipped, restore it as in[1] adjusted according to ratio
781 float add = 0.0f;
782 if(in[offs] >= clip1 - 1e-5f)
783 add = fmaxf(clip0, clip1);
784 else if(k & 1)
785 add = in[offs] * ratio;
786 else
787 add = in[offs] / ratio;
788
789 if(pass == 0)
790 out[0] = add;
791 else if(pass == 3)
792 out[0] = (out[0] + add) / 4.0f;
793 else
794 out[0] += add;
795 }
796 else
797 {
798 if(pass == 3) out[0] = in[0];
799 }
800 }
801 out += offs;
802 in += offs;
803 }
804}
805
806/*
807 * these 2 constants were computed using following Sage code:
808 *
809 * sqrt3 = sqrt(3)
810 * sqrt12 = sqrt(12) # 2*sqrt(3)
811 *
812 * print 'sqrt3 = ', sqrt3, ' ~= ', RealField(128)(sqrt3)
813 * print 'sqrt12 = ', sqrt12, ' ~= ', RealField(128)(sqrt12)
814 */
815#define SQRT3 1.7320508075688772935274463415058723669L
816#define SQRT12 3.4641016151377545870548926830117447339L // 2*SQRT3
817
819static void process_lch_bayer(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
820 void *const ovoid, const dt_iop_roi_t *const roi_in,
821 const dt_iop_roi_t *const roi_out, const float clip)
822{
823 const uint32_t filters = piece->dsc_in.filters;
824 __OMP_PARALLEL_FOR__(collapse(2))
825 for(int j = 0; j < roi_out->height; j++)
826 {
827 for(int i = 0; i < roi_out->width; i++)
828 {
829 float *const out = (float *)ovoid + (size_t)roi_out->width * j + i;
830 const float *const in = (float *)ivoid + (size_t)roi_out->width * j + i;
831
832 if(i == roi_out->width - 1 || j == roi_out->height - 1)
833 {
834 // fast path for border
835 out[0] = MIN(clip, in[0]);
836 }
837 else
838 {
839 int clipped = 0;
840
841 // sample 1 bayer block. thus we will have 2 green values.
842 float R = 0.0f, Gmin = FLT_MAX, Gmax = -FLT_MAX, B = 0.0f;
843 for(int jj = 0; jj <= 1; jj++)
844 {
845 for(int ii = 0; ii <= 1; ii++)
846 {
847 const float val = in[(size_t)jj * roi_out->width + ii];
848
849 clipped = (clipped || (val > clip));
850
851 const int c = FC(j + jj + roi_out->y, i + ii + roi_out->x, filters);
852 switch(c)
853 {
854 case 0:
855 R = val;
856 break;
857 case 1:
858 Gmin = MIN(Gmin, val);
859 Gmax = MAX(Gmax, val);
860 break;
861 case 2:
862 B = val;
863 break;
864 }
865 }
866 }
867
868 if(clipped)
869 {
870 const float Ro = MIN(R, clip);
871 const float Go = MIN(Gmin, clip);
872 const float Bo = MIN(B, clip);
873
874 const float L = (R + Gmax + B) / 3.0f;
875
876 float C = SQRT3 * (R - Gmax);
877 float H = 2.0f * B - Gmax - R;
878
879 const float Co = SQRT3 * (Ro - Go);
880 const float Ho = 2.0f * Bo - Go - Ro;
881
882 if(R != Gmax && Gmax != B)
883 {
884 const float ratio = sqrtf((Co * Co + Ho * Ho) / (C * C + H * H));
885 C *= ratio;
886 H *= ratio;
887 }
888
889 dt_aligned_pixel_t RGB = { 0.0f, 0.0f, 0.0f };
890
891 /*
892 * backtransform proof, sage:
893 *
894 * R,G,B,L,C,H = var('R,G,B,L,C,H')
895 * solve([L==(R+G+B)/3, C==sqrt(3)*(R-G), H==2*B-G-R], R, G, B)
896 *
897 * result:
898 * [[R == 1/6*sqrt(3)*C - 1/6*H + L, G == -1/6*sqrt(3)*C - 1/6*H + L, B == 1/3*H + L]]
899 */
900 RGB[0] = L - H / 6.0f + C / SQRT12;
901 RGB[1] = L - H / 6.0f - C / SQRT12;
902 RGB[2] = L + H / 3.0f;
903
904 out[0] = RGB[FC(j + roi_out->y, i + roi_out->x, filters)];
905 }
906 else
907 {
908 out[0] = in[0];
909 }
910 }
911 }
912 }
913
914}
915
917static void process_lch_xtrans(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
918 void *const ovoid, const dt_iop_roi_t *const roi_in,
919 const dt_iop_roi_t *const roi_out, const float clip)
920{
921 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
923 for(int j = 0; j < roi_out->height; j++)
924 {
925 float *out = (float *)ovoid + (size_t)roi_out->width * j;
926 float *in = (float *)ivoid + (size_t)roi_in->width * j;
927
928 // bit vector used as ring buffer to remember clipping of current
929 // and last two columns, checking current pixel and its vertical
930 // neighbors
931 int cl = 0;
932
933 for(int i = 0; i < roi_out->width; i++)
934 {
935 // update clipping ring buffer
936 cl = (cl << 1) & 6;
937 if(j >= 2 && j <= roi_out->height - 3)
938 {
939 cl |= (in[-roi_in->width] > clip) | (in[0] > clip) | (in[roi_in->width] > clip);
940 }
941
942 if(i < 2 || i > roi_out->width - 3 || j < 2 || j > roi_out->height - 3)
943 {
944 // fast path for border
945 out[0] = MIN(clip, in[0]);
946 }
947 else
948 {
949 // if current pixel is clipped, always reconstruct
950 int clipped = (in[0] > clip);
951 if(!clipped)
952 {
953 clipped = cl;
954 if(clipped)
955 {
956 // If the ring buffer can't show we are in an obviously
957 // unclipped region, this is the slow case: check if there
958 // is any 3x3 block touching the current pixel which has
959 // no clipping, as then don't need to reconstruct the
960 // current pixel. This avoids zippering in edge
961 // transitions from clipped to unclipped areas. The
962 // X-Trans sensor seems prone to this, unlike Bayer, due
963 // to its irregular pattern.
964 for(int offset_j = -2; offset_j <= 0; offset_j++)
965 {
966 for(int offset_i = -2; offset_i <= 0; offset_i++)
967 {
968 if(clipped)
969 {
970 clipped = 0;
971 for(int jj = offset_j; jj <= offset_j + 2; jj++)
972 {
973 for(int ii = offset_i; ii <= offset_i + 2; ii++)
974 {
975 const float val = in[(ssize_t)jj * roi_in->width + ii];
976 clipped = (clipped || (val > clip));
977 }
978 }
979 }
980 }
981 }
982 }
983 }
984
985 if(clipped)
986 {
987 dt_aligned_pixel_t mean = { 0.0f, 0.0f, 0.0f };
988 dt_aligned_pixel_t RGBmax = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
989 int cnt[3] = { 0, 0, 0 };
990
991 for(int jj = -1; jj <= 1; jj++)
992 {
993 for(int ii = -1; ii <= 1; ii++)
994 {
995 const float val = in[(ssize_t)jj * roi_in->width + ii];
996 const int c = FCxtrans(j+jj, i+ii, roi_in, xtrans);
997 mean[c] += val;
998 cnt[c]++;
999 RGBmax[c] = MAX(RGBmax[c], val);
1000 }
1001 }
1002
1003 const float Ro = MIN(mean[0]/cnt[0], clip);
1004 const float Go = MIN(mean[1]/cnt[1], clip);
1005 const float Bo = MIN(mean[2]/cnt[2], clip);
1006
1007 const float R = RGBmax[0];
1008 const float G = RGBmax[1];
1009 const float B = RGBmax[2];
1010
1011 const float L = (R + G + B) / 3.0f;
1012
1013 float C = SQRT3 * (R - G);
1014 float H = 2.0f * B - G - R;
1015
1016 const float Co = SQRT3 * (Ro - Go);
1017 const float Ho = 2.0f * Bo - Go - Ro;
1018
1019 if(R != G && G != B)
1020 {
1021 const float ratio = sqrtf((Co * Co + Ho * Ho) / (C * C + H * H));
1022 C *= ratio;
1023 H *= ratio;
1024 }
1025
1026 dt_aligned_pixel_t RGB = { 0.0f, 0.0f, 0.0f };
1027
1028 RGB[0] = L - H / 6.0f + C / SQRT12;
1029 RGB[1] = L - H / 6.0f - C / SQRT12;
1030 RGB[2] = L + H / 3.0f;
1031
1032 out[0] = RGB[FCxtrans(j, i, roi_out, xtrans)];
1033 }
1034 else
1035 out[0] = in[0];
1036 }
1037 out++;
1038 in++;
1039 }
1040 }
1041
1042}
1043
1044#undef SQRT3
1045#undef SQRT12
1046
1048static void _interpolate_and_mask(const float *const restrict input,
1049 float *const restrict interpolated,
1050 float *const restrict clipping_mask,
1051 const dt_aligned_pixel_t clips,
1052 const dt_aligned_pixel_t wb,
1053 const uint32_t filters,
1054 const size_t width, const size_t height)
1055{
1056 // Bilinear interpolation
1057 __OMP_PARALLEL_FOR__(collapse(2))
1058 for(size_t i = 0; i < height; i++)
1059 for(size_t j = 0; j < width; j++)
1060 {
1061 const size_t c = FC(i, j, filters);
1062 const size_t i_center = i * width;
1063 const float center = input[i_center + j];
1064
1065 float R = 0.f;
1066 float G = 0.f;
1067 float B = 0.f;
1068
1069 int R_clipped = 0;
1070 int G_clipped = 0;
1071 int B_clipped = 0;
1072
1073 if(i == 0 || j == 0 || i == height - 1 || j == width - 1)
1074 {
1075 // We are on the image edges. We don't need to demosaic,
1076 // just set R = G = B = center and record clipping.
1077 // This will introduce a marginal error close to edges, mostly irrelevant
1078 // because we are dealing with local averages anyway, later on.
1079 // Also we remosaic the image at the end, so only the relevant channel gets picked.
1080 // Finally, it's unlikely that the borders of the image get clipped due to vignetting.
1081 R = G = B = center;
1082 R_clipped = G_clipped = B_clipped = (center > clips[c]);
1083 }
1084 else
1085 {
1086 const size_t i_prev = (i - 1) * width;
1087 const size_t i_next = (i + 1) * width;
1088 const size_t j_prev = (j - 1);
1089 const size_t j_next = (j + 1);
1090
1091 const float north = input[i_prev + j];
1092 const float south = input[i_next + j];
1093 const float west = input[i_center + j_prev];
1094 const float east = input[i_center + j_next];
1095
1096 const float north_east = input[i_prev + j_next];
1097 const float north_west = input[i_prev + j_prev];
1098 const float south_east = input[i_next + j_next];
1099 const float south_west = input[i_next + j_prev];
1100
1101 if(c == GREEN) // green pixel
1102 {
1103 G = center;
1104 G_clipped = (center > clips[GREEN]);
1105 }
1106 else // non-green pixel
1107 {
1108 // interpolate inside an X/Y cross
1109 G = (north + south + east + west) / 4.f;
1110 G_clipped = (north > clips[GREEN] || south > clips[GREEN] || east > clips[GREEN] || west > clips[GREEN]);
1111 }
1112
1113 if(c == RED ) // red pixel
1114 {
1115 R = center;
1116 R_clipped = (center > clips[RED]);
1117 }
1118 else // non-red pixel
1119 {
1120 if(FC(i - 1, j, filters) == RED && FC(i + 1, j, filters) == RED)
1121 {
1122 // we are on a red column, so interpolate column-wise
1123 R = (north + south) / 2.f;
1124 R_clipped = (north > clips[RED] || south > clips[RED]);
1125 }
1126 else if(FC(i, j - 1, filters) == RED && FC(i, j + 1, filters) == RED)
1127 {
1128 // we are on a red row, so interpolate row-wise
1129 R = (west + east) / 2.f;
1130 R_clipped = (west > clips[RED] || east > clips[RED]);
1131 }
1132 else
1133 {
1134 // we are on a blue row, so interpolate inside a square
1135 R = (north_west + north_east + south_east + south_west) / 4.f;
1136 R_clipped = (north_west > clips[RED] || north_east > clips[RED] || south_west > clips[RED]
1137 || south_east > clips[RED]);
1138 }
1139 }
1140
1141 if(c == BLUE ) // blue pixel
1142 {
1143 B = center;
1144 B_clipped = (center > clips[BLUE]);
1145 }
1146 else // non-blue pixel
1147 {
1148 if(FC(i - 1, j, filters) == BLUE && FC(i + 1, j, filters) == BLUE)
1149 {
1150 // we are on a blue column, so interpolate column-wise
1151 B = (north + south) / 2.f;
1152 B_clipped = (north > clips[BLUE] || south > clips[BLUE]);
1153 }
1154 else if(FC(i, j - 1, filters) == BLUE && FC(i, j + 1, filters) == BLUE)
1155 {
1156 // we are on a red row, so interpolate row-wise
1157 B = (west + east) / 2.f;
1158 B_clipped = (west > clips[BLUE] || east > clips[BLUE]);
1159 }
1160 else
1161 {
1162 // we are on a red row, so interpolate inside a square
1163 B = (north_west + north_east + south_east + south_west) / 4.f;
1164
1165 B_clipped = (north_west > clips[BLUE] || north_east > clips[BLUE] || south_west > clips[BLUE]
1166 || south_east > clips[BLUE]);
1167 }
1168 }
1169 }
1170
1171 dt_aligned_pixel_t RGB = { R, G, B, sqrtf(sqf(R) + sqf(G) + sqf(B)) };
1172 dt_aligned_pixel_t clipped = { R_clipped, G_clipped, B_clipped, (R_clipped || G_clipped || B_clipped) };
1173
1174 for_each_channel(k, aligned(RGB, interpolated, clipping_mask, clipped, wb))
1175 {
1176 const size_t idx = (i * width + j) * 4 + k;
1177 interpolated[idx] = fmaxf(RGB[k] / wb[k], 0.f);
1178 clipping_mask[idx] = clipped[k];
1179 }
1180 }
1181
1182 }
1183
1192static void _compute_laplacian_normalization(const float *const restrict input,
1193 const dt_iop_roi_t *const roi_in,
1194 const uint32_t filters,
1195 const uint8_t (*const xtrans)[6],
1196 dt_aligned_pixel_t normalization)
1197{
1198 float sum_R = 0.f;
1199 float sum_G = 0.f;
1200 float sum_B = 0.f;
1201 const float n_pixels = roi_in->height * roi_in->width;
1202 __OMP_PARALLEL_FOR__(collapse(2) reduction(+:sum_R, sum_G, sum_B))
1203 for(size_t i = 0; i < roi_in->height; i++)
1204 for(size_t j = 0; j < roi_in->width; j++)
1205 {
1206 const int c = (filters == 9u) ? FCxtrans((int)i, (int)j, roi_in, xtrans) : FC(i, j, filters);
1207 if(c < 0 || c > 2) continue;
1208
1209 const float value = input[i * roi_in->width + j] / n_pixels;
1210 if(c == RED)
1211 sum_R += value;
1212 else if(c == GREEN)
1213 sum_G += value;
1214 else
1215 sum_B += value;
1216 }
1217
1218 normalization[RED] = sum_R;
1219 normalization[GREEN] = sum_G;
1220 normalization[BLUE] = sum_B;
1221 normalization[ALPHA] = 1.f;
1222}
1223
1231static void _build_xtrans_bilinear_lookup(int32_t lookup[6][6][32],
1232 const dt_iop_roi_t *const roi_in,
1233 const uint8_t (*const xtrans)[6])
1234{
1235 __OMP_PARALLEL_FOR__(collapse(2))
1236 for(int row = 0; row < 6; row++)
1237 for(int col = 0; col < 6; col++)
1238 {
1239 int32_t *ip = &(lookup[row][col][1]);
1240 int sum[3] = { 0 };
1241 const int f = FCxtrans(row, col, roi_in, xtrans);
1242
1243 // Loop over the local 3x3 support and keep every weighted contributor of
1244 // the missing colors visible in the lookup table.
1245 for(int y = -1; y <= 1; y++)
1246 for(int x = -1; x <= 1; x++)
1247 {
1248 const int weight = 1 << ((y == 0) + (x == 0));
1249 const int color = FCxtrans(row + y, col + x, roi_in, xtrans);
1250 if(color == f) continue;
1251 *ip++ = (y << 16) | (x & 0xffffu);
1252 *ip++ = weight;
1253 *ip++ = color;
1254 sum[color] += weight;
1255 }
1256
1257 lookup[row][col][0] = (ip - &(lookup[row][col][0])) / 3;
1258 for(int c = 0; c < 3; c++)
1259 if(c != f)
1260 {
1261 *ip++ = c;
1262 *ip++ = sum[c];
1263 }
1264 *ip = f;
1265 }
1266
1267}
1268
1276static void _interpolate_and_mask_xtrans(const float *const restrict input,
1277 float *const restrict interpolated,
1278 float *const restrict clipping_mask,
1279 const dt_aligned_pixel_t clips,
1280 const dt_aligned_pixel_t wb,
1281 const dt_iop_roi_t *const roi_in,
1282 const int32_t lookup[6][6][32],
1283 const uint8_t (*const xtrans)[6],
1284 const size_t width, const size_t height)
1285{
1286 __OMP_PARALLEL_FOR__(collapse(2))
1287 for(size_t i = 0; i < height; i++)
1288 for(size_t j = 0; j < width; j++)
1289 {
1290 const size_t idx = i * width + j;
1291 const float center = input[idx];
1292
1293 dt_aligned_pixel_t RGB = { 0.f };
1294 dt_aligned_pixel_t clipped = { 0.f };
1295
1296 if(i == 0 || j == 0 || i == height - 1 || j == width - 1)
1297 {
1298 dt_aligned_pixel_t sum = { 0.f };
1299 int count[3] = { 0 };
1300 int used_clipped[3] = { 0 };
1301 const int f = FCxtrans((int)i, (int)j, roi_in, xtrans);
1302
1303 // Along tile borders we average only the available neighbours because
1304 // the full 3x3 support would otherwise leave the current ROI.
1305 for(int y = MAX((int)i - 1, 0); y <= MIN((int)i + 1, (int)height - 1); y++)
1306 for(int x = MAX((int)j - 1, 0); x <= MIN((int)j + 1, (int)width - 1); x++)
1307 {
1308 const int color = FCxtrans(y, x, roi_in, xtrans);
1309 const float value = input[(size_t)y * width + x];
1310 sum[color] += value;
1311 count[color]++;
1312 used_clipped[color] |= (value > clips[color]);
1313 }
1314
1315 for(int c = 0; c < 3; c++)
1316 {
1317 const int has_samples = (count[c] > 0);
1318 RGB[c] = (c == f || !has_samples) ? center : sum[c] / count[c];
1319 clipped[c] = (c == f || !has_samples) ? (center > clips[c]) : used_clipped[c];
1320 }
1321 }
1322 else
1323 {
1324 const int32_t *ip = &(lookup[i % 6][j % 6][0]);
1325 dt_aligned_pixel_t sum = { 0.f };
1326 int used_clipped[3] = { 0 };
1327 const int neighbours = *ip++;
1328
1329 // We are looping on every neighbour that contributes to a missing color
1330 // so the interpolation follows the X-Trans CFA geometry exactly.
1331 for(int k = 0; k < neighbours; k++, ip += 3)
1332 {
1333 const int32_t offset = ip[0];
1334 const int x = (int16_t)(offset & 0xffffu);
1335 const int y = (int16_t)(offset >> 16);
1336 const size_t neighbour = ((size_t)((int)i + y) * width + (size_t)((int)j + x));
1337 const int color = ip[2];
1338 const float value = input[neighbour];
1339 sum[color] += value * ip[1];
1340 used_clipped[color] |= (value > clips[color]);
1341 }
1342
1343 // Normalize the two missing colors from the accumulated weights, then
1344 // restore the measured center color unchanged.
1345 for(int k = 0; k < 2; k++, ip += 2)
1346 {
1347 const int color = ip[0];
1348 const int total = ip[1];
1349 RGB[color] = (total > 0) ? sum[color] / total : center;
1350 clipped[color] = used_clipped[color];
1351 }
1352
1353 const int f = *ip;
1354 RGB[f] = center;
1355 clipped[f] = (center > clips[f]);
1356 }
1357
1358 RGB[ALPHA] = sqrtf(sqf(RGB[RED]) + sqf(RGB[GREEN]) + sqf(RGB[BLUE]));
1359 clipped[ALPHA] = (clipped[RED] || clipped[GREEN] || clipped[BLUE]);
1360
1361 for_each_channel(k, aligned(RGB, interpolated, clipping_mask, clipped, wb))
1362 {
1363 const size_t index = idx * 4 + k;
1364 interpolated[index] = fmaxf(RGB[k] / wb[k], 0.f);
1365 clipping_mask[index] = clipped[k];
1366 }
1367 }
1368
1369}
1370
1372static void _remosaic_and_replace(const float *const restrict input,
1373 const float *const restrict interpolated,
1374 const float *const restrict clipping_mask,
1375 float *const restrict output,
1376 const dt_aligned_pixel_t wb,
1377 const uint32_t filters,
1378 const size_t width, const size_t height)
1379{
1380 // Take RGB ratios and norm, reconstruct RGB and remosaic the image
1381 __OMP_PARALLEL_FOR__(collapse(2))
1382 for(size_t i = 0; i < height; i++)
1383 for(size_t j = 0; j < width; j++)
1384 {
1385 const size_t c = FC(i, j, filters);
1386 const size_t idx = i * width + j;
1387 const size_t index = idx * 4;
1388 const float opacity = clipping_mask[index + ALPHA];
1389 output[idx] = opacity * fmaxf(interpolated[index + c] * wb[c], 0.f)
1390 + (1.f - opacity) * input[idx];
1391 }
1392
1393}
1394
1397static void _remosaic_and_replace_xtrans(const float *const restrict input,
1398 const float *const restrict interpolated,
1399 const float *const restrict clipping_mask,
1400 float *const restrict output,
1401 const dt_aligned_pixel_t wb,
1402 const dt_iop_roi_t *const roi_in,
1403 const uint8_t (*const xtrans)[6],
1404 const size_t width, const size_t height)
1405{
1406 __OMP_PARALLEL_FOR__(collapse(2))
1407 for(size_t i = 0; i < height; i++)
1408 for(size_t j = 0; j < width; j++)
1409 {
1410 const size_t idx = i * width + j;
1411 const size_t index = idx * 4;
1412 const int c = FCxtrans((int)i, (int)j, roi_in, xtrans);
1413 const float opacity = clipping_mask[index + ALPHA];
1414 output[idx] = opacity * fmaxf(interpolated[index + c] * wb[c], 0.f)
1415 + (1.f - opacity) * input[idx];
1416 }
1417
1418}
1419
1425
1426
1428{
1429 ANY_SCALE = 1 << 0, // any wavelets scale : reconstruct += HF
1430 FIRST_SCALE = 1 << 1, // first wavelets scale : reconstruct = 0
1431 LAST_SCALE = 1 << 2, // last wavelets scale : reconstruct += residual
1432};
1433
1434
1435static inline __attribute__((always_inline)) uint8_t scale_type(const int s, const int scales)
1436{
1437 uint8_t scale = ANY_SCALE;
1438 if(s == 0) scale |= FIRST_SCALE;
1439 if(s == scales - 1) scale |= LAST_SCALE;
1440 return scale;
1441}
1442
1443
1445static inline void guide_laplacians(const float *const restrict high_freq, const float *const restrict low_freq,
1446 const float *const restrict clipping_mask,
1447 float *const restrict output, const size_t width, const size_t height,
1448 const int mult, const float noise_level, const int salt,
1449 const uint8_t scale, const float radius_sq)
1450{
1451 float *const restrict out = DT_IS_ALIGNED(output);
1452 const float *const restrict LF = DT_IS_ALIGNED(low_freq);
1453 const float *const restrict HF = DT_IS_ALIGNED(high_freq);
1454 const dt_aligned_pixel_simd_t zero = dt_simd_set1(0.f);
1455 const dt_aligned_pixel_simd_t ones = dt_simd_set1(1.f);
1456 const dt_aligned_pixel_simd_t inv_patch = dt_simd_set1(1.f / 9.f);
1457 const dt_aligned_pixel_simd_t scale_multiplier = dt_simd_set1(1.f / radius_sq);
1458 const float eps = 1e-12f;
1460 for(size_t row = 0; row < height; ++row)
1461 {
1462 // interleave the order in which we process the rows so that we minimize cache misses
1463 const int i = dwt_interleave_rows(row, height, mult);
1464 const float *const row0 = HF + 4 * ((size_t)MAX(i - mult, 0) * width);
1465 const float *const row1 = HF + 4 * ((size_t)i * width);
1466 const float *const row2 = HF + 4 * ((size_t)MIN(i + mult, (int)height - 1) * width);
1467 const float *const rows[3] = { row0, row1, row2 };
1468 const int max_col = (int)width - 1;
1469
1470 for(int j = 0; j < width; ++j)
1471 {
1472 const size_t idx = (i * width + j);
1473 const size_t index = idx * 4;
1474 const float alpha = clipping_mask[index + ALPHA];
1475 const float alpha_comp = 1.f - alpha;
1476 dt_aligned_pixel_simd_t high_frequency = dt_load_simd_aligned(HF + index);
1477
1478 if(alpha > 0.f) // reconstruct
1479 {
1480 const int col_offsets[3]
1481 = { 4 * MAX(j - mult, 0),
1482 4 * j,
1483 4 * MIN(j + mult, max_col) };
1484 dt_aligned_pixel_simd_t sum = zero;
1485 dt_aligned_pixel_simd_t sum_sq = zero;
1486 dt_aligned_pixel_simd_t prod_r = zero;
1487 dt_aligned_pixel_simd_t prod_g = zero;
1488 dt_aligned_pixel_simd_t prod_b = zero;
1489
1490 // Walk the dense 3x3 neighbourhood as counted loops so GCC keeps the
1491 // fit as a regular reduction instead of fully unrolling all 9 taps and
1492 // spilling the intermediate moments to the stack.
1493#if defined(__GNUC__) && !defined(__clang__)
1494#pragma GCC unroll 1
1495#endif
1496 for(int jj = 0; jj < 3; ++jj)
1497 {
1498 const float *const row_ptr = rows[jj];
1499#if defined(__GNUC__) && !defined(__clang__)
1500#pragma GCC unroll 1
1501#endif
1502 for(int ii = 0; ii < 3; ++ii)
1503 {
1504 const dt_aligned_pixel_simd_t sample = dt_load_simd_aligned(row_ptr + col_offsets[ii]);
1505
1506 sum += sample;
1507 sum_sq += sample * sample;
1508 prod_r += sample * dt_simd_set1(sample[RED]);
1509 prod_g += sample * dt_simd_set1(sample[GREEN]);
1510 prod_b += sample * dt_simd_set1(sample[BLUE]);
1511 }
1512 }
1513
1514 dt_aligned_pixel_simd_t means = sum * inv_patch;
1515 dt_aligned_pixel_simd_t variance = sum_sq * inv_patch - means * means;
1516 variance = dt_simd_max_zero(variance);
1517 variance[ALPHA] = 0.f;
1518
1519 size_t guiding_channel = RED;
1520 float guide_variance = variance[RED];
1521 if(variance[GREEN] > guide_variance)
1522 {
1523 guiding_channel = GREEN;
1524 guide_variance = variance[GREEN];
1525 }
1526 if(variance[BLUE] > guide_variance)
1527 {
1528 guiding_channel = BLUE;
1529 guide_variance = variance[BLUE];
1530 }
1531
1532 if(guide_variance > eps)
1533 {
1534 const float guide_mean = means[guiding_channel];
1535 dt_aligned_pixel_simd_t covariance
1536 = (guiding_channel == RED ? prod_r : (guiding_channel == GREEN ? prod_g : prod_b)) * inv_patch
1537 - means * dt_simd_set1(guide_mean);
1538 dt_aligned_pixel_simd_t slope = covariance / dt_simd_set1(guide_variance);
1539 slope = dt_simd_max_zero(slope);
1540 dt_aligned_pixel_simd_t intercept = means - slope * dt_simd_set1(guide_mean);
1541 const dt_aligned_pixel_simd_t blend = dt_load_simd_aligned(clipping_mask + index) * scale_multiplier;
1542 const dt_aligned_pixel_simd_t guide = dt_simd_set1(high_frequency[guiding_channel]);
1543 high_frequency = blend * (slope * guide + intercept) + (ones - blend) * high_frequency;
1544 }
1545 }
1546
1547 dt_aligned_pixel_simd_t out_pixel = high_frequency;
1548 if((scale & FIRST_SCALE))
1549 {
1550 // out is not inited yet
1551 }
1552 else
1553 {
1554 // just accumulate HF
1555 out_pixel += dt_load_simd_aligned(out + index);
1556 }
1557
1558 if((scale & LAST_SCALE))
1559 {
1560 // add the residual and clamp
1561 out_pixel = dt_simd_max_zero(out_pixel + dt_load_simd_aligned(LF + index));
1562 }
1563
1564 // Last step of RGB reconstruct : add noise
1565 if((scale & LAST_SCALE) && salt && alpha > 0.f)
1566 {
1567 // Init random number generator
1568 uint32_t DT_ALIGNED_ARRAY state[4] = { splitmix32(j + 1), splitmix32((j + 1) * (i + 3)), splitmix32(1337), splitmix32(666) };
1573
1574 dt_aligned_pixel_t noise = { 0.f };
1575 dt_aligned_pixel_t sigma = { 0.20f };
1576 const int DT_ALIGNED_ARRAY flip[4] = { TRUE, FALSE, TRUE, FALSE };
1577
1578 sigma[RED] = out_pixel[RED] * noise_level;
1579 sigma[GREEN] = out_pixel[GREEN] * noise_level;
1580 sigma[BLUE] = out_pixel[BLUE] * noise_level;
1581 sigma[ALPHA] = out_pixel[ALPHA] * noise_level;
1582
1583 // create statistical noise
1584 dt_aligned_pixel_t current = { out_pixel[RED], out_pixel[GREEN], out_pixel[BLUE], out_pixel[ALPHA] };
1586
1587 // Save the noisy interpolated image
1588 for_each_channel(c, aligned(noise, current))
1589 noise[c] = current[c] + fabsf(noise[c] - current[c]);
1590
1591 out_pixel[RED] = fmaxf(alpha * noise[RED] + alpha_comp * current[RED], 0.f);
1592 out_pixel[GREEN] = fmaxf(alpha * noise[GREEN] + alpha_comp * current[GREEN], 0.f);
1593 out_pixel[BLUE] = fmaxf(alpha * noise[BLUE] + alpha_comp * current[BLUE], 0.f);
1594 out_pixel[ALPHA] = fmaxf(alpha * noise[ALPHA] + alpha_comp * current[ALPHA], 0.f);
1595 }
1596
1597 if((scale & LAST_SCALE))
1598 {
1599 // Break the RGB channels into ratios/norm for the next step of reconstruction
1600 const float norm = fmaxf(sqrtf(sqf(out_pixel[RED]) + sqf(out_pixel[GREEN]) + sqf(out_pixel[BLUE])), 1e-6f);
1601 out_pixel /= dt_simd_set1(norm);
1602 out_pixel[ALPHA] = norm;
1603 }
1604
1605 dt_store_simd_aligned(out + index, out_pixel);
1606 }
1607 }
1608
1609}
1610
1612static inline void heat_PDE_diffusion(const float *const restrict high_freq, const float *const restrict low_freq,
1613 const float *const restrict clipping_mask,
1614 float *const restrict output, const size_t width, const size_t height,
1615 const int mult, const uint8_t scale,
1616 const float first_order_factor)
1617{
1618 // Simultaneous inpainting for image structure and texture using anisotropic heat transfer model
1619 // https://www.researchgate.net/publication/220663968
1620 // modified as follow :
1621 // * apply it in a multi-scale wavelet setup : we basically solve it twice, on the wavelets LF and HF layers.
1622 // * replace the manual texture direction/distance selection by an automatic detection similar to the structure one,
1623 // * generalize the framework for isotropic diffusion and anisotropic weighted on the isophote direction
1624 // * add a variance regularization to better avoid edges.
1625 // The sharpness setting mimics the contrast equalizer effect by simply multiplying the HF by some gain.
1626
1627 float *const restrict out = DT_IS_ALIGNED(output);
1628 const float *const restrict LF = DT_IS_ALIGNED(low_freq);
1629 const float *const restrict HF = DT_IS_ALIGNED(high_freq);
1631 for(size_t row = 0; row < height; ++row)
1632 {
1633 // interleave the order in which we process the rows so that we minimize cache misses
1634 const size_t i = dwt_interleave_rows(row, height, mult);
1635 // compute the 'above' and 'below' coordinates, clamping them to the image, once for the entire row
1636 const size_t i_neighbours[3]
1637 = { MAX((int)(i - mult), (int)0) * width, // x - mult
1638 i * width, // x
1639 MIN((int)(i + mult), (int)height - 1) * width }; // x + mult
1640
1641 static const float DT_ALIGNED_ARRAY anisotropic_kernel_isophote[9]
1642 = { 0.25f, 0.5f, 0.25f, 0.5f, -3.f, 0.5f, 0.25f, 0.5f, 0.25f };
1643
1644 for(size_t j = 0; j < width; ++j)
1645 {
1646 const size_t idx = (i * width + j);
1647 const size_t index = idx * 4;
1648
1649 // fetch the clipping mask opacity : opaque (alpha = 100 %) where clipped
1650 const dt_aligned_pixel_t alpha = { clipping_mask[index + RED],
1651 clipping_mask[index + GREEN],
1652 clipping_mask[index + BLUE],
1653 clipping_mask[index + ALPHA] };
1654
1655 dt_aligned_pixel_t high_frequency = { HF[index + 0], HF[index + 1], HF[index + 2], HF[index + 3] };
1656
1657 // The for_each_channel macro uses 4 floats SIMD instructions or 3 float regular ops,
1658 // depending on system. Since we don't want to diffuse the norm, make sure to store and restore it later.
1659 // This is not much of an issue when processing image at full-res, but more harmful since
1660 // we reconstruct highlights on a downscaled variant
1661 const float norm_backup = high_frequency[3];
1662
1663 if(alpha[ALPHA] > 0.f) // reconstruct
1664 {
1665 // non-local neighbours coordinates
1666 const size_t j_neighbours[3]
1667 = { MAX((int)(j - mult), (int)0), // y - mult
1668 j, // y
1669 MIN((int)(j + mult), (int)width - 1) }; // y + mult
1670
1671 // fetch non-local pixels and store them locally and contiguously
1672 dt_aligned_pixel_t neighbour_pixel_HF[9];
1673 for_four_channels(c, aligned(neighbour_pixel_HF, HF: 16))
1674 {
1675 neighbour_pixel_HF[3 * 0 + 0][c] = HF[4 * (i_neighbours[0] + j_neighbours[0]) + c];
1676 neighbour_pixel_HF[3 * 0 + 1][c] = HF[4 * (i_neighbours[0] + j_neighbours[1]) + c];
1677 neighbour_pixel_HF[3 * 0 + 2][c] = HF[4 * (i_neighbours[0] + j_neighbours[2]) + c];
1678
1679 neighbour_pixel_HF[3 * 1 + 0][c] = HF[4 * (i_neighbours[1] + j_neighbours[0]) + c];
1680 neighbour_pixel_HF[3 * 1 + 1][c] = HF[4 * (i_neighbours[1] + j_neighbours[1]) + c];
1681 neighbour_pixel_HF[3 * 1 + 2][c] = HF[4 * (i_neighbours[1] + j_neighbours[2]) + c];
1682
1683 neighbour_pixel_HF[3 * 2 + 0][c] = HF[4 * (i_neighbours[2] + j_neighbours[0]) + c];
1684 neighbour_pixel_HF[3 * 2 + 1][c] = HF[4 * (i_neighbours[2] + j_neighbours[1]) + c];
1685 neighbour_pixel_HF[3 * 2 + 2][c] = HF[4 * (i_neighbours[2] + j_neighbours[2]) + c];
1686 }
1687
1688 // Compute the laplacian in the direction parallel to the steepest gradient on the norm
1689 // Convolve the filter to get the laplacian
1690 dt_aligned_pixel_t laplacian_HF = { 0.f, 0.f, 0.f, 0.f };
1691 for(int k = 0; k < 9; k++)
1692 {
1693 for_each_channel(c, aligned(laplacian_HF, neighbour_pixel_HF:16) aligned(anisotropic_kernel_isophote: 64))
1694 laplacian_HF[c] += neighbour_pixel_HF[k][c] * anisotropic_kernel_isophote[k];
1695 }
1696
1697 // Diffuse
1698 const dt_aligned_pixel_t multipliers_HF = { 1.f / B_SPLINE_TO_LAPLACIAN, 1.f / B_SPLINE_TO_LAPLACIAN, 1.f / B_SPLINE_TO_LAPLACIAN, 0.f };
1699 for_each_channel(c, aligned(high_frequency, multipliers_HF, laplacian_HF, alpha))
1700 high_frequency[c] += alpha[c] * multipliers_HF[c] * (laplacian_HF[c] - first_order_factor * high_frequency[c]);
1701
1702 // Restore. See above.
1703 high_frequency[3] = norm_backup;
1704 }
1705
1706 if((scale & FIRST_SCALE))
1707 {
1708 // out is not inited yet
1709 for_each_channel(c, aligned(out, high_frequency : 64))
1710 out[index + c] = high_frequency[c];
1711 }
1712 else
1713 {
1714 // just accumulate HF
1715 for_each_channel(c, aligned(out, high_frequency : 64))
1716 out[index + c] += high_frequency[c];
1717 }
1718
1719 if((scale & LAST_SCALE))
1720 {
1721 // add the residual and clamp
1722 for_each_channel(c, aligned(out, LF, high_frequency : 64))
1723 out[index + c] = fmaxf(out[index + c] + LF[index + c], 0.f);
1724
1725 // renormalize ratios
1726 if(alpha[ALPHA] > 0.f)
1727 {
1728 const float norm = sqrtf(sqf(out[index + RED]) + sqf(out[index + GREEN]) + sqf(out[index + BLUE]));
1729 for_each_channel(c, aligned(out, LF, high_frequency : 64))
1730 out[index + c] /= (c != ALPHA && norm > 1e-4f) ? norm : 1.f;
1731 }
1732
1733 // Last scale : reconstruct RGB from ratios and norm - norm stays in the 4th channel
1734 // we need it to evaluate the gradient
1735 for_four_channels(c, aligned(out))
1736 out[index + c] = (c == ALPHA) ? out[index + ALPHA] : out[index + c] * out[index + ALPHA];
1737 }
1738 }
1739 }
1740
1741}
1742
1743static inline int wavelets_process(const float *const restrict in, float
1744 *const restrict reconstructed,
1745 const float *const restrict clipping_mask,
1746 const size_t width, const size_t height,
1747 const int scales,
1748 float *const restrict HF,
1749 float *const restrict LF_odd,
1750 float *const restrict LF_even,
1751 const diffuse_reconstruct_variant_t variant,
1752 const float noise_level,
1753 const int salt, const float first_order_factor)
1754{
1755 // À trous decimated wavelet decompose
1756 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
1757 // the wavelets decomposition here is the same as the equalizer/atrous module,
1758
1759 // allocate a one-row temporary buffer for the decomposition
1760 size_t padded_size;
1761 float *const tempbuf = dt_pixelpipe_cache_alloc_perthread_float(4 * width, &padded_size); //TODO: alloc in caller
1762 if(IS_NULL_PTR(tempbuf)) return 1;
1763
1764 for(int s = 0; s < scales; ++s)
1765 {
1766 //fprintf(stderr, "CPU Wavelet decompose : scale %i\n", s);
1767 const int mult = 1 << s;
1768
1769 const float *restrict buffer_in;
1770 float *restrict buffer_out;
1771
1772 if(s == 0)
1773 {
1774 buffer_in = in;
1775 buffer_out = LF_odd;
1776 }
1777 else if(s % 2 != 0)
1778 {
1779 buffer_in = LF_odd;
1780 buffer_out = LF_even;
1781 }
1782 else
1783 {
1784 buffer_in = LF_even;
1785 buffer_out = LF_odd;
1786 }
1787
1788 decompose_2D_Bspline(buffer_in, HF, buffer_out, width, height, mult, tempbuf, padded_size);
1789
1790 uint8_t current_scale_type = scale_type(s, scales);
1791 const float radius = sqf(equivalent_sigma_at_step(B_SPLINE_SIGMA, s * DS_FACTOR));
1792
1793 if(variant == DIFFUSE_RECONSTRUCT_RGB)
1794 guide_laplacians(HF, buffer_out, clipping_mask, reconstructed, width, height, mult, noise_level, salt, current_scale_type, radius);
1795 else
1796 heat_PDE_diffusion(HF, buffer_out, clipping_mask, reconstructed, width, height, mult, current_scale_type, first_order_factor);
1797
1798#if DEBUG_DUMP_PFM
1799 char name[64];
1800 sprintf(name, "/tmp/scale-input-%i.pfm", s);
1801 dump_PFM(name, buffer_in, width, height);
1802
1803 sprintf(name, "/tmp/scale-blur-%i.pfm", s);
1804 dump_PFM(name, buffer_out, width, height);
1805#endif
1806 }
1808
1809 return 0;
1810}
1811
1812
1815 const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
1816 void *const restrict ovoid,
1817 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
1818 const dt_aligned_pixel_t clips)
1819{
1821 int err = 0;
1822
1823 const uint32_t filters = piece->dsc_in.filters;
1824
1825 const size_t height = roi_in->height;
1826 const size_t width = roi_in->width;
1827 const size_t size = roi_in->width * roi_in->height;
1828
1829 const size_t ds_height = height / DS_FACTOR;
1830 const size_t ds_width = width / DS_FACTOR;
1831 const size_t ds_size = ds_height * ds_width;
1832
1833 float *const restrict interpolated = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe); // [R, G, B, norm] for each pixel
1834 float *const restrict clipping_mask = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe); // [R, G, B, norm] for each pixel
1835
1836 // temp buffer for blurs. We will need to cycle between them for memory efficiency
1837 float *const restrict LF_odd = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1838 float *const restrict LF_even = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1839 float *const restrict temp = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1840
1841 const float scale = DS_FACTOR * dt_dev_get_module_scale(pipe, roi_in);
1842 const float final_radius = (float)((int)(1 << data->scales)) / scale;
1843 const int scales = CLAMP((int)ceilf(log2f(final_radius)), 1, MAX_NUM_SCALES);
1844
1845 const float noise_level = data->noise_level / scale;
1846
1847 // wavelets scales buffers
1848 float *restrict HF = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1849 float *restrict ds_interpolated = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1850 float *restrict ds_clipping_mask = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1851
1852 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask) || IS_NULL_PTR(LF_odd) || IS_NULL_PTR(LF_even) || IS_NULL_PTR(temp) || IS_NULL_PTR(HF) || IS_NULL_PTR(ds_interpolated) || IS_NULL_PTR(ds_clipping_mask))
1853 {
1854 err = 1;
1855 goto error;
1856 }
1857
1858 const float *const restrict input = (const float *const restrict)ivoid;
1859 float *const restrict output = (float *const restrict)ovoid;
1860 dt_aligned_pixel_t normalization = { 1.f, 1.f, 1.f, 1.f };
1861 _compute_laplacian_normalization(input, roi_in, filters, NULL, normalization);
1862
1863 _interpolate_and_mask(input, interpolated, clipping_mask, clips, normalization, filters, width, height);
1864 if(dt_box_mean(clipping_mask, height, width, 4, 2, 1) != 0)
1865 {
1866 err = 1;
1867 goto error;
1868 }
1869
1870 // Downsample
1871 interpolate_bilinear(clipping_mask, width, height, ds_clipping_mask, ds_width, ds_height, 4);
1872 interpolate_bilinear(interpolated, width, height, ds_interpolated, ds_width, ds_height, 4);
1873
1874 for(int i = 0; i < data->iterations; i++)
1875 {
1876 const int salt = (i == data->iterations - 1); // add noise on the last iteration only
1877 if(wavelets_process(ds_interpolated, temp, ds_clipping_mask, ds_width, ds_height, scales, HF, LF_odd,
1878 LF_even, DIFFUSE_RECONSTRUCT_RGB, noise_level, salt, data->solid_color))
1879 {
1880 err = 1;
1881 goto error;
1882 }
1883 if(wavelets_process(temp, ds_interpolated, ds_clipping_mask, ds_width, ds_height, scales, HF, LF_odd,
1884 LF_even, DIFFUSE_RECONSTRUCT_CHROMA, noise_level, salt, data->solid_color))
1885 {
1886 err = 1;
1887 goto error;
1888 }
1889 }
1890
1891 // Upsample
1892 interpolate_bilinear(ds_interpolated, ds_width, ds_height, interpolated, width, height, 4);
1893 _remosaic_and_replace(input, interpolated, clipping_mask, output, normalization, filters, width, height);
1894
1895#if DEBUG_DUMP_PFM
1896 dump_PFM("/tmp/interpolated.pfm", interpolated, width, height);
1897 dump_PFM("/tmp/clipping_mask.pfm", clipping_mask, width, height);
1898#endif
1899
1900error:;
1901 dt_pixelpipe_cache_free_align(interpolated);
1902 dt_pixelpipe_cache_free_align(clipping_mask);
1907 dt_pixelpipe_cache_free_align(ds_interpolated);
1908 dt_pixelpipe_cache_free_align(ds_clipping_mask);
1909 return err;
1910}
1911
1914 const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
1915 void *const restrict ovoid,
1916 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
1917 const dt_aligned_pixel_t clips)
1918{
1920 int err = 0;
1921 (void)roi_out;
1922
1923 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
1924
1925 const size_t height = roi_in->height;
1926 const size_t width = roi_in->width;
1927 const size_t size = roi_in->width * roi_in->height;
1928
1929 const size_t ds_height = height / DS_FACTOR;
1930 const size_t ds_width = width / DS_FACTOR;
1931 const size_t ds_size = ds_height * ds_width;
1932
1933 float *const restrict interpolated = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe);
1934 float *const restrict clipping_mask = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe);
1935 float *const restrict LF_odd = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1936 float *const restrict LF_even = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1937 float *const restrict temp = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1938
1939 const float scale = DS_FACTOR * dt_dev_get_module_scale(pipe, roi_in);
1940 const float final_radius = (float)((int)(1 << data->scales)) / scale;
1941 const int scales = CLAMP((int)ceilf(log2f(final_radius)), 1, MAX_NUM_SCALES);
1942 const float noise_level = data->noise_level / scale;
1943
1944 float *restrict HF = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1945 float *restrict ds_interpolated = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1946 float *restrict ds_clipping_mask = dt_pixelpipe_cache_alloc_align_float(ds_size * 4, pipe);
1947
1948 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask) || IS_NULL_PTR(LF_odd) || IS_NULL_PTR(LF_even) || IS_NULL_PTR(temp) || IS_NULL_PTR(HF) || IS_NULL_PTR(ds_interpolated) || IS_NULL_PTR(ds_clipping_mask))
1949 {
1950 err = 1;
1951 goto error;
1952 }
1953
1954 const float *const restrict input = (const float *const restrict)ivoid;
1955 float *const restrict output = (float *const restrict)ovoid;
1956 dt_aligned_pixel_t normalization = { 1.f, 1.f, 1.f, 1.f };
1957 int32_t lookup[6][6][32] = { { { 0 } } };
1958
1959 _compute_laplacian_normalization(input, roi_in, 9u, xtrans, normalization);
1960 _build_xtrans_bilinear_lookup(lookup, roi_in, xtrans);
1961 _interpolate_and_mask_xtrans(input, interpolated, clipping_mask, clips, normalization, roi_in, lookup, xtrans, width, height);
1962 if(dt_box_mean(clipping_mask, height, width, 4, 2, 1) != 0)
1963 {
1964 err = 1;
1965 goto error;
1966 }
1967
1968 interpolate_bilinear(clipping_mask, width, height, ds_clipping_mask, ds_width, ds_height, 4);
1969 interpolate_bilinear(interpolated, width, height, ds_interpolated, ds_width, ds_height, 4);
1970
1971 for(int i = 0; i < data->iterations; i++)
1972 {
1973 const int salt = (i == data->iterations - 1);
1974 if(wavelets_process(ds_interpolated, temp, ds_clipping_mask, ds_width, ds_height, scales, HF, LF_odd,
1975 LF_even, DIFFUSE_RECONSTRUCT_RGB, noise_level, salt, data->solid_color))
1976 {
1977 err = 1;
1978 goto error;
1979 }
1980 if(wavelets_process(temp, ds_interpolated, ds_clipping_mask, ds_width, ds_height, scales, HF, LF_odd,
1981 LF_even, DIFFUSE_RECONSTRUCT_CHROMA, noise_level, salt, data->solid_color))
1982 {
1983 err = 1;
1984 goto error;
1985 }
1986 }
1987
1988 interpolate_bilinear(ds_interpolated, ds_width, ds_height, interpolated, width, height, 4);
1989 _remosaic_and_replace_xtrans(input, interpolated, clipping_mask, output, normalization, roi_in, xtrans, width, height);
1990
1991error:
1992 dt_pixelpipe_cache_free_align(interpolated);
1993 dt_pixelpipe_cache_free_align(clipping_mask);
1998 dt_pixelpipe_cache_free_align(ds_interpolated);
1999 dt_pixelpipe_cache_free_align(ds_clipping_mask);
2000 return err;
2001}
2002
2003#ifdef HAVE_OPENCL
2004static inline cl_int wavelets_process_cl(const int devid,
2005 cl_mem in, cl_mem reconstructed,
2006 cl_mem reconstructed_scratch,
2007 cl_mem clipping_mask,
2008 const size_t sizes[3], const int width, const int height,
2010 const int scales,
2011 cl_mem HF,
2012 cl_mem LF_odd,
2013 cl_mem LF_even,
2014 const diffuse_reconstruct_variant_t variant,
2015 const float noise_level,
2016 const int salt, const float solid_color)
2017{
2018 cl_int err = DT_OPENCL_DEFAULT_ERROR;
2019 cl_mem reconstruct_read = reconstructed_scratch;
2020
2021 // À trous wavelet decompose
2022 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
2023 // the wavelets decomposition here is the same as the equalizer/atrous module,
2024 for(int s = 0; s < scales; ++s)
2025 {
2026 //fprintf(stderr, "GPU Wavelet decompose : scale %i\n", s);
2027 const int mult = 1 << s;
2028
2029 cl_mem buffer_in;
2030 cl_mem buffer_out;
2031
2032 if(s == 0)
2033 {
2034 buffer_in = in;
2035 buffer_out = LF_odd;
2036 }
2037 else if(s % 2 != 0)
2038 {
2039 buffer_in = LF_odd;
2040 buffer_out = LF_even;
2041 }
2042 else
2043 {
2044 buffer_in = LF_even;
2045 buffer_out = LF_odd;
2046 }
2047
2048 // Compute wavelets low-frequency scales
2049 const int clamp_lf = 1;
2050 int hblocksize;
2051 dt_opencl_local_buffer_t hlocopt = (dt_opencl_local_buffer_t){ .xoffset = 2 * mult, .xfactor = 1,
2052 .yoffset = 0, .yfactor = 1,
2053 .cellsize = 4 * sizeof(float), .overhead = 0,
2054 .sizex = 1 << 16, .sizey = 1 };
2056 hblocksize = hlocopt.sizex;
2057 else
2058 hblocksize = 1;
2059
2060 if(hblocksize > 1)
2061 {
2062 const size_t horizontal_sizes[3] = { ROUNDUP(width, hblocksize), ROUNDUPDHT(height, devid), 1 };
2063 const size_t horizontal_local[3] = { hblocksize, 1, 1 };
2064 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 0, sizeof(cl_mem), (void *)&buffer_in);
2065 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 1, sizeof(cl_mem), (void *)&HF);
2066 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 2, sizeof(int), (void *)&width);
2067 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 3, sizeof(int), (void *)&height);
2068 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 4, sizeof(int), (void *)&mult);
2069 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 5, sizeof(int), (void *)&clamp_lf);
2071 (hblocksize + 4 * mult) * 4 * sizeof(float), NULL);
2073 horizontal_sizes, horizontal_local);
2074 }
2075 else
2076 {
2077 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&buffer_in);
2078 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&HF);
2079 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
2080 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
2081 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&mult);
2082 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_lf);
2084 }
2085 if(err != CL_SUCCESS) return err;
2086
2087 int vblocksize;
2088 dt_opencl_local_buffer_t vlocopt = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1,
2089 .yoffset = 2 * mult, .yfactor = 1,
2090 .cellsize = 4 * sizeof(float), .overhead = 0,
2091 .sizex = 1, .sizey = 1 << 16 };
2093 vblocksize = vlocopt.sizey;
2094 else
2095 vblocksize = 1;
2096
2097 if(vblocksize > 1)
2098 {
2099 const size_t vertical_sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUP(height, vblocksize), 1 };
2100 const size_t vertical_local[3] = { 1, vblocksize, 1 };
2101 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 0, sizeof(cl_mem), (void *)&HF);
2102 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 1, sizeof(cl_mem), (void *)&buffer_out);
2103 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 2, sizeof(int), (void *)&width);
2104 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 3, sizeof(int), (void *)&height);
2105 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 4, sizeof(int), (void *)&mult);
2106 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 5, sizeof(int), (void *)&clamp_lf);
2108 (vblocksize + 4 * mult) * 4 * sizeof(float), NULL);
2110 vertical_sizes, vertical_local);
2111 }
2112 else
2113 {
2114 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&HF);
2115 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&buffer_out);
2116 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
2117 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
2118 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&mult);
2119 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_lf);
2121 }
2122 if(err != CL_SUCCESS) return err;
2123
2124 uint8_t current_scale_type = scale_type(s, scales);
2125 const float radius = sqf(equivalent_sigma_at_step(B_SPLINE_SIGMA, s * DS_FACTOR));
2126 cl_mem reconstruct_write = (s == scales - 1)
2127 ? reconstructed
2128 : (reconstruct_read == reconstructed ? reconstructed_scratch : reconstructed);
2129
2130 // Keep the accumulation image read/write handles distinct at each scale.
2131 // Some AMD OpenCL drivers get unstable when the same image is bound for both roles.
2132 if(variant == DIFFUSE_RECONSTRUCT_RGB)
2133 {
2134 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 0, sizeof(cl_mem), (void *)&buffer_in);
2135 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 1, sizeof(cl_mem), (void *)&buffer_out);
2136 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 2, sizeof(cl_mem), (void *)&clipping_mask);
2137 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 3, sizeof(cl_mem), (void *)&reconstruct_read);
2138 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 4, sizeof(cl_mem), (void *)&reconstruct_write);
2139 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 5, sizeof(int), (void *)&width);
2140 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 6, sizeof(int), (void *)&height);
2141 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 7, sizeof(int), (void *)&mult);
2142 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 8, sizeof(float), (void *)&noise_level);
2143 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 9, sizeof(int), (void *)&salt);
2144 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 10, sizeof(uint8_t), (void *)&current_scale_type);
2145 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_guide_laplacians, 11, sizeof(float), (void *)&radius);
2147 if(err != CL_SUCCESS) return err;
2148 }
2149 else // DIFFUSE_RECONSTRUCT_CHROMA
2150 {
2151 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 0, sizeof(cl_mem), (void *)&buffer_in);
2152 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 1, sizeof(cl_mem), (void *)&buffer_out);
2153 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 2, sizeof(cl_mem), (void *)&clipping_mask);
2154 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 3, sizeof(cl_mem), (void *)&reconstruct_read);
2155 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 4, sizeof(cl_mem), (void *)&reconstruct_write);
2156 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 5, sizeof(int), (void *)&width);
2157 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 6, sizeof(int), (void *)&height);
2158 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 7, sizeof(int), (void *)&mult);
2159 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 8, sizeof(uint8_t), (void *)&current_scale_type);
2160 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_diffuse_color, 9, sizeof(float), (void *)&solid_color);
2162 if(err != CL_SUCCESS) return err;
2163 }
2164
2165 reconstruct_read = reconstruct_write;
2166 }
2167
2168 return err;
2169}
2170
2171static cl_int process_laplacian_bayer_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
2172 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
2173 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
2174 const dt_aligned_pixel_t clips)
2175{
2178
2179 cl_int err = DT_OPENCL_DEFAULT_ERROR;
2180
2181 const int devid = pipe->devid;
2182 const int width = roi_in->width;
2183 const int height = roi_in->height;
2184
2185 const int ds_height = height / DS_FACTOR;
2186 const int ds_width = width / DS_FACTOR;
2187
2188 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2189 size_t ds_sizes[] = { ROUNDUPDWD(ds_width, devid), ROUNDUPDHT(ds_height, devid), 1 };
2190
2191 const uint32_t filters = piece->dsc_in.filters;
2192
2193 cl_mem interpolated = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // [R, G, B, norm] for each pixel
2194 cl_mem clipping_mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // [R, G, B, norm] for each pixel
2195 cl_mem normalization = NULL;
2196 cl_mem normalization_tmp = NULL;
2197 cl_mem normalization_partials = NULL;
2198 cl_mem normalization_final = NULL;
2199
2200 // temp buffer for blurs. We will need to cycle between them for memory efficiency
2201 cl_mem LF_odd = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2202 cl_mem LF_even = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2203 cl_mem temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // need full size here for blurring
2204
2205 const float scale = DS_FACTOR * dt_dev_get_module_scale(pipe, roi_in);
2206 const float final_radius = (float)((int)(1 << data->scales)) / scale;
2207 const int scales = CLAMP((int)ceilf(log2f(final_radius)), 1, MAX_NUM_SCALES);
2208
2209 const float noise_level = data->noise_level / scale;
2210
2211 // wavelets scales buffers
2212 cl_mem HF = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2213 cl_mem ds_interpolated = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2214 cl_mem ds_clipping_mask = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2215 cl_mem reconstructed_scratch = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2216 cl_mem clips_cl = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), (float*)clips);
2217
2218 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask) || IS_NULL_PTR(LF_odd)
2219 || IS_NULL_PTR(LF_even) || IS_NULL_PTR(temp) || IS_NULL_PTR(HF) || IS_NULL_PTR(ds_interpolated)
2220 || IS_NULL_PTR(ds_clipping_mask) || IS_NULL_PTR(reconstructed_scratch) || IS_NULL_PTR(clips_cl))
2221 goto error;
2222
2223 {
2225 = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
2226 .cellsize = 4 * sizeof(float), .overhead = 0,
2227 .sizex = 1 << 4, .sizey = 1 << 4 };
2228
2230 goto error;
2231
2232 const size_t bwidth = ROUNDUP(width, flocopt.sizex);
2233 const size_t bheight = ROUNDUP(height, flocopt.sizey);
2234 const int bufsize = (int)((bwidth / flocopt.sizex) * (bheight / flocopt.sizey));
2235
2236 normalization_partials = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * (size_t)bufsize);
2237 normalization = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * REDUCESIZE);
2238 normalization_tmp = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * REDUCESIZE);
2239 if(!normalization_partials || !normalization || !normalization_tmp) goto error;
2240
2241 size_t fsizes[3] = { bwidth, bheight, 1 };
2242 size_t flocal[3] = { flocopt.sizex, flocopt.sizey, 1 };
2243 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first, 0, sizeof(cl_mem), &dev_in);
2246 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first, 3, sizeof(cl_mem), &normalization_partials);
2247 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first, 4, sizeof(uint32_t), &filters);
2248 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first, 5, sizeof(int), &roi_in->x);
2249 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first, 6, sizeof(int), &roi_in->y);
2251 sizeof(float) * 4 * flocopt.sizex * flocopt.sizey, NULL);
2253 if(err != CL_SUCCESS) goto error;
2254
2256 = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
2257 .cellsize = 4 * sizeof(float), .overhead = 0,
2258 .sizex = 1 << 16, .sizey = 1 };
2259
2261 goto error;
2262
2263 int current_length = bufsize;
2264 cl_mem reduce_in = normalization_partials;
2265 cl_mem reduce_out = normalization;
2266
2267 while(TRUE)
2268 {
2269 const int reducesize = MIN(REDUCESIZE, ROUNDUP(current_length, slocopt.sizex) / slocopt.sizex);
2270 size_t ssizes[3] = { (size_t)reducesize * slocopt.sizex, 1, 1 };
2271 size_t slocal[3] = { slocopt.sizex, 1, 1 };
2272 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_second, 0, sizeof(cl_mem), &reduce_in);
2273 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_second, 1, sizeof(cl_mem), &reduce_out);
2274 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_second, 2, sizeof(int), &current_length);
2276 sizeof(float) * 4 * slocopt.sizex, NULL);
2278 if(err != CL_SUCCESS) goto error;
2279
2280 if(reducesize == 1) break;
2281 current_length = reducesize;
2282 cl_mem swap = reduce_in;
2283 reduce_in = reduce_out;
2284 reduce_out = (swap == normalization_partials) ? normalization_tmp : normalization;
2285 }
2286
2287 normalization_final = reduce_out;
2288 }
2289
2290 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 0, sizeof(cl_mem), (void *)&dev_in);
2291 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 1, sizeof(cl_mem), (void *)&interpolated);
2292 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 2, sizeof(cl_mem), (void *)&temp);
2293 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 3, sizeof(cl_mem), (void *)&clips_cl);
2294 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 4, sizeof(cl_mem), (void *)&normalization_final);
2295 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 5, sizeof(int), (void *)&filters);
2296 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask, 6, sizeof(int), (void *)&roi_out->width);
2298 (void *)&roi_out->height);
2300 if(err != CL_SUCCESS) goto error;
2301
2302 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 0, sizeof(cl_mem), (void *)&temp);
2303 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 1, sizeof(cl_mem), (void *)&clipping_mask);
2304 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 2, sizeof(int), (void *)&roi_out->width);
2305 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 3, sizeof(int), (void *)&roi_out->height);
2307 if(err != CL_SUCCESS) goto error;
2308
2309 // Downsample
2310 const int RGBa = TRUE;
2311 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 0, sizeof(cl_mem), (void *)&clipping_mask);
2312 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 1, sizeof(int), (void *)&width);
2313 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 2, sizeof(int), (void *)&height);
2314 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 3, sizeof(cl_mem), (void *)&ds_clipping_mask);
2315 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 4, sizeof(int), (void *)&ds_width);
2316 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 5, sizeof(int), (void *)&ds_height);
2317 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 6, sizeof(int), (void *)&RGBa);
2318 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_interpolate_bilinear, ds_sizes);
2319 if(err != CL_SUCCESS) goto error;
2320
2321 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 0, sizeof(cl_mem), (void *)&interpolated);
2322 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 1, sizeof(int), (void *)&width);
2323 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 2, sizeof(int), (void *)&height);
2324 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 3, sizeof(cl_mem), (void *)&ds_interpolated);
2325 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 4, sizeof(int), (void *)&ds_width);
2326 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 5, sizeof(int), (void *)&ds_height);
2327 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 6, sizeof(int), (void *)&RGBa);
2328 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_interpolate_bilinear, ds_sizes);
2329 if(err != CL_SUCCESS) goto error;
2330
2331 for(int i = 0; i < data->iterations; i++)
2332 {
2333 const int salt = (i == data->iterations - 1); // add noise on the last iteration only
2334 err = wavelets_process_cl(devid, ds_interpolated, temp, reconstructed_scratch, ds_clipping_mask,
2335 ds_sizes, ds_width, ds_height, gd, scales, HF, LF_odd, LF_even,
2336 DIFFUSE_RECONSTRUCT_RGB, noise_level, salt, data->solid_color);
2337 if(err != CL_SUCCESS) goto error;
2338
2339 err = wavelets_process_cl(devid, temp, ds_interpolated, reconstructed_scratch, ds_clipping_mask,
2340 ds_sizes, ds_width, ds_height, gd, scales, HF, LF_odd, LF_even,
2341 DIFFUSE_RECONSTRUCT_CHROMA, noise_level, salt, data->solid_color);
2342 if(err != CL_SUCCESS) goto error;
2343 }
2344
2345 // Upsample
2346 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 0, sizeof(cl_mem), (void *)&ds_interpolated);
2347 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 1, sizeof(int), (void *)&ds_width);
2348 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 2, sizeof(int), (void *)&ds_height);
2349 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 3, sizeof(cl_mem), (void *)&interpolated);
2350 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 4, sizeof(int), (void *)&width);
2351 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 5, sizeof(int), (void *)&height);
2353 if(err != CL_SUCCESS) goto error;
2354
2355 // Remosaic
2356 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 0, sizeof(cl_mem), (void *)&dev_in);
2357 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 1, sizeof(cl_mem), (void *)&interpolated);
2358 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 2, sizeof(cl_mem), (void *)&clipping_mask);
2359 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 3, sizeof(cl_mem), (void *)&dev_out);
2360 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 4, sizeof(cl_mem), (void *)&normalization_final);
2361 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 5, sizeof(int), (void *)&filters);
2362 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 6, sizeof(int), (void *)&width);
2363 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace, 7, sizeof(int), (void *)&height);
2365 if(err != CL_SUCCESS) goto error;
2366
2367 // cleanup and exit on success
2369 dt_opencl_release_mem_object(normalization_partials);
2370 if(normalization_tmp != normalization_final) dt_opencl_release_mem_object(normalization_tmp);
2371 if(normalization != normalization_final) dt_opencl_release_mem_object(normalization);
2372 dt_opencl_release_mem_object(normalization_final);
2373 dt_opencl_release_mem_object(interpolated);
2374 dt_opencl_release_mem_object(clipping_mask);
2379 dt_opencl_release_mem_object(ds_clipping_mask);
2380 dt_opencl_release_mem_object(ds_interpolated);
2381 dt_opencl_release_mem_object(reconstructed_scratch);
2382 return err;
2383
2384error:
2386 dt_opencl_release_mem_object(normalization_partials);
2387 if(normalization_tmp != normalization_final) dt_opencl_release_mem_object(normalization_tmp);
2388 if(normalization != normalization_final) dt_opencl_release_mem_object(normalization);
2389 dt_opencl_release_mem_object(normalization_final);
2390 dt_opencl_release_mem_object(interpolated);
2391 dt_opencl_release_mem_object(clipping_mask);
2396 dt_opencl_release_mem_object(ds_clipping_mask);
2397 dt_opencl_release_mem_object(ds_interpolated);
2398 dt_opencl_release_mem_object(reconstructed_scratch);
2399
2400 dt_print(DT_DEBUG_OPENCL, "[opencl_highlights] couldn't enqueue kernel! %i\n", err);
2401 return err;
2402}
2403
2405 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
2406 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
2407 const dt_aligned_pixel_t clips)
2408{
2411
2412 cl_int err = DT_OPENCL_DEFAULT_ERROR;
2413
2414 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
2415 const int devid = pipe->devid;
2416 const int width = roi_in->width;
2417 const int height = roi_in->height;
2418
2419 const int ds_height = height / DS_FACTOR;
2420 const int ds_width = width / DS_FACTOR;
2421
2422 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2423 size_t ds_sizes[] = { ROUNDUPDWD(ds_width, devid), ROUNDUPDHT(ds_height, devid), 1 };
2424
2425 cl_mem interpolated = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
2426 cl_mem clipping_mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
2427 cl_mem normalization = NULL;
2428 cl_mem normalization_tmp = NULL;
2429 cl_mem normalization_partials = NULL;
2430 cl_mem normalization_final = NULL;
2431 cl_mem LF_odd = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2432 cl_mem LF_even = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2433 cl_mem temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
2434
2435 const float scale = DS_FACTOR * dt_dev_get_module_scale(pipe, roi_in);
2436 const float final_radius = (float)((int)(1 << data->scales)) / scale;
2437 const int scales = CLAMP((int)ceilf(log2f(final_radius)), 1, MAX_NUM_SCALES);
2438 const float noise_level = data->noise_level / scale;
2439
2440 cl_mem HF = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2441 cl_mem ds_interpolated = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2442 cl_mem ds_clipping_mask = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2443 cl_mem reconstructed_scratch = dt_opencl_alloc_device(devid, ds_sizes[0], ds_sizes[1], sizeof(float) * 4);
2444
2445 cl_mem clips_cl = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), (float *)clips);
2446 cl_mem dev_xtrans = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->dsc_in.xtrans), (void *)piece->dsc_in.xtrans);
2447 int32_t lookup[6][6][32] = { { { 0 } } };
2448 _build_xtrans_bilinear_lookup(lookup, roi_in, xtrans);
2449 cl_mem lookup_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(lookup), lookup);
2450
2451 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask) || IS_NULL_PTR(LF_odd)
2452 || IS_NULL_PTR(LF_even) || IS_NULL_PTR(temp) || IS_NULL_PTR(HF) || IS_NULL_PTR(ds_interpolated)
2453 || IS_NULL_PTR(ds_clipping_mask) || IS_NULL_PTR(reconstructed_scratch) || IS_NULL_PTR(clips_cl)
2454 || IS_NULL_PTR(dev_xtrans) || IS_NULL_PTR(lookup_cl))
2455 goto error;
2456
2457 {
2459 = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
2460 .cellsize = 4 * sizeof(float), .overhead = 0,
2461 .sizex = 1 << 4, .sizey = 1 << 4 };
2462
2464 goto error;
2465
2466 const size_t bwidth = ROUNDUP(width, flocopt.sizex);
2467 const size_t bheight = ROUNDUP(height, flocopt.sizey);
2468 const int bufsize = (int)((bwidth / flocopt.sizex) * (bheight / flocopt.sizey));
2469
2470 normalization_partials = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * (size_t)bufsize);
2471 normalization = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * REDUCESIZE);
2472 normalization_tmp = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * REDUCESIZE);
2473 if(!normalization_partials || !normalization || !normalization_tmp) goto error;
2474
2475 size_t fsizes[3] = { bwidth, bheight, 1 };
2476 size_t flocal[3] = { flocopt.sizex, flocopt.sizey, 1 };
2477 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first_xtrans, 0, sizeof(cl_mem), &dev_in);
2480 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first_xtrans, 3, sizeof(cl_mem), &normalization_partials);
2483 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_first_xtrans, 6, sizeof(cl_mem), &dev_xtrans);
2485 sizeof(float) * 4 * flocopt.sizex * flocopt.sizey, NULL);
2487 if(err != CL_SUCCESS) goto error;
2488
2490 = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
2491 .cellsize = 4 * sizeof(float), .overhead = 0,
2492 .sizex = 1 << 16, .sizey = 1 };
2493
2495 goto error;
2496
2497 int current_length = bufsize;
2498 cl_mem reduce_in = normalization_partials;
2499 cl_mem reduce_out = normalization;
2500
2501 while(TRUE)
2502 {
2503 const int reducesize = MIN(REDUCESIZE, ROUNDUP(current_length, slocopt.sizex) / slocopt.sizex);
2504 size_t ssizes[3] = { (size_t)reducesize * slocopt.sizex, 1, 1 };
2505 size_t slocal[3] = { slocopt.sizex, 1, 1 };
2506 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_second, 0, sizeof(cl_mem), &reduce_in);
2507 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_second, 1, sizeof(cl_mem), &reduce_out);
2508 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_normalize_reduce_second, 2, sizeof(int), &current_length);
2510 sizeof(float) * 4 * slocopt.sizex, NULL);
2512 if(err != CL_SUCCESS) goto error;
2513
2514 if(reducesize == 1) break;
2515 current_length = reducesize;
2516 cl_mem swap = reduce_in;
2517 reduce_in = reduce_out;
2518 reduce_out = (swap == normalization_partials) ? normalization_tmp : normalization;
2519 }
2520
2521 normalization_final = reduce_out;
2522 }
2523
2524 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 0, sizeof(cl_mem), (void *)&dev_in);
2525 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 1, sizeof(cl_mem), (void *)&interpolated);
2526 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 2, sizeof(cl_mem), (void *)&temp);
2527 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 3, sizeof(cl_mem), (void *)&clips_cl);
2528 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 4, sizeof(cl_mem), (void *)&normalization_final);
2531 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 7, sizeof(int), (void *)&roi_in->x);
2532 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 8, sizeof(int), (void *)&roi_in->y);
2533 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 9, sizeof(cl_mem), (void *)&dev_xtrans);
2534 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_bilinear_and_mask_xtrans, 10, sizeof(cl_mem), (void *)&lookup_cl);
2536 if(err != CL_SUCCESS) goto error;
2537
2538 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 0, sizeof(cl_mem), (void *)&temp);
2539 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 1, sizeof(cl_mem), (void *)&clipping_mask);
2540 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 2, sizeof(int), (void *)&roi_out->width);
2541 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_box_blur, 3, sizeof(int), (void *)&roi_out->height);
2543 if(err != CL_SUCCESS) goto error;
2544
2545 const int RGBa = TRUE;
2546 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 0, sizeof(cl_mem), (void *)&clipping_mask);
2547 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 1, sizeof(int), (void *)&width);
2548 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 2, sizeof(int), (void *)&height);
2549 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 3, sizeof(cl_mem), (void *)&ds_clipping_mask);
2550 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 4, sizeof(int), (void *)&ds_width);
2551 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 5, sizeof(int), (void *)&ds_height);
2552 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 6, sizeof(int), (void *)&RGBa);
2553 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_interpolate_bilinear, ds_sizes);
2554 if(err != CL_SUCCESS) goto error;
2555
2556 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 0, sizeof(cl_mem), (void *)&interpolated);
2557 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 1, sizeof(int), (void *)&width);
2558 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 2, sizeof(int), (void *)&height);
2559 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 3, sizeof(cl_mem), (void *)&ds_interpolated);
2560 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 4, sizeof(int), (void *)&ds_width);
2561 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 5, sizeof(int), (void *)&ds_height);
2562 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 6, sizeof(int), (void *)&RGBa);
2563 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_interpolate_bilinear, ds_sizes);
2564 if(err != CL_SUCCESS) goto error;
2565
2566 for(int i = 0; i < data->iterations; i++)
2567 {
2568 const int salt = (i == data->iterations - 1);
2569 err = wavelets_process_cl(devid, ds_interpolated, temp, reconstructed_scratch, ds_clipping_mask,
2570 ds_sizes, ds_width, ds_height, gd, scales, HF, LF_odd, LF_even,
2571 DIFFUSE_RECONSTRUCT_RGB, noise_level, salt, data->solid_color);
2572 if(err != CL_SUCCESS) goto error;
2573
2574 err = wavelets_process_cl(devid, temp, ds_interpolated, reconstructed_scratch, ds_clipping_mask,
2575 ds_sizes, ds_width, ds_height, gd, scales, HF, LF_odd, LF_even,
2576 DIFFUSE_RECONSTRUCT_CHROMA, noise_level, salt, data->solid_color);
2577 if(err != CL_SUCCESS) goto error;
2578 }
2579
2580 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 0, sizeof(cl_mem), (void *)&ds_interpolated);
2581 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 1, sizeof(int), (void *)&ds_width);
2582 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 2, sizeof(int), (void *)&ds_height);
2583 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 3, sizeof(cl_mem), (void *)&interpolated);
2584 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 4, sizeof(int), (void *)&width);
2585 dt_opencl_set_kernel_arg(devid, gd->kernel_interpolate_bilinear, 5, sizeof(int), (void *)&height);
2587 if(err != CL_SUCCESS) goto error;
2588
2589 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 0, sizeof(cl_mem), (void *)&dev_in);
2590 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 1, sizeof(cl_mem), (void *)&interpolated);
2591 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 2, sizeof(cl_mem), (void *)&clipping_mask);
2592 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 3, sizeof(cl_mem), (void *)&dev_out);
2593 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 4, sizeof(cl_mem), (void *)&normalization_final);
2596 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 7, sizeof(int), (void *)&roi_in->x);
2597 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 8, sizeof(int), (void *)&roi_in->y);
2598 dt_opencl_set_kernel_arg(devid, gd->kernel_highlights_remosaic_and_replace_xtrans, 9, sizeof(cl_mem), (void *)&dev_xtrans);
2600 if(err != CL_SUCCESS) goto error;
2603 dt_opencl_release_mem_object(dev_xtrans);
2604 dt_opencl_release_mem_object(normalization_partials);
2605 if(normalization_tmp != normalization_final) dt_opencl_release_mem_object(normalization_tmp);
2606 if(normalization != normalization_final) dt_opencl_release_mem_object(normalization);
2607 dt_opencl_release_mem_object(normalization_final);
2608 dt_opencl_release_mem_object(interpolated);
2609 dt_opencl_release_mem_object(clipping_mask);
2614 dt_opencl_release_mem_object(ds_clipping_mask);
2615 dt_opencl_release_mem_object(ds_interpolated);
2616 dt_opencl_release_mem_object(reconstructed_scratch);
2617 return err;
2618
2619error:
2622 dt_opencl_release_mem_object(dev_xtrans);
2623 dt_opencl_release_mem_object(normalization_partials);
2624 if(normalization_tmp != normalization_final) dt_opencl_release_mem_object(normalization_tmp);
2625 if(normalization != normalization_final) dt_opencl_release_mem_object(normalization);
2626 dt_opencl_release_mem_object(normalization_final);
2627 dt_opencl_release_mem_object(interpolated);
2628 dt_opencl_release_mem_object(clipping_mask);
2633 dt_opencl_release_mem_object(ds_clipping_mask);
2634 dt_opencl_release_mem_object(ds_interpolated);
2635 dt_opencl_release_mem_object(reconstructed_scratch);
2636
2637 dt_print(DT_DEBUG_OPENCL, "[opencl_highlights] couldn't enqueue kernel! %i\n", err);
2638 return err;
2639}
2640#endif
2641
2643static void process_clip(const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid,
2644 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
2645 const float clip)
2646{
2647 const float *const in = (const float *const)ivoid;
2648 float *const out = (float *const)ovoid;
2649
2650 if(piece->dsc_in.filters)
2651 { // raw mosaic
2653 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
2654 {
2655 out[k] = MIN(clip, in[k]);
2656 }
2657
2658 }
2659 else
2660 {
2661 const int ch = piece->dsc_in.channels;
2663 for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k++)
2664 {
2665 out[k] = MIN(clip, in[k]);
2666 }
2667
2668 }
2669}
2670
2672static void process_visualize(const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid,
2673 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
2674 const uint32_t filters, dt_iop_highlights_data_t *data)
2675{
2676 const float *const in = (const float *const)ivoid;
2677 float *const out = (float *const)ovoid;
2678 const size_t width = roi_out->width;
2679 const size_t height = roi_out->height;
2680 const float clips[4] = { 0.995f * data->clip * piece->dsc_in.processed_maximum[0],
2681 0.995f * data->clip * piece->dsc_in.processed_maximum[1],
2682 0.995f * data->clip * piece->dsc_in.processed_maximum[2],
2683 data->clip};
2684 __OMP_FOR_SIMD__(aligned(in, out : 64))
2685 for(size_t row = 0; row < height; row++)
2686 {
2687 for(size_t col = 0, i = row*width; col < width; col++, i++)
2688 {
2689 const int c = FC(row, col, filters);
2690 const float ival = in[i];
2691 out[i] = (ival < clips[c]) ? 0.2f * ival : 1.0f;
2692 }
2693 }
2694}
2695
2697int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
2698 void *const ovoid)
2699{
2700 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2701 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2702 const uint32_t filters = piece->dsc_in.filters;
2705
2706 /* This transient preview belongs to the central darkroom view. Do not infer
2707 * its owner from ROI geometry: at zoom-to-fit the main and navigation pipes
2708 * can produce identical dimensions while both still need distinct outputs. */
2709 const gboolean visualizing = !IS_NULL_PTR(g) && g->show_visualize
2710 && self->dev->gui_attached && pipe == self->dev->pipe;
2711
2712 if(visualizing)
2713 {
2714 process_visualize(piece, ivoid, ovoid, roi_in, roi_out, filters, data);
2715 /* The clipping preview is the final output of this module. Blending would
2716 * interpret PASSTHRU as a channel-display request and replace RAW output
2717 * with zeroes before the downstream demosaic stage can display it. */
2718 ((dt_dev_pixelpipe_t *)pipe)->mask_display = DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
2719 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
2720 return 0;
2721 }
2722
2723 const float clip
2724 = data->clip * fminf(piece->dsc_in.processed_maximum[0],
2725 fminf(piece->dsc_in.processed_maximum[1], piece->dsc_in.processed_maximum[2]));
2726
2727 if(!filters)
2728 {
2729 process_clip(piece, ivoid, ovoid, roi_in, roi_out, clip);
2730 return 0;
2731 }
2732
2733 switch(data->mode)
2734 {
2735 case DT_IOP_HIGHLIGHTS_INPAINT: // a1ex's (magiclantern) idea of color inpainting:
2736 {
2737 const float clips[4] = { 0.987 * data->clip * piece->dsc_in.processed_maximum[0],
2738 0.987 * data->clip * piece->dsc_in.processed_maximum[1],
2739 0.987 * data->clip * piece->dsc_in.processed_maximum[2], clip };
2740
2741 if(filters == 9u)
2742 {
2743 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
2745 for(int j = 0; j < roi_out->height; j++)
2746 {
2747 interpolate_color_xtrans(ivoid, ovoid, roi_in, roi_out, 0, 1, j, clips, xtrans, 0);
2748 interpolate_color_xtrans(ivoid, ovoid, roi_in, roi_out, 0, -1, j, clips, xtrans, 1);
2749 }
2750
2752 for(int i = 0; i < roi_out->width; i++)
2753 {
2754 interpolate_color_xtrans(ivoid, ovoid, roi_in, roi_out, 1, 1, i, clips, xtrans, 2);
2755 interpolate_color_xtrans(ivoid, ovoid, roi_in, roi_out, 1, -1, i, clips, xtrans, 3);
2756 }
2757
2758 }
2759 else
2760 {
2762 for(int j = 0; j < roi_out->height; j++)
2763 {
2764 interpolate_color(ivoid, ovoid, roi_out, 0, 1, j, clips, filters, 0);
2765 interpolate_color(ivoid, ovoid, roi_out, 0, -1, j, clips, filters, 1);
2766 }
2767
2768
2769// up/down directions
2771 for(int i = 0; i < roi_out->width; i++)
2772 {
2773 interpolate_color(ivoid, ovoid, roi_out, 1, 1, i, clips, filters, 2);
2774 interpolate_color(ivoid, ovoid, roi_out, 1, -1, i, clips, filters, 3);
2775 }
2776
2777 }
2778 break;
2779 }
2781 if(filters == 9u)
2782 process_lch_xtrans(self, piece, ivoid, ovoid, roi_in, roi_out, clip);
2783 else
2784 process_lch_bayer(self, piece, ivoid, ovoid, roi_in, roi_out, clip);
2785 break;
2787 {
2788 const dt_aligned_pixel_t clips = { 0.995f * data->clip * piece->dsc_in.processed_maximum[0],
2789 0.995f * data->clip * piece->dsc_in.processed_maximum[1],
2790 0.995f * data->clip * piece->dsc_in.processed_maximum[2], clip };
2791 if((filters == 9u && process_laplacian_xtrans(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, clips))
2792 || (filters != 9u && process_laplacian_bayer(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, clips)))
2793 return 1;
2794 break;
2795 }
2796 default:
2798 process_clip(piece, ivoid, ovoid, roi_in, roi_out, clip);
2799 break;
2800 }
2801
2802 if(pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
2803 return 0;
2804}
2805
2808{
2811
2812 memcpy(d, p, sizeof(*p));
2813
2814 // Image-type gating (raw colorimetry, not monochrome) is handled at history level by
2815 // enable()/force_enable()/reload_defaults(); nothing type-related is decided here. process()
2816 // still has a dedicated !filters branch so already-demosaiced raw (sRAW / linear DNG) is
2817 // processed correctly when the module is enabled.
2818 const dt_image_t *const img = &self->dev->image_storage;
2819 dt_iop_fmt_log(self, "commit: class=%s filters=%u mode=%d -> enabled=%d",
2821 d->mode, piece->enabled);
2822
2823 // no OpenCL for DT_IOP_HIGHLIGHTS_INPAINT
2824 piece->process_cl_ready = (d->mode == DT_IOP_HIGHLIGHTS_INPAINT) ? 0 : 1;
2825
2826 if(d->mode == DT_IOP_HIGHLIGHTS_LAPLACIAN)
2827 piece->cache_output_on_ram = TRUE;
2828
2829 if(d->mode != DT_IOP_HIGHLIGHTS_LAPLACIAN)
2830 {
2831 if(!piece->dsc_in.filters)
2832 {
2833 const float m = fminf(piece->dsc_in.processed_maximum[0],
2834 fminf(piece->dsc_in.processed_maximum[1], piece->dsc_in.processed_maximum[2]));
2835 for(int k = 0; k < 3; k++) piece->dsc_out.processed_maximum[k] = m;
2836 }
2837 else
2838 {
2839 const float m = fmaxf(piece->dsc_in.processed_maximum[0],
2840 fmaxf(piece->dsc_in.processed_maximum[1], piece->dsc_in.processed_maximum[2]));
2841 for(int k = 0; k < 3; k++) piece->dsc_out.processed_maximum[k] = m;
2842 }
2843 }
2844}
2845
2846static gboolean enable(dt_image_t *image)
2847{
2848 // raw colorimetry (raw or sraw/linear-DNG), but not real monochrome. Must match the
2849 // commit_params() gate above.
2850 return dt_image_needs_rawprepare(image) && !dt_image_is_monochrome(image);
2851}
2852
2853gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
2854{
2855 // History sanitization: clamp against the SAME support rule as enable()/reload_defaults()
2856 // (raw colorimetry, not monochrome). The previous version only handled the monochrome case and
2857 // let a highlights entry pasted onto a non-raw image survive until commit_params() patched it.
2858 const gboolean active = enable(&self->dev->image_storage);
2859 const gboolean state = current_state && active;
2860 dt_iop_fmt_log(self, "force_enable: class=%s supported=%d current=%d -> %d",
2862 active, current_state, state);
2863 return state;
2864}
2865
2867{
2868 const int program = 2; // basic.cl, from programs.conf
2871 module->data = gd;
2872 gd->kernel_highlights_1f_clip = dt_opencl_create_kernel(program, "highlights_1f_clip");
2873 gd->kernel_highlights_1f_lch_bayer = dt_opencl_create_kernel(program, "highlights_1f_lch_bayer");
2874 gd->kernel_highlights_1f_lch_xtrans = dt_opencl_create_kernel(program, "highlights_1f_lch_xtrans");
2875 gd->kernel_highlights_4f_clip = dt_opencl_create_kernel(program, "highlights_4f_clip");
2876 gd->kernel_highlights_bilinear_and_mask = dt_opencl_create_kernel(program, "interpolate_and_mask");
2877 gd->kernel_highlights_bilinear_and_mask_xtrans = dt_opencl_create_kernel(program, "interpolate_and_mask_xtrans");
2878 gd->kernel_highlights_normalize_reduce_first = dt_opencl_create_kernel(program, "highlights_normalize_reduce_first");
2879 gd->kernel_highlights_normalize_reduce_first_xtrans = dt_opencl_create_kernel(program, "highlights_normalize_reduce_first_xtrans");
2880 gd->kernel_highlights_normalize_reduce_second = dt_opencl_create_kernel(program, "highlights_normalize_reduce_second");
2881 gd->kernel_highlights_remosaic_and_replace = dt_opencl_create_kernel(program, "remosaic_and_replace");
2882 gd->kernel_highlights_remosaic_and_replace_xtrans = dt_opencl_create_kernel(program, "remosaic_and_replace_xtrans");
2883 gd->kernel_highlights_box_blur = dt_opencl_create_kernel(program, "box_blur_5x5");
2884 gd->kernel_highlights_guide_laplacians = dt_opencl_create_kernel(program, "guide_laplacians");
2885 gd->kernel_highlights_diffuse_color = dt_opencl_create_kernel(program, "diffuse_color");
2886 gd->kernel_highlights_false_color = dt_opencl_create_kernel(program, "highlights_false_color");
2887 gd->kernel_interpolate_bilinear = dt_opencl_create_kernel(program, "interpolate_bilinear");
2888
2889 const int wavelets = 35; // bspline.cl, from programs.conf
2890 gd->kernel_filmic_bspline_horizontal = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal");
2891 gd->kernel_filmic_bspline_vertical = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical");
2892 gd->kernel_filmic_bspline_horizontal_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal_local");
2893 gd->kernel_filmic_bspline_vertical_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical_local");
2894
2895}
2896
2898{
2915
2920
2922
2923 dt_free(module->data);
2924}
2925
2927{
2929 piece->data_size = sizeof(dt_iop_highlights_data_t);
2930}
2931
2933{
2934 dt_free_align(piece->data);
2935 piece->data = NULL;
2936}
2937
2938void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2939{
2942
2943 const gboolean raw = (self->dev->image_storage.dsc.filters != 0);
2944 const gboolean israw = (self->dev->image_storage.dsc.filters != 0);
2945 dt_iop_highlights_mode_t mode = p->mode;
2946
2947 gtk_widget_set_visible(g->noise_level, raw && mode == DT_IOP_HIGHLIGHTS_LAPLACIAN);
2948 gtk_widget_set_visible(g->iterations, raw && mode == DT_IOP_HIGHLIGHTS_LAPLACIAN);
2949 gtk_widget_set_visible(g->scales, raw && mode == DT_IOP_HIGHLIGHTS_LAPLACIAN);
2950 gtk_widget_set_visible(g->solid_color, raw && mode == DT_IOP_HIGHLIGHTS_LAPLACIAN);
2951
2953}
2954
2955void gui_update(struct dt_iop_module_t *self)
2956{
2958 const gboolean monochrome = dt_image_is_monochrome(&self->dev->image_storage);
2959 // enable this per default if raw or sraw if not real monochrome
2960 self->default_enabled = dt_image_needs_rawprepare(&self->dev->image_storage) && !monochrome;
2961
2962 // Neuter the on/off button only if not already enabled.
2963 // It can be enabled by history copy & paste from a RAW image.
2964 self->hide_enable_button = monochrome && !self->enabled;
2965 gtk_stack_set_visible_child_name(GTK_STACK(self->widget), self->default_enabled ? "default" : "monochrome");
2966
2968 g->show_visualize = FALSE;
2969 gui_changed(self, NULL, NULL);
2970}
2971
2972
2974{
2975 // we might be called from presets update infrastructure => there is no image
2976 if(!module->dev || module->dev->image_storage.id == -1) return;
2977
2978 // enable this per default if raw or sraw if not real monochrome
2979 module->default_enabled = enable(&module->dev->image_storage);
2980 module->hide_enable_button = !enable(&module->dev->image_storage);
2981 dt_iop_fmt_log(module, "reload_defaults: class=%s default_enabled=%d",
2983 module->default_enabled);
2984 if(module->widget)
2985 gtk_stack_set_visible_child_name(GTK_STACK(module->widget), module->default_enabled ? "default" : "monochrome");
2986
2988
2989 if(g)
2991 dt_bauhaus_combobox_add_full(g->mode, _("guided laplacians"), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT,
2992 GINT_TO_POINTER(DT_IOP_HIGHLIGHTS_LAPLACIAN), NULL, TRUE);
2993}
2994
2995static void _visualize_callback(GtkWidget *quad, gpointer user_data)
2996{
2997 if(darktable.gui->reset) return;
2998 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3000
3001 // if blend module is displaying mask do not display it here
3004
3005 g->show_visualize = dt_bauhaus_widget_get_quad_active(quad);
3006
3007 if(g->show_visualize)
3009
3010 dt_iop_set_cache_bypass(self, g->show_visualize);
3012}
3013
3014void gui_focus(struct dt_iop_module_t *self, gboolean in)
3015{
3017 if(!in)
3018 {
3019 const gboolean was_visualize = g->show_visualize;
3021 g->show_visualize = FALSE;
3022 if(was_visualize) dt_dev_pixelpipe_update_history_main(self->dev);
3023 }
3024}
3025
3026void gui_init(struct dt_iop_module_t *self)
3027{
3029 GtkWidget *box_raw = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3030
3031 g->mode = dt_bauhaus_combobox_from_params(self, "mode");
3032 gtk_widget_set_tooltip_text(g->mode, _("highlight reconstruction method"));
3033
3034 g->clip = dt_bauhaus_slider_from_params(self, "clip");
3036 gtk_widget_set_tooltip_text(g->clip,
3037 _("manually adjust the clipping threshold against "
3038 "magenta highlights\nthe mask icon shows the clipped area\n"
3039 "(you shouldn't ever need to touch this)"));
3043 g_signal_connect(G_OBJECT(g->clip), "quad-pressed", G_CALLBACK(_visualize_callback), self);
3044
3045 g->noise_level = dt_bauhaus_slider_from_params(self, "noise_level");
3046 gtk_widget_set_tooltip_text(g->noise_level, _("add noise to visually blend the reconstructed areas\n"
3047 "into the rest of the noisy image. useful at high ISO."));
3048
3049 g->iterations = dt_bauhaus_slider_from_params(self, "iterations");
3050 dt_bauhaus_slider_set_soft_range(g->iterations, 1, 256);
3051 gtk_widget_set_tooltip_text(g->iterations, _("increase if magenta highlights don't get fully corrected\n"
3052 "each new iteration brings a performance penalty."));
3053
3054 g->solid_color = dt_bauhaus_slider_from_params(self, "solid_color");
3055 dt_bauhaus_slider_set_format(g->solid_color, "%");
3056 gtk_widget_set_tooltip_text(g->solid_color, _("increase if magenta highlights don't get fully corrected.\n"
3057 "this may produce non-smooth boundaries between valid and clipped regions."));
3058
3059 g->scales = dt_bauhaus_combobox_from_params(self, "scales");
3060 gtk_widget_set_tooltip_text(g->scales, _("increase to correct larger clipped areas.\n"
3061 "large values bring huge performance penalties"));
3062
3063 GtkWidget *monochromes = dt_ui_label_new(_("not applicable"));
3064 gtk_widget_set_tooltip_text(monochromes, _("no highlights reconstruction for monochrome images"));
3065
3066 // start building top level widget
3067 self->widget = gtk_stack_new();
3068 gtk_stack_set_homogeneous(GTK_STACK(self->widget), FALSE);
3069 gtk_stack_add_named(GTK_STACK(self->widget), monochromes, "monochrome");
3070 gtk_stack_add_named(GTK_STACK(self->widget), box_raw, "default");
3071}
3072
3073// clang-format off
3074// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3075// vim: shiftwidth=2 expandtab tabstop=2 cindent
3076// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3077// 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_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1720
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:2155
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1726
void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(free_func)(void *data), gboolean sensitive)
Definition bauhaus.c:2038
int dt_bauhaus_widget_get_quad_active(GtkWidget *widget)
Definition bauhaus.c:1743
void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
Definition bauhaus.c:1736
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1702
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
Definition bauhaus.h:125
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
int dt_box_mean(float *const buf, const size_t height, const size_t width, const int ch, const int radius, const unsigned iterations)
#define B_SPLINE_SIGMA
Definition bspline.h:34
static float equivalent_sigma_at_step(const float sigma, const unsigned int s)
Definition bspline.h:48
#define B_SPLINE_TO_LAPLACIAN
Definition bspline.h:45
static void decompose_2D_Bspline(const float *const restrict in, float *const restrict HF, float *const restrict LF, const size_t width, const size_t height, const int mult, float *const tempbuf, size_t padded_size)
Definition bspline.h:347
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
return vector dt_simd_set1(valid ?(scaling+NORM_MIN) :NORM_MIN)
static float lookup(read_only image2d_t lut, const float x)
@ IOP_CS_RAW
@ IOP_CS_RGB
#define B(y, x)
const dt_aligned_pixel_t f
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
static const float const float C
static const int row
static dt_aligned_pixel_t RGB
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
dt_image_pipe_class_t dt_image_pipe_class(const dt_image_t *img)
const char * dt_image_pipe_class_name(const dt_image_pipe_class_t klass)
gboolean dt_image_is_monochrome(const dt_image_t *img)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
static const dt_aligned_pixel_simd_t const dt_aligned_pixel_simd_t row1
Definition darktable.h:623
#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_ALIGNED_ARRAY
Definition darktable.h:388
#define DT_IS_ALIGNED(x)
Definition darktable.h:371
#define for_each_channel(_var,...)
Definition darktable.h:662
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_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_pixelpipe_cache_alloc_align_float(pixels, pipe)
Definition darktable.h:442
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
static const dt_aligned_pixel_simd_t const dt_aligned_pixel_simd_t const dt_aligned_pixel_simd_t row2
Definition darktable.h:624
#define for_four_channels(_var,...)
Definition darktable.h:664
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define __OMP_FOR_SIMD__(...)
Definition darktable.h:260
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
Definition darktable.h:1030
static const dt_aligned_pixel_simd_t row0
Definition darktable.h:622
#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
#define BLUE
#define RED
#define GREEN
static int FCxtrans(const int row, const int col, global const unsigned char(*const xtrans)[6])
static int FC(const int row, const int col, const unsigned int filters)
#define ALPHA
static float4 dt_noise_generator_simd(const dt_noise_distribution_t distribution, const float4 mu, const float4 param, uint state[4])
static unsigned int splitmix32(const unsigned long seed)
static float xoshiro128plus(uint state[4])
void dt_iop_params_t
Definition dev_history.h:41
#define dt_dev_pixelpipe_update_history_main(dev)
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:136
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:117
#define H
Definition diffuse.c:613
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
static int dwt_interleave_rows(const int rowid, const int height, const int stride)
Definition dwt.h:93
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
static __DT_CLONE_TARGETS__ void interpolate_bilinear(const float *const restrict in, const size_t width_in, const size_t height_in, float *const restrict out, const size_t width_out, const size_t height_out, const size_t ch)
void default_output_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition format.c:75
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
static __DT_CLONE_TARGETS__ void _compute_laplacian_normalization(const float *const restrict input, const dt_iop_roi_t *const roi_in, const uint32_t filters, const uint8_t(*const xtrans)[6], dt_aligned_pixel_t normalization)
static cl_int process_laplacian_bayer_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
void commit_params(struct 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 process_laplacian_bayer(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
const char ** description(struct dt_iop_module_t *self)
Definition highlights.c:194
int default_group()
Definition highlights.c:203
__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)
static __DT_CLONE_TARGETS__ void heat_PDE_diffusion(const float *const restrict high_freq, const float *const restrict low_freq, const float *const restrict clipping_mask, float *const restrict output, const size_t width, const size_t height, const int mult, const uint8_t scale, const float first_order_factor)
#define SQRT12
Definition highlights.c:816
void reload_defaults(dt_iop_module_t *module)
static int wavelets_process(const float *const restrict in, float *const restrict reconstructed, const float *const restrict clipping_mask, const size_t width, const size_t height, const int scales, float *const restrict HF, float *const restrict LF_odd, float *const restrict LF_even, const diffuse_reconstruct_variant_t variant, const float noise_level, const int salt, const float first_order_factor)
dt_iop_highlights_mode_t
Definition highlights.c:106
@ DT_IOP_HIGHLIGHTS_CLIP
Definition highlights.c:107
@ DT_IOP_HIGHLIGHTS_INPAINT
Definition highlights.c:109
@ DT_IOP_HIGHLIGHTS_LAPLACIAN
Definition highlights.c:110
@ DT_IOP_HIGHLIGHTS_LCH
Definition highlights.c:108
static __DT_CLONE_TARGETS__ void _remosaic_and_replace_xtrans(const float *const restrict input, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t wb, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const size_t width, const size_t height)
dt_atrous_wavelets_scales_t
Definition highlights.c:114
@ WAVELETS_10_SCALE
Definition highlights.c:124
@ WAVELETS_2_SCALE
Definition highlights.c:116
@ WAVELETS_8_SCALE
Definition highlights.c:122
@ WAVELETS_5_SCALE
Definition highlights.c:119
@ WAVELETS_6_SCALE
Definition highlights.c:120
@ WAVELETS_11_SCALE
Definition highlights.c:125
@ WAVELETS_4_SCALE
Definition highlights.c:118
@ WAVELETS_1_SCALE
Definition highlights.c:115
@ WAVELETS_12_SCALE
Definition highlights.c:126
@ WAVELETS_3_SCALE
Definition highlights.c:117
@ WAVELETS_7_SCALE
Definition highlights.c:121
@ WAVELETS_9_SCALE
Definition highlights.c:123
#define REDUCESIZE
Definition highlights.c:81
static __DT_CLONE_TARGETS__ void _build_xtrans_bilinear_lookup(int32_t lookup[6][6][32], const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6])
static gboolean enable(dt_image_t *image)
static __DT_CLONE_TARGETS__ void process_lch_xtrans(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const float clip)
Definition highlights.c:917
void gui_focus(struct dt_iop_module_t *self, gboolean in)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static __DT_CLONE_TARGETS__ int process_laplacian_xtrans(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
static void interpolate_color(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_out, int dim, int dir, int other, const float *clip, const uint32_t filters, const int pass)
Definition highlights.c:706
const char * name()
Definition highlights.c:189
void output_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition highlights.c:220
static __DT_CLONE_TARGETS__ void _interpolate_and_mask(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t wb, const uint32_t filters, const size_t width, const size_t height)
static __DT_CLONE_TARGETS__ void _remosaic_and_replace(const float *const restrict input, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t wb, const uint32_t filters, const size_t width, const size_t height)
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
void gui_init(struct dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
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)
Definition highlights.c:485
diffuse_reconstruct_variant_t
@ DIFFUSE_RECONSTRUCT_RGB
@ DIFFUSE_RECONSTRUCT_CHROMA
void cleanup_global(dt_iop_module_so_t *module)
static cl_int process_laplacian_xtrans_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
dt_iop_highlights_params_t dt_iop_highlights_data_t
Definition highlights.c:160
static __DT_CLONE_TARGETS__ void process_lch_bayer(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const float clip)
Definition highlights.c:819
wavelets_scale_t
@ ANY_SCALE
@ FIRST_SCALE
@ LAST_SCALE
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition highlights.c:213
int flags()
Definition highlights.c:208
void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const void *input)
Definition highlights.c:226
static __DT_CLONE_TARGETS__ void process_clip(const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const float clip)
gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
#define MAX_NUM_SCALES
Definition highlights.c:80
static __DT_CLONE_TARGETS__ void guide_laplacians(const float *const restrict high_freq, const float *const restrict low_freq, const float *const restrict clipping_mask, float *const restrict output, const size_t width, const size_t height, const int mult, const float noise_level, const int salt, const uint8_t scale, const float radius_sq)
#define DS_FACTOR
Definition highlights.c:84
static float interp_pix_xtrans(const int ratio_next, const ssize_t offset_next, const float clip0, const float clip_next, const float *const in, const float *const ratios)
Definition highlights.c:553
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void _visualize_callback(GtkWidget *quad, gpointer user_data)
static __DT_CLONE_TARGETS__ void process_visualize(const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const uint32_t filters, dt_iop_highlights_data_t *data)
void init_global(dt_iop_module_so_t *module)
#define SQRT3
Definition highlights.c:815
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition highlights.c:321
static cl_int wavelets_process_cl(const int devid, cl_mem in, cl_mem reconstructed, cl_mem reconstructed_scratch, cl_mem clipping_mask, const size_t sizes[3], const int width, const int height, dt_iop_highlights_global_data_t *const gd, const int scales, cl_mem HF, cl_mem LF_odd, cl_mem LF_even, const diffuse_reconstruct_variant_t variant, const float noise_level, const int salt, const float solid_color)
static __DT_CLONE_TARGETS__ void _interpolate_and_mask_xtrans(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t wb, const dt_iop_roi_t *const roi_in, const int32_t lookup[6][6][32], const uint8_t(*const xtrans)[6], const size_t width, const size_t height)
static void interpolate_color_xtrans(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, int dim, int dir, int other, const float *const clip, const uint8_t(*const xtrans)[6], const int pass)
Definition highlights.c:580
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition highlights.c:250
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:131
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2915
#define dt_iop_fmt_log(module, fmt,...)
Debug helper to trace a module's input-format-driven decisions on the -d pipe channel (DT_DEBUG_PIPE)...
Definition imageop.h:453
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
@ 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
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
static const float x
static void swap(float *x, float *y)
Definition lightroom.c:1022
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define R
size_t size
Definition mipmap_cache.c:3
float dt_aligned_pixel_t[4]
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3156
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_buffer(const int devid, const size_t size)
Definition opencl.c:2544
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
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
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_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2142
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:57
#define ROUNDUP(a, n)
Definition opencl.h:78
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
#define eps
Definition rcd.c:81
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float sigma
const float r
const float const int flip
const float noise
struct dt_gui_gtk_t * gui
Definition darktable.h:775
dt_iop_buffer_dsc_t dsc_out
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:162
dt_image_t image_storage
Definition develop.h:259
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
int32_t reset
Definition gtk.h:172
dt_iop_buffer_dsc_t dsc
Definition image.h:337
int32_t id
Definition image.h:319
uint32_t filters
Definition format.h:60
unsigned int channels
Definition format.h:54
uint8_t xtrans[6][6]
Definition format.h:70
dt_aligned_pixel_t processed_maximum
Definition format.h:85
dt_iop_highlights_mode_t mode
Definition highlights.c:132
dt_atrous_wavelets_scales_t scales
Definition highlights.c:141
dt_iop_global_data_t * data
Definition imageop.h:233
int32_t hide_enable_button
Definition imageop.h:262
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
gboolean default_enabled
Definition imageop.h:303
dt_iop_global_data_t * global_data
Definition imageop.h:314
gboolean enabled
Definition imageop.h:298
int request_mask_display
Definition imageop.h:268
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29