1 /*
2   This file is part of darktable,
3   Copyright (C) 2015-2021 darktable developers.
4 
5   darktable is free software: you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation, either version 3 of the License, or
8   (at your option) any later version.
9 
10   darktable is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 #include "bauhaus/bauhaus.h"
23 #include "common/colorspaces_inline_conversions.h"
24 #include "common/debug.h"
25 #include "common/imagebuf.h"
26 #include "common/opencl.h"
27 #include "control/control.h"
28 #include "develop/develop.h"
29 #include "develop/imageop.h"
30 #include "develop/imageop_gui.h"
31 #include "develop/tiling.h"
32 #include "gui/accelerators.h"
33 #include "gui/gtk.h"
34 #include "gui/presets.h"
35 #include "iop/iop_api.h"
36 
37 #include <assert.h>
38 #include <gtk/gtk.h>
39 #include <inttypes.h>
40 #include <math.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #define DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S 500
45 #define DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_R 100
46 #define DT_COLORRECONSTRUCT_SPATIAL_APPROX 100.0f
47 
48 DT_MODULE_INTROSPECTION(3, dt_iop_colorreconstruct_params_t)
49 
50 typedef enum dt_iop_colorreconstruct_precedence_t
51 {
52   COLORRECONSTRUCT_PRECEDENCE_NONE,   // $DESCRIPTION: "none" same weighting factor for all pixels
53   COLORRECONSTRUCT_PRECEDENCE_CHROMA, // $DESCRIPTION: "saturated colors" use chromaticy as weighting factor -> prefers saturated colors
54   COLORRECONSTRUCT_PRECEDENCE_HUE     // $DESCRIPTION: "hue" use a specific hue as weighting factor
55 } dt_iop_colorreconstruct_precedence_t;
56 
57 typedef struct dt_iop_colorreconstruct_params1_t
58 {
59   float threshold;
60   float spatial;
61   float range;
62 } dt_iop_colorreconstruct_params1_t;
63 
64 typedef struct dt_iop_colorreconstruct_params2_t
65 {
66   float threshold;
67   float spatial;
68   float range;
69   dt_iop_colorreconstruct_precedence_t precedence;
70 } dt_iop_colorreconstruct_params2_t;
71 
72 typedef struct dt_iop_colorreconstruct_params_t
73 {
74   float threshold; // $MIN: 50.0 $MAX: 150.0 $DEFAULT: 100.0
75   float spatial;   // $MIN: 0.0 $MAX: 1000.0 $DEFAULT: 400.0 $DESCRIPTION: "spatial extent"
76   float range;     // $MIN: 0.0 $MAX: 50.0 $DEFAULT: 10.0 $DESCRIPTION: "range extent"
77   float hue;       // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.66
78   dt_iop_colorreconstruct_precedence_t precedence; // $DEFAULT: 0 COLORRECONSTRUCT_PRECEDENCE_NONE
79 } dt_iop_colorreconstruct_params_t;
80 
81 typedef struct dt_iop_colorreconstruct_Lab_t
82 {
83   float L;
84   float a;
85   float b;
86   float weight;
87 } dt_iop_colorreconstruct_Lab_t;
88 
89 typedef struct dt_iop_colorreconstruct_bilateral_frozen_t
90 {
91   size_t size_x, size_y, size_z;
92   int width, height, x, y;
93   float scale;
94   float sigma_s, sigma_r;
95   dt_iop_colorreconstruct_Lab_t *buf;
96 } dt_iop_colorreconstruct_bilateral_frozen_t;
97 
98 typedef struct dt_iop_colorreconstruct_gui_data_t
99 {
100   GtkWidget *threshold;
101   GtkWidget *spatial;
102   GtkWidget *range;
103   GtkWidget *precedence;
104   GtkWidget *hue;
105   dt_iop_colorreconstruct_bilateral_frozen_t *can;
106   uint64_t hash;
107 } dt_iop_colorreconstruct_gui_data_t;
108 
109 typedef struct dt_iop_colorreconstruct_data_t
110 {
111   float threshold;
112   float spatial;
113   float range;
114   float hue;
115   dt_iop_colorreconstruct_precedence_t precedence;
116 } dt_iop_colorreconstruct_data_t;
117 
118 typedef struct dt_iop_colorreconstruct_global_data_t
119 {
120   int kernel_colorreconstruct_zero;
121   int kernel_colorreconstruct_splat;
122   int kernel_colorreconstruct_blur_line;
123   int kernel_colorreconstruct_slice;
124 } dt_iop_colorreconstruct_global_data_t;
125 
126 
name()127 const char *name()
128 {
129   return _("color reconstruction");
130 }
131 
description(struct dt_iop_module_t * self)132 const char *description(struct dt_iop_module_t *self)
133 {
134   return dt_iop_set_description(self, _("recover clipped highlights by propagating surrounding colors"),
135                                       _("corrective"),
136                                       _("linear or non-linear, Lab, display-referred"),
137                                       _("non-linear, Lab"),
138                                       _("non-linear, Lab, display-referred"));
139 }
140 
flags()141 int flags()
142 {
143   // we do not allow tiling. reason: this module needs to see the full surrounding of highlights.
144   // if we would split into tiles, each tile would result in different color corrections
145   return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING;
146 }
147 
default_group()148 int default_group()
149 {
150   return IOP_GROUP_BASIC | IOP_GROUP_TECHNICAL;
151 }
152 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)153 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
154 {
155   return iop_cs_Lab;
156 }
157 
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)158 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
159                   void *new_params, const int new_version)
160 {
161   if(old_version == 1 && new_version == 3)
162   {
163     const dt_iop_colorreconstruct_params1_t *old = old_params;
164     dt_iop_colorreconstruct_params_t *new = new_params;
165     new->threshold = old->threshold;
166     new->spatial = old->spatial;
167     new->range = old->range;
168     new->precedence = COLORRECONSTRUCT_PRECEDENCE_NONE;
169     new->hue = 0.66f;
170     return 0;
171   }
172   else if(old_version == 2 && new_version == 3)
173   {
174     const dt_iop_colorreconstruct_params2_t *old = old_params;
175     dt_iop_colorreconstruct_params_t *new = new_params;
176     new->threshold = old->threshold;
177     new->spatial = old->spatial;
178     new->range = old->range;
179     new->precedence = old->precedence;
180     new->hue = 0.66f;
181     return 0;
182   }
183   return 1;
184 }
185 
186 typedef struct dt_iop_colorreconstruct_bilateral_t
187 {
188   size_t size_x, size_y, size_z;
189   int width, height, x, y;
190   float scale;
191   float sigma_s, sigma_r;
192   dt_iop_colorreconstruct_Lab_t *buf;
193 } dt_iop_colorreconstruct_bilateral_t;
194 
195 
hue_conversion(const float HSL_Hue)196 static inline float hue_conversion(const float HSL_Hue)
197 {
198   dt_aligned_pixel_t rgb = { 0 };
199   dt_aligned_pixel_t XYZ = { 0 };
200   dt_aligned_pixel_t Lab = { 0 };
201 
202   hsl2rgb(rgb, HSL_Hue, 1.0f, 0.5f);
203 
204   XYZ[0] = (rgb[0] * 0.4360747f) + (rgb[1] * 0.3850649f) + (rgb[2] * 0.1430804f);
205   XYZ[1] = (rgb[0] * 0.2225045f) + (rgb[1] * 0.7168786f) + (rgb[2] * 0.0606169f);
206   XYZ[2] = (rgb[0] * 0.0139322f) + (rgb[1] * 0.0971045f) + (rgb[2] * 0.7141733f);
207 
208   dt_XYZ_to_Lab(XYZ, Lab);
209 
210   // Hue from LCH color space in [-pi, +pi] interval
211   float LCH_hue = atan2f(Lab[2], Lab[1]);
212 
213   return LCH_hue;
214 }
215 
216 
image_to_grid(const dt_iop_colorreconstruct_bilateral_t * const b,const float i,const float j,const float L,float * x,float * y,float * z)217 static inline void image_to_grid(const dt_iop_colorreconstruct_bilateral_t *const b, const float i, const float j, const float L, float *x,
218                           float *y, float *z)
219 {
220   *x = CLAMPS(i / b->sigma_s, 0, b->size_x - 1);
221   *y = CLAMPS(j / b->sigma_s, 0, b->size_y - 1);
222   *z = CLAMPS(L / b->sigma_r, 0, b->size_z - 1);
223 }
224 
grid_rescale(const dt_iop_colorreconstruct_bilateral_t * const b,const int i,const int j,const dt_iop_roi_t * roi,const float scale,float * px,float * py)225 static inline void grid_rescale(const dt_iop_colorreconstruct_bilateral_t *const b, const int i, const int j, const dt_iop_roi_t *roi,
226                          const float scale, float *px, float *py)
227 {
228   *px = (roi->x + i) * scale - b->x;
229   *py = (roi->y + j) * scale - b->y;
230 }
231 
dt_iop_colorreconstruct_bilateral_dump(dt_iop_colorreconstruct_bilateral_frozen_t * bf)232 static void dt_iop_colorreconstruct_bilateral_dump(dt_iop_colorreconstruct_bilateral_frozen_t *bf)
233 {
234   if(!bf) return;
235   dt_free_align(bf->buf);
236   free(bf);
237 }
238 
dt_iop_colorreconstruct_bilateral_free(dt_iop_colorreconstruct_bilateral_t * b)239 static void dt_iop_colorreconstruct_bilateral_free(dt_iop_colorreconstruct_bilateral_t *b)
240 {
241   if(!b) return;
242   dt_free_align(b->buf);
243   free(b);
244 }
245 
dt_iop_colorreconstruct_bilateral_init(const dt_iop_roi_t * roi,const float iscale,const float sigma_s,const float sigma_r)246 static dt_iop_colorreconstruct_bilateral_t *dt_iop_colorreconstruct_bilateral_init(const dt_iop_roi_t *roi, // dimensions of input image
247                                                                                    const float iscale,      // overall scale of input image
248                                                                                    const float sigma_s,     // spatial sigma (blur pixel coords)
249                                                                                    const float sigma_r)     // range sigma (blur luma values)
250 {
251   dt_iop_colorreconstruct_bilateral_t *b = (dt_iop_colorreconstruct_bilateral_t *)malloc(sizeof(dt_iop_colorreconstruct_bilateral_t));
252   if(!b)
253   {
254     fprintf(stderr, "[color reconstruction] not able to allocate buffer (a)\n");
255     return NULL;
256   }
257   float _x = roundf(roi->width / sigma_s);
258   float _y = roundf(roi->height / sigma_s);
259   float _z = roundf(100.0f / sigma_r);
260   b->size_x = CLAMPS((int)_x, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
261   b->size_y = CLAMPS((int)_y, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
262   b->size_z = CLAMPS((int)_z, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_R) + 1;
263   b->width = roi->width;
264   b->height = roi->height;
265   b->x = roi->x;
266   b->y = roi->y;
267   b->scale = iscale / roi->scale;
268   b->sigma_s = MAX(roi->height / (b->size_y - 1.0f), roi->width / (b->size_x - 1.0f));
269   b->sigma_r = 100.0f / (b->size_z - 1.0f);
270   b->buf = dt_alloc_align(64, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
271   if(!b->buf)
272   {
273     fprintf(stderr, "[color reconstruction] not able to allocate buffer (b)\n");
274     dt_iop_colorreconstruct_bilateral_free(b);
275     return NULL;
276   }
277 
278   memset(b->buf, 0, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
279 #if 0
280   fprintf(stderr, "[bilateral] created grid [%d %d %d]"
281           " with sigma (%f %f) (%f %f)\n", b->size_x, b->size_y, b->size_z,
282           b->sigma_s, sigma_s, b->sigma_r, sigma_r);
283 #endif
284   return b;
285 }
286 
dt_iop_colorreconstruct_bilateral_freeze(dt_iop_colorreconstruct_bilateral_t * b)287 static dt_iop_colorreconstruct_bilateral_frozen_t *dt_iop_colorreconstruct_bilateral_freeze(dt_iop_colorreconstruct_bilateral_t *b)
288 {
289   if(!b) return NULL;
290 
291   dt_iop_colorreconstruct_bilateral_frozen_t *bf = (dt_iop_colorreconstruct_bilateral_frozen_t *)malloc(sizeof(dt_iop_colorreconstruct_bilateral_frozen_t));
292   if(!bf)
293   {
294     fprintf(stderr, "[color reconstruction] not able to allocate buffer (c)\n");
295     return NULL;
296   }
297 
298   bf->size_x = b->size_x;
299   bf->size_y = b->size_y;
300   bf->size_z = b->size_z;
301   bf->width = b->width;
302   bf->height = b->height;
303   bf->x = b->x;
304   bf->y = b->y;
305   bf->scale = b->scale;
306   bf->sigma_s = b->sigma_s;
307   bf->sigma_r = b->sigma_r;
308   bf->buf = dt_alloc_align(64, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
309   if(bf->buf && b->buf)
310   {
311     memcpy(bf->buf, b->buf, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
312   }
313   else
314   {
315     fprintf(stderr, "[color reconstruction] not able to allocate buffer (d)\n");
316     dt_iop_colorreconstruct_bilateral_dump(bf);
317     return NULL;
318   }
319 
320   return bf;
321 }
322 
dt_iop_colorreconstruct_bilateral_thaw(dt_iop_colorreconstruct_bilateral_frozen_t * bf)323 static dt_iop_colorreconstruct_bilateral_t *dt_iop_colorreconstruct_bilateral_thaw(dt_iop_colorreconstruct_bilateral_frozen_t *bf)
324 {
325   if(!bf) return NULL;
326 
327   dt_iop_colorreconstruct_bilateral_t *b = (dt_iop_colorreconstruct_bilateral_t *)malloc(sizeof(dt_iop_colorreconstruct_bilateral_t));
328   if(!b)
329   {
330     fprintf(stderr, "[color reconstruction] not able to allocate buffer (e)\n");
331     return NULL;
332   }
333 
334   b->size_x = bf->size_x;
335   b->size_y = bf->size_y;
336   b->size_z = bf->size_z;
337   b->width = bf->width;
338   b->height = bf->height;
339   b->x = bf->x;
340   b->y = bf->y;
341   b->scale = bf->scale;
342   b->sigma_s = bf->sigma_s;
343   b->sigma_r = bf->sigma_r;
344   b->buf = dt_alloc_align(64, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
345   if(b->buf && bf->buf)
346   {
347     memcpy(b->buf, bf->buf, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
348   }
349   else
350   {
351     fprintf(stderr, "[color reconstruction] not able to allocate buffer (f)\n");
352     dt_iop_colorreconstruct_bilateral_free(b);
353     return NULL;
354   }
355 
356   return b;
357 }
358 
359 
dt_iop_colorreconstruct_bilateral_splat(dt_iop_colorreconstruct_bilateral_t * b,const float * const in,const float threshold,dt_iop_colorreconstruct_precedence_t precedence,const float * params)360 static void dt_iop_colorreconstruct_bilateral_splat(dt_iop_colorreconstruct_bilateral_t *b, const float *const in, const float threshold,
361                                                     dt_iop_colorreconstruct_precedence_t precedence, const float *params)
362 {
363   if(!b) return;
364 
365   // splat into downsampled grid
366 #ifdef _OPENMP
367 #pragma omp parallel for default(none) \
368   dt_omp_firstprivate(in, threshold) \
369   shared(b, precedence, params)
370 #endif
371   for(int j = 0; j < b->height; j++)
372   {
373     size_t index = (size_t)4 * j * b->width;
374     for(int i = 0; i < b->width; i++, index += 4)
375     {
376       float x, y, z, weight, m;
377       const float Lin = in[index];
378       const float ain = in[index + 1];
379       const float bin = in[index + 2];
380       // we deliberately ignore pixels above threshold
381       if (Lin > threshold) continue;
382 
383       switch(precedence)
384       {
385         case COLORRECONSTRUCT_PRECEDENCE_CHROMA:
386           weight = sqrtf(ain * ain + bin * bin);
387           break;
388 
389         case COLORRECONSTRUCT_PRECEDENCE_HUE:
390           m = atan2f(bin, ain) - params[0];
391           // readjust m into [-pi, +pi] interval
392           m = m > M_PI ? m - 2*M_PI : (m < -M_PI ? m + 2*M_PI : m);
393           weight = expf(-m*m/params[1]);
394           break;
395 
396         case COLORRECONSTRUCT_PRECEDENCE_NONE:
397         default:
398           weight = 1.0f;
399           break;
400       }
401 
402       image_to_grid(b, i, j, Lin, &x, &y, &z);
403 
404       // closest integer splatting:
405       const int xi = CLAMPS((int)round(x), 0, b->size_x - 1);
406       const int yi = CLAMPS((int)round(y), 0, b->size_y - 1);
407       const int zi = CLAMPS((int)round(z), 0, b->size_z - 1);
408       const size_t grid_index = xi + b->size_x * (yi + b->size_y * zi);
409 
410 #ifdef _OPENMP
411 #pragma omp atomic
412 #endif
413       b->buf[grid_index].L += Lin * weight;
414 
415 #ifdef _OPENMP
416 #pragma omp atomic
417 #endif
418       b->buf[grid_index].a += ain * weight;
419 
420 #ifdef _OPENMP
421 #pragma omp atomic
422 #endif
423       b->buf[grid_index].b += bin * weight;
424 
425 #ifdef _OPENMP
426 #pragma omp atomic
427 #endif
428       b->buf[grid_index].weight += weight;
429     }
430   }
431 }
432 
433 
blur_line(dt_iop_colorreconstruct_Lab_t * buf,const int offset1,const int offset2,const int offset3,const int size1,const int size2,const int size3)434 static void blur_line(dt_iop_colorreconstruct_Lab_t *buf, const int offset1, const int offset2, const int offset3, const int size1,
435                       const int size2, const int size3)
436 {
437   if(!buf) return;
438 
439   const float w0 = 6.f / 16.f;
440   const float w1 = 4.f / 16.f;
441   const float w2 = 1.f / 16.f;
442 #ifdef _OPENMP
443 #pragma omp parallel for default(none) \
444   dt_omp_firstprivate(offset1, offset2, offset3, size1, size2, size3, w0, w1, w2) \
445   shared(buf)
446 #endif
447   for(int k = 0; k < size1; k++)
448   {
449     size_t index = (size_t)k * offset1;
450     for(int j = 0; j < size2; j++)
451     {
452       dt_iop_colorreconstruct_Lab_t tmp1 = buf[index];
453       buf[index].L      = buf[index].L      * w0 + w1 * buf[index + offset3].L      + w2 * buf[index + 2 * offset3].L;
454       buf[index].a      = buf[index].a      * w0 + w1 * buf[index + offset3].a      + w2 * buf[index + 2 * offset3].a;
455       buf[index].b      = buf[index].b      * w0 + w1 * buf[index + offset3].b      + w2 * buf[index + 2 * offset3].b;
456       buf[index].weight = buf[index].weight * w0 + w1 * buf[index + offset3].weight + w2 * buf[index + 2 * offset3].weight;
457       index += offset3;
458       dt_iop_colorreconstruct_Lab_t tmp2 = buf[index];
459       buf[index].L      = buf[index].L      * w0 + w1 * (buf[index + offset3].L      + tmp1.L)      + w2 * buf[index + 2 * offset3].L;
460       buf[index].a      = buf[index].a      * w0 + w1 * (buf[index + offset3].a      + tmp1.a)      + w2 * buf[index + 2 * offset3].a;
461       buf[index].b      = buf[index].b      * w0 + w1 * (buf[index + offset3].b      + tmp1.b)      + w2 * buf[index + 2 * offset3].b;
462       buf[index].weight = buf[index].weight * w0 + w1 * (buf[index + offset3].weight + tmp1.weight) + w2 * buf[index + 2 * offset3].weight;
463       index += offset3;
464       for(int i = 2; i < size3 - 2; i++)
465       {
466         const dt_iop_colorreconstruct_Lab_t tmp3 = buf[index];
467         buf[index].L      = buf[index].L      * w0 + w1 * (buf[index + offset3].L      + tmp2.L)
468                      + w2 * (buf[index + 2 * offset3].L      + tmp1.L);
469         buf[index].a      = buf[index].a      * w0 + w1 * (buf[index + offset3].a      + tmp2.a)
470                      + w2 * (buf[index + 2 * offset3].a      + tmp1.a);
471         buf[index].b      = buf[index].b      * w0 + w1 * (buf[index + offset3].b      + tmp2.b)
472                      + w2 * (buf[index + 2 * offset3].b      + tmp1.b);
473         buf[index].weight = buf[index].weight * w0 + w1 * (buf[index + offset3].weight + tmp2.weight)
474                      + w2 * (buf[index + 2 * offset3].weight + tmp1.weight);
475 
476         index += offset3;
477         tmp1 = tmp2;
478         tmp2 = tmp3;
479       }
480       const dt_iop_colorreconstruct_Lab_t tmp3 = buf[index];
481       buf[index].L      = buf[index].L      * w0 + w1 * (buf[index + offset3].L      + tmp2.L)      + w2 * tmp1.L;
482       buf[index].a      = buf[index].a      * w0 + w1 * (buf[index + offset3].a      + tmp2.a)      + w2 * tmp1.a;
483       buf[index].b      = buf[index].b      * w0 + w1 * (buf[index + offset3].b      + tmp2.b)      + w2 * tmp1.b;
484       buf[index].weight = buf[index].weight * w0 + w1 * (buf[index + offset3].weight + tmp2.weight) + w2 * tmp1.weight;
485       index += offset3;
486       buf[index].L      = buf[index].L      * w0 + w1 * tmp3.L      + w2 * tmp2.L;
487       buf[index].a      = buf[index].a      * w0 + w1 * tmp3.a      + w2 * tmp2.a;
488       buf[index].b      = buf[index].b      * w0 + w1 * tmp3.b      + w2 * tmp2.b;
489       buf[index].weight = buf[index].weight * w0 + w1 * tmp3.weight + w2 * tmp2.weight;
490       index += offset3;
491       index += offset2 - offset3 * size3;
492     }
493   }
494 }
495 
496 
dt_iop_colorreconstruct_bilateral_blur(dt_iop_colorreconstruct_bilateral_t * b)497 static void dt_iop_colorreconstruct_bilateral_blur(dt_iop_colorreconstruct_bilateral_t *b)
498 {
499   if(!b) return;
500 
501   // gaussian up to 3 sigma
502   blur_line(b->buf, b->size_x * b->size_y, b->size_x, 1, b->size_z, b->size_y, b->size_x);
503   // gaussian up to 3 sigma
504   blur_line(b->buf, b->size_x * b->size_y, 1, b->size_x, b->size_z, b->size_x, b->size_y);
505   // gaussian up to 3 sigma
506   blur_line(b->buf, 1, b->size_x, b->size_x * b->size_y, b->size_x, b->size_y, b->size_z);
507 }
508 
dt_iop_colorreconstruct_bilateral_slice(const dt_iop_colorreconstruct_bilateral_t * const b,const float * const in,float * const out,const float threshold,const dt_iop_roi_t * const roi,const float iscale)509 static void dt_iop_colorreconstruct_bilateral_slice(const dt_iop_colorreconstruct_bilateral_t *const b,
510                                                     const float *const in, float *const out,
511                                                     const float threshold, const dt_iop_roi_t *const roi,
512                                                     const float iscale)
513 {
514   if(!b) return;
515 
516   const float rescale = iscale / (roi->scale * b->scale);
517   const int ox = 1;
518   const int oy = b->size_x;
519   const int oz = b->size_y * b->size_x;
520 #ifdef _OPENMP
521 #pragma omp parallel for default(none) \
522   dt_omp_firstprivate(b, in, out, oy, oz, rescale, roi, threshold, ox)
523 #endif
524   for(int j = 0; j < roi->height; j++)
525   {
526     size_t index = (size_t)4 * j * roi->width;
527     for(int i = 0; i < roi->width; i++, index += 4)
528     {
529       float x, y, z;
530       float px, py;
531       const float Lin = out[index + 0] = in[index + 0];
532       const float ain = out[index + 1] = in[index + 1];
533       const float bin = out[index + 2] = in[index + 2];
534       out[index + 3] = in[index + 3];
535       const float blend = CLAMPS(20.0f / threshold * Lin - 19.0f, 0.0f, 1.0f);
536       if (blend == 0.0f) continue;
537       grid_rescale(b, i, j, roi, rescale, &px, &py);
538       image_to_grid(b, px, py, Lin, &x, &y, &z);
539       // trilinear lookup:
540       const int xi = MIN((int)x, b->size_x - 2);
541       const int yi = MIN((int)y, b->size_y - 2);
542       const int zi = MIN((int)z, b->size_z - 2);
543       const float xf = x - xi;
544       const float yf = y - yi;
545       const float zf = z - zi;
546       const size_t gi = xi + b->size_x * (yi + b->size_y * zi);
547 
548       const float Lout =   b->buf[gi].L * (1.0f - xf) * (1.0f - yf) * (1.0f - zf)
549                          + b->buf[gi + ox].L * (xf) * (1.0f - yf) * (1.0f - zf)
550                          + b->buf[gi + oy].L * (1.0f - xf) * (yf) * (1.0f - zf)
551                          + b->buf[gi + ox + oy].L * (xf) * (yf) * (1.0f - zf)
552                          + b->buf[gi + oz].L * (1.0f - xf) * (1.0f - yf) * (zf)
553                          + b->buf[gi + ox + oz].L * (xf) * (1.0f - yf) * (zf)
554                          + b->buf[gi + oy + oz].L * (1.0f - xf) * (yf) * (zf)
555                          + b->buf[gi + ox + oy + oz].L * (xf) * (yf) * (zf);
556 
557       const float aout =   b->buf[gi].a * (1.0f - xf) * (1.0f - yf) * (1.0f - zf)
558                          + b->buf[gi + ox].a * (xf) * (1.0f - yf) * (1.0f - zf)
559                          + b->buf[gi + oy].a * (1.0f - xf) * (yf) * (1.0f - zf)
560                          + b->buf[gi + ox + oy].a * (xf) * (yf) * (1.0f - zf)
561                          + b->buf[gi + oz].a * (1.0f - xf) * (1.0f - yf) * (zf)
562                          + b->buf[gi + ox + oz].a * (xf) * (1.0f - yf) * (zf)
563                          + b->buf[gi + oy + oz].a * (1.0f - xf) * (yf) * (zf)
564                          + b->buf[gi + ox + oy + oz].a * (xf) * (yf) * (zf);
565 
566 
567       const float bout =   b->buf[gi].b * (1.0f - xf) * (1.0f - yf) * (1.0f - zf)
568                          + b->buf[gi + ox].b * (xf) * (1.0f - yf) * (1.0f - zf)
569                          + b->buf[gi + oy].b * (1.0f - xf) * (yf) * (1.0f - zf)
570                          + b->buf[gi + ox + oy].b * (xf) * (yf) * (1.0f - zf)
571                          + b->buf[gi + oz].b * (1.0f - xf) * (1.0f - yf) * (zf)
572                          + b->buf[gi + ox + oz].b * (xf) * (1.0f - yf) * (zf)
573                          + b->buf[gi + oy + oz].b * (1.0f - xf) * (yf) * (zf)
574                          + b->buf[gi + ox + oy + oz].b * (xf) * (yf) * (zf);
575 
576       const float weight = b->buf[gi].weight * (1.0f - xf) * (1.0f - yf) * (1.0f - zf)
577                          + b->buf[gi + ox].weight * (xf) * (1.0f - yf) * (1.0f - zf)
578                          + b->buf[gi + oy].weight * (1.0f - xf) * (yf) * (1.0f - zf)
579                          + b->buf[gi + ox + oy].weight * (xf) * (yf) * (1.0f - zf)
580                          + b->buf[gi + oz].weight * (1.0f - xf) * (1.0f - yf) * (zf)
581                          + b->buf[gi + ox + oz].weight * (xf) * (1.0f - yf) * (zf)
582                          + b->buf[gi + oy + oz].weight * (1.0f - xf) * (yf) * (zf)
583                          + b->buf[gi + ox + oy + oz].weight * (xf) * (yf) * (zf);
584 
585       const float lout = fmax(Lout, 0.01f);
586       out[index + 1] = (weight > 0.0f) ? ain * (1.0f - blend) + aout * Lin/lout * blend : ain;
587       out[index + 2] = (weight > 0.0f) ? bin * (1.0f - blend) + bout * Lin/lout * blend : bin;
588     }
589   }
590 }
591 
592 
process(struct dt_iop_module_t * self,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)593 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
594              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
595 {
596   dt_iop_colorreconstruct_data_t *data = (dt_iop_colorreconstruct_data_t *)piece->data;
597   dt_iop_colorreconstruct_gui_data_t *g = (dt_iop_colorreconstruct_gui_data_t *)self->gui_data;
598   float *in = (float *)ivoid;
599   float *out = (float *)ovoid;
600 
601   const float scale = fmaxf(piece->iscale / roi_in->scale, 1.f);
602   const float sigma_r = fmax(data->range, 0.1f);
603   const float sigma_s = fmax(data->spatial, 1.0f) / scale;
604   const float hue = hue_conversion(data->hue); // convert to LCH hue which better fits to Lab colorspace
605 
606   const dt_aligned_pixel_t params = { hue, M_PI*M_PI/8, 0.0f, 0.0f };
607 
608   dt_iop_colorreconstruct_bilateral_t *b;
609   dt_iop_colorreconstruct_bilateral_frozen_t *can = NULL;
610 
611   // color reconstruction often involves a massive spatial blur of the bilateral grid. this typically requires
612   // more or less the whole image to contribute to the grid. In pixelpipe FULL we can not rely on this
613   // as the pixelpipe might only see part of the image (region of interest). Therefore we "steal" the bilateral grid
614   // of the preview pipe if needed. However, the grid of the preview pipeline is coarser and may lead
615   // to other artifacts so we only want to use it when necessary. The threshold for data->spatial has been selected
616   // arbitrarily.
617   if(sigma_s > DT_COLORRECONSTRUCT_SPATIAL_APPROX
618      && self->dev->gui_attached
619      && g
620      && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
621   {
622     // check how far we are zoomed-in
623     dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
624     int closeup = dt_control_get_dev_closeup();
625     const float min_scale = dt_dev_get_zoom_scale(self->dev, DT_ZOOM_FIT, 1<<closeup, 0);
626     const float cur_scale = dt_dev_get_zoom_scale(self->dev, zoom, 1<<closeup, 0);
627 
628     // if we are zoomed in more than just a little bit, we try to use the canned grid of the preview pipeline
629     if(cur_scale > 1.05f * min_scale)
630     {
631       if(!dt_dev_sync_pixelpipe_hash(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &self->gui_lock, &g->hash))
632         dt_control_log(_("inconsistent output"));
633 
634       dt_iop_gui_enter_critical_section(self);
635       can = g->can;
636       dt_iop_gui_leave_critical_section(self);
637     }
638   }
639 
640   if(can)
641   {
642     b = dt_iop_colorreconstruct_bilateral_thaw(can);
643   }
644   else
645   {
646     b = dt_iop_colorreconstruct_bilateral_init(roi_in, piece->iscale, sigma_s, sigma_r);
647     dt_iop_colorreconstruct_bilateral_splat(b, in, data->threshold, data->precedence, params);
648     dt_iop_colorreconstruct_bilateral_blur(b);
649   }
650 
651   if(!b) goto error;
652 
653   dt_iop_colorreconstruct_bilateral_slice(b, in, out, data->threshold, roi_in, piece->iscale);
654 
655   // here is where we generate the canned bilateral grid of the preview pipe for later use
656   if(self->dev->gui_attached && g && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW) == DT_DEV_PIXELPIPE_PREVIEW)
657   {
658     uint64_t hash = dt_dev_hash_plus(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL);
659     dt_iop_gui_enter_critical_section(self);
660     dt_iop_colorreconstruct_bilateral_dump(g->can);
661     g->can = dt_iop_colorreconstruct_bilateral_freeze(b);
662     g->hash = hash;
663     dt_iop_gui_leave_critical_section(self);
664   }
665 
666   dt_iop_colorreconstruct_bilateral_free(b);
667   return;
668 
669 error:
670   dt_control_log(_("module `color reconstruction' failed"));
671   dt_iop_colorreconstruct_bilateral_free(b);
672   dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, piece->colors);
673 }
674 
675 #ifdef HAVE_OPENCL
676 typedef struct dt_iop_colorreconstruct_bilateral_cl_t
677 {
678   dt_iop_colorreconstruct_global_data_t *global;
679   int devid;
680   size_t size_x, size_y, size_z;
681   int width, height, x, y;
682   float scale;
683   size_t blocksizex, blocksizey;
684   float sigma_s, sigma_r;
685   cl_mem dev_grid;
686   cl_mem dev_grid_tmp;
687 } dt_iop_colorreconstruct_bilateral_cl_t;
688 
dt_iop_colorreconstruct_bilateral_free_cl(dt_iop_colorreconstruct_bilateral_cl_t * b)689 static void dt_iop_colorreconstruct_bilateral_free_cl(dt_iop_colorreconstruct_bilateral_cl_t *b)
690 {
691   if(!b) return;
692   // be sure we're done with the memory:
693   dt_opencl_finish(b->devid);
694   // free device mem
695   dt_opencl_release_mem_object(b->dev_grid);
696   dt_opencl_release_mem_object(b->dev_grid_tmp);
697   free(b);
698 }
699 
dt_iop_colorreconstruct_bilateral_init_cl(const int devid,dt_iop_colorreconstruct_global_data_t * global,const dt_iop_roi_t * roi,const float iscale,const float sigma_s,const float sigma_r)700 static dt_iop_colorreconstruct_bilateral_cl_t *dt_iop_colorreconstruct_bilateral_init_cl(
701                                         const int devid,
702                                         dt_iop_colorreconstruct_global_data_t *global,
703                                         const dt_iop_roi_t *roi, // dimensions of input image
704                                         const float iscale,      // overall scale of input image
705                                         const float sigma_s,     // spatial sigma (blur pixel coords)
706                                         const float sigma_r)     // range sigma (blur luma values)
707 {
708   int blocksizex, blocksizey;
709 
710   dt_opencl_local_buffer_t locopt
711     = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
712                                   .cellsize = 4 * sizeof(float) + sizeof(int), .overhead = 0,
713                                   .sizex = 1 << 6, .sizey = 1 << 6 };
714 
715   if(dt_opencl_local_buffer_opt(devid, global->kernel_colorreconstruct_splat, &locopt))
716   {
717     blocksizex = locopt.sizex;
718     blocksizey = locopt.sizey;
719   }
720   else
721     blocksizex = blocksizey = 1;
722 
723   if(blocksizex * blocksizey < 16 * 16)
724   {
725     dt_print(DT_DEBUG_OPENCL,
726              "[opencl_colorreconstruction] device %d does not offer sufficient resources to run bilateral grid\n",
727              devid);
728     return NULL;
729   }
730 
731   dt_iop_colorreconstruct_bilateral_cl_t *b = (dt_iop_colorreconstruct_bilateral_cl_t *)malloc(sizeof(dt_iop_colorreconstruct_bilateral_cl_t));
732   if(!b)
733   {
734     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate host buffer (a)\n");
735     return NULL;
736   }
737 
738   float _x = roundf(roi->width / sigma_s);
739   float _y = roundf(roi->height / sigma_s);
740   float _z = roundf(100.0f / sigma_r);
741   b->size_x = CLAMPS((int)_x, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
742   b->size_y = CLAMPS((int)_y, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
743   b->size_z = CLAMPS((int)_z, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_R) + 1;
744   b->width = roi->width;
745   b->height = roi->height;
746   b->x = roi->x;
747   b->y = roi->y;
748   b->scale = iscale / roi->scale;
749   b->blocksizex = blocksizex;
750   b->blocksizey = blocksizey;
751   b->sigma_s = MAX(roi->height / (b->size_y - 1.0f), roi->width / (b->size_x - 1.0f));
752   b->sigma_r = 100.0f / (b->size_z - 1.0f);
753   b->devid = devid;
754   b->global = global;
755   b->dev_grid = NULL;
756   b->dev_grid_tmp = NULL;
757 
758   // alloc grid buffer:
759   b->dev_grid
760       = dt_opencl_alloc_device_buffer(b->devid, sizeof(float) * 4 * b->size_x * b->size_y * b->size_z);
761   if(!b->dev_grid)
762   {
763     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate device buffer (b)\n");
764     dt_iop_colorreconstruct_bilateral_free_cl(b);
765     return NULL;
766   }
767 
768   // alloc temporary grid buffer
769   b->dev_grid_tmp
770       = dt_opencl_alloc_device_buffer(b->devid, sizeof(float) * 4 * b->size_x * b->size_y * b->size_z);
771   if(!b->dev_grid_tmp)
772   {
773     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate device buffer (c)\n");
774     dt_iop_colorreconstruct_bilateral_free_cl(b);
775     return NULL;
776   }
777 
778   // zero out grid
779   int wd = 4 * b->size_x, ht = b->size_y * b->size_z;
780   size_t sizes[] = { ROUNDUPWD(wd), ROUNDUPHT(ht), 1 };
781   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_zero, 0, sizeof(cl_mem), (void *)&b->dev_grid);
782   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_zero, 1, sizeof(int), (void *)&wd);
783   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_zero, 2, sizeof(int), (void *)&ht);
784   cl_int err = -666;
785   err = dt_opencl_enqueue_kernel_2d(b->devid, b->global->kernel_colorreconstruct_zero, sizes);
786   if(err != CL_SUCCESS)
787   {
788     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] error running kernel colorreconstruct_zero: %d\n", err);
789     dt_iop_colorreconstruct_bilateral_free_cl(b);
790     return NULL;
791   }
792 
793 #if 0
794   fprintf(stderr, "[bilateral] created grid [%d %d %d]"
795           " with sigma (%f %f) (%f %f)\n", b->size_x, b->size_y, b->size_z,
796           b->sigma_s, sigma_s, b->sigma_r, sigma_r);
797 #endif
798   return b;
799 }
800 
dt_iop_colorreconstruct_bilateral_freeze_cl(dt_iop_colorreconstruct_bilateral_cl_t * b)801 static dt_iop_colorreconstruct_bilateral_frozen_t *dt_iop_colorreconstruct_bilateral_freeze_cl(dt_iop_colorreconstruct_bilateral_cl_t *b)
802 {
803   if(!b) return NULL;
804 
805   dt_iop_colorreconstruct_bilateral_frozen_t *bf = (dt_iop_colorreconstruct_bilateral_frozen_t *)malloc(sizeof(dt_iop_colorreconstruct_bilateral_frozen_t));
806   if(!bf)
807   {
808     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate host buffer (d)\n");
809     return NULL;
810   }
811 
812   bf->size_x = b->size_x;
813   bf->size_y = b->size_y;
814   bf->size_z = b->size_z;
815   bf->width = b->width;
816   bf->height = b->height;
817   bf->x = b->x;
818   bf->y = b->y;
819   bf->scale = b->scale;
820   bf->sigma_s = b->sigma_s;
821   bf->sigma_r = b->sigma_r;
822   bf->buf = dt_alloc_align(64, sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z);
823   if(bf->buf && b->dev_grid)
824   {
825     // read bilateral grid from device memory to host buffer (blocking)
826     cl_int err = dt_opencl_read_buffer_from_device(b->devid, bf->buf, b->dev_grid, 0,
827                                     sizeof(dt_iop_colorreconstruct_Lab_t) * b->size_x * b->size_y * b->size_z, TRUE);
828     if(err != CL_SUCCESS)
829     {
830       dt_print(DT_DEBUG_OPENCL,
831            "[opencl_colorreconstruction] can not read bilateral grid from device %d\n", b->devid);
832       dt_iop_colorreconstruct_bilateral_dump(bf);
833       return NULL;
834     }
835   }
836   else
837   {
838     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate host buffer (e)\n");
839     dt_iop_colorreconstruct_bilateral_dump(bf);
840     return NULL;
841   }
842 
843   return bf;
844 }
845 
dt_iop_colorreconstruct_bilateral_thaw_cl(dt_iop_colorreconstruct_bilateral_frozen_t * bf,const int devid,dt_iop_colorreconstruct_global_data_t * global)846 static dt_iop_colorreconstruct_bilateral_cl_t *dt_iop_colorreconstruct_bilateral_thaw_cl(dt_iop_colorreconstruct_bilateral_frozen_t *bf,
847                                                                                          const int devid,
848                                                                                          dt_iop_colorreconstruct_global_data_t *global)
849 {
850   if(!bf || !bf->buf) return NULL;
851 
852   int blocksizex, blocksizey;
853 
854   dt_opencl_local_buffer_t locopt
855     = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
856                                   .cellsize = 4 * sizeof(float) + sizeof(int), .overhead = 0,
857                                   .sizex = 1 << 6, .sizey = 1 << 6 };
858 
859   if(dt_opencl_local_buffer_opt(devid, global->kernel_colorreconstruct_splat, &locopt))
860   {
861     blocksizex = locopt.sizex;
862     blocksizey = locopt.sizey;
863   }
864   else
865     blocksizex = blocksizey = 1;
866 
867   if(blocksizex * blocksizey < 16 * 16)
868   {
869     dt_print(DT_DEBUG_OPENCL,
870              "[opencl_colorreconstruction] device %d does not offer sufficient resources to run bilateral grid\n",
871              devid);
872     return NULL;
873   }
874 
875   dt_iop_colorreconstruct_bilateral_cl_t *b = (dt_iop_colorreconstruct_bilateral_cl_t *)malloc(sizeof(dt_iop_colorreconstruct_bilateral_cl_t));
876   if(!b)
877   {
878     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate host buffer (f)\n");
879     return NULL;
880   }
881 
882   b->devid = devid;
883   b->blocksizex = blocksizex;
884   b->blocksizey = blocksizey;
885   b->global = global;
886   b->size_x = bf->size_x;
887   b->size_y = bf->size_y;
888   b->size_z = bf->size_z;
889   b->width = bf->width;
890   b->height = bf->height;
891   b->x = bf->x;
892   b->y = bf->y;
893   b->scale = bf->scale;
894   b->sigma_s = bf->sigma_s;
895   b->sigma_r = bf->sigma_r;
896   b->dev_grid = NULL;
897   b->dev_grid_tmp = NULL;
898 
899   // alloc grid buffer:
900   b->dev_grid
901       = dt_opencl_alloc_device_buffer(b->devid, sizeof(float) * 4 * b->size_x * b->size_y * b->size_z);
902   if(!b->dev_grid)
903   {
904     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate device buffer (g)\n");
905     dt_iop_colorreconstruct_bilateral_free_cl(b);
906     return NULL;
907   }
908 
909   // alloc temporary grid buffer
910   b->dev_grid_tmp
911       = dt_opencl_alloc_device_buffer(b->devid, sizeof(float) * 4 * b->size_x * b->size_y * b->size_z);
912   if(!b->dev_grid_tmp)
913   {
914     dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] not able to allocate device buffer (h)\n");
915     dt_iop_colorreconstruct_bilateral_free_cl(b);
916     return NULL;
917   }
918 
919   if(bf->buf)
920   {
921     // write bilateral grid from host buffer to device memory (blocking)
922     cl_int err = dt_opencl_write_buffer_to_device(b->devid, bf->buf, b->dev_grid, 0,
923                                     bf->size_x * bf->size_y * bf->size_z * sizeof(dt_iop_colorreconstruct_Lab_t), TRUE);
924     if(err != CL_SUCCESS)
925     {
926       dt_print(DT_DEBUG_OPENCL,
927            "[opencl_colorreconstruction] can not write bilateral grid to device %d\n", b->devid);
928       dt_iop_colorreconstruct_bilateral_free_cl(b);
929       return NULL;
930     }
931   }
932 
933   return b;
934 }
935 
dt_iop_colorreconstruct_bilateral_splat_cl(dt_iop_colorreconstruct_bilateral_cl_t * b,cl_mem in,const float threshold,dt_iop_colorreconstruct_precedence_t precedence,const float * params)936 static cl_int dt_iop_colorreconstruct_bilateral_splat_cl(dt_iop_colorreconstruct_bilateral_cl_t *b, cl_mem in, const float threshold,
937                                                          dt_iop_colorreconstruct_precedence_t precedence, const float *params)
938 {
939   cl_int err = -666;
940   if(!b) return err;
941   int pref = precedence;
942   size_t sizes[] = { ROUNDUP(b->width, b->blocksizex), ROUNDUP(b->height, b->blocksizey), 1 };
943   size_t local[] = { b->blocksizex, b->blocksizey, 1 };
944   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 0, sizeof(cl_mem), (void *)&in);
945   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 1, sizeof(cl_mem), (void *)&b->dev_grid);
946   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 2, sizeof(int), (void *)&b->width);
947   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 3, sizeof(int), (void *)&b->height);
948   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 4, sizeof(int), (void *)&b->size_x);
949   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 5, sizeof(int), (void *)&b->size_y);
950   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 6, sizeof(int), (void *)&b->size_z);
951   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 7, sizeof(float), (void *)&b->sigma_s);
952   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 8, sizeof(float), (void *)&b->sigma_r);
953   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 9, sizeof(float), (void *)&threshold);
954   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 10, sizeof(int), (void *)&pref);
955   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 11, 4*sizeof(float), (void *)params);
956   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 12, b->blocksizex * b->blocksizey * sizeof(int),
957                            NULL);
958   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_splat, 13,
959                            b->blocksizex * b->blocksizey * 4 * sizeof(float), NULL);
960   err = dt_opencl_enqueue_kernel_2d_with_local(b->devid, b->global->kernel_colorreconstruct_splat, sizes, local);
961   return err;
962 }
963 
dt_iop_colorreconstruct_bilateral_blur_cl(dt_iop_colorreconstruct_bilateral_cl_t * b)964 static cl_int dt_iop_colorreconstruct_bilateral_blur_cl(dt_iop_colorreconstruct_bilateral_cl_t *b)
965 {
966   cl_int err = -666;
967   if(!b) return err;
968   size_t sizes[3] = { 0, 0, 1 };
969 
970   err = dt_opencl_enqueue_copy_buffer_to_buffer(b->devid, b->dev_grid, b->dev_grid_tmp, 0, 0,
971                                                 b->size_x * b->size_y * b->size_z * 4 * sizeof(float));
972   if(err != CL_SUCCESS) return err;
973 
974   sizes[0] = ROUNDUPWD(b->size_z);
975   sizes[1] = ROUNDUPHT(b->size_y);
976   int stride1, stride2, stride3;
977   stride1 = b->size_x * b->size_y;
978   stride2 = b->size_x;
979   stride3 = 1;
980   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 0, sizeof(cl_mem), (void *)&b->dev_grid_tmp);
981   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 1, sizeof(cl_mem), (void *)&b->dev_grid);
982   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 2, sizeof(int), (void *)&stride1);
983   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 3, sizeof(int), (void *)&stride2);
984   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 4, sizeof(int), (void *)&stride3);
985   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 5, sizeof(int), (void *)&b->size_z);
986   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 6, sizeof(int), (void *)&b->size_y);
987   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 7, sizeof(int), (void *)&b->size_x);
988   err = dt_opencl_enqueue_kernel_2d(b->devid, b->global->kernel_colorreconstruct_blur_line, sizes);
989   if(err != CL_SUCCESS) return err;
990 
991   stride1 = b->size_x * b->size_y;
992   stride2 = 1;
993   stride3 = b->size_x;
994   sizes[0] = ROUNDUPWD(b->size_z);
995   sizes[1] = ROUNDUPHT(b->size_x);
996   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 0, sizeof(cl_mem), (void *)&b->dev_grid);
997   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 1, sizeof(cl_mem), (void *)&b->dev_grid_tmp);
998   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 2, sizeof(int), (void *)&stride1);
999   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 3, sizeof(int), (void *)&stride2);
1000   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 4, sizeof(int), (void *)&stride3);
1001   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 5, sizeof(int), (void *)&b->size_z);
1002   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 6, sizeof(int), (void *)&b->size_x);
1003   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 7, sizeof(int), (void *)&b->size_y);
1004   err = dt_opencl_enqueue_kernel_2d(b->devid, b->global->kernel_colorreconstruct_blur_line, sizes);
1005   if(err != CL_SUCCESS) return err;
1006 
1007   stride1 = 1;
1008   stride2 = b->size_x;
1009   stride3 = b->size_x * b->size_y;
1010   sizes[0] = ROUNDUPWD(b->size_x);
1011   sizes[1] = ROUNDUPHT(b->size_y);
1012   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 0, sizeof(cl_mem),
1013                            (void *)&b->dev_grid_tmp);
1014   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 1, sizeof(cl_mem), (void *)&b->dev_grid);
1015   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 2, sizeof(int), (void *)&stride1);
1016   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 3, sizeof(int), (void *)&stride2);
1017   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 4, sizeof(int), (void *)&stride3);
1018   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 5, sizeof(int), (void *)&b->size_x);
1019   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 6, sizeof(int), (void *)&b->size_y);
1020   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_blur_line, 7, sizeof(int), (void *)&b->size_z);
1021   err = dt_opencl_enqueue_kernel_2d(b->devid, b->global->kernel_colorreconstruct_blur_line, sizes);
1022   return err;
1023 }
1024 
dt_iop_colorreconstruct_bilateral_slice_cl(dt_iop_colorreconstruct_bilateral_cl_t * b,cl_mem in,cl_mem out,const float threshold,const dt_iop_roi_t * roi,const float iscale)1025 static cl_int dt_iop_colorreconstruct_bilateral_slice_cl(dt_iop_colorreconstruct_bilateral_cl_t *b, cl_mem in, cl_mem out,
1026                                                          const float threshold, const dt_iop_roi_t *roi, const float iscale)
1027 {
1028   cl_int err = -666;
1029   if(!b) return err;
1030   const int bxy[2] = { b->x, b->y };
1031   const int roixy[2] = { roi->x, roi->y };
1032   const float rescale = iscale / (roi->scale * b->scale);
1033 
1034   size_t sizes[] = { ROUNDUPWD(roi->width), ROUNDUPHT(roi->height), 1 };
1035   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 0, sizeof(cl_mem), (void *)&in);
1036   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 1, sizeof(cl_mem), (void *)&out);
1037   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 2, sizeof(cl_mem), (void *)&b->dev_grid);
1038   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 3, sizeof(int), (void *)&roi->width);
1039   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 4, sizeof(int), (void *)&roi->height);
1040   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 5, sizeof(int), (void *)&b->size_x);
1041   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 6, sizeof(int), (void *)&b->size_y);
1042   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 7, sizeof(int), (void *)&b->size_z);
1043   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 8, sizeof(float), (void *)&b->sigma_s);
1044   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 9, sizeof(float), (void *)&b->sigma_r);
1045   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 10, sizeof(float), (void *)&threshold);
1046   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 11, 2*sizeof(int), (void *)&bxy);
1047   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 12, 2*sizeof(int), (void *)&roixy);
1048   dt_opencl_set_kernel_arg(b->devid, b->global->kernel_colorreconstruct_slice, 13, sizeof(float), (void *)&rescale);
1049   err = dt_opencl_enqueue_kernel_2d(b->devid, b->global->kernel_colorreconstruct_slice, sizes);
1050   return err;
1051 }
1052 
process_cl(struct dt_iop_module_t * self,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)1053 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
1054                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1055 {
1056   dt_iop_colorreconstruct_data_t *d = (dt_iop_colorreconstruct_data_t *)piece->data;
1057   dt_iop_colorreconstruct_global_data_t *gd = (dt_iop_colorreconstruct_global_data_t *)self->global_data;
1058   dt_iop_colorreconstruct_gui_data_t *g = (dt_iop_colorreconstruct_gui_data_t *)self->gui_data;
1059 
1060   const float scale = piece->iscale / roi_in->scale;
1061   const float sigma_r = fmax(d->range, 0.1f); // does not depend on scale
1062   const float sigma_s = fmax(d->spatial, 1.0f) / scale;
1063   const float hue = hue_conversion(d->hue); // convert to LCH hue which better fits to Lab colorspace
1064 
1065   const float params[4] = { hue, M_PI*M_PI/8, 0.0f, 0.0f };
1066 
1067   cl_int err = -666;
1068 
1069   dt_iop_colorreconstruct_bilateral_cl_t *b;
1070   dt_iop_colorreconstruct_bilateral_frozen_t *can = NULL;
1071 
1072   // see process() for more details on how we transfer a bilateral grid from the preview to the full pipeline
1073   if(sigma_s > DT_COLORRECONSTRUCT_SPATIAL_APPROX
1074      && self->dev->gui_attached
1075      && g
1076      && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
1077   {
1078     // check how far we are zoomed-in
1079     dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
1080     int closeup = dt_control_get_dev_closeup();
1081     const float min_scale = dt_dev_get_zoom_scale(self->dev, DT_ZOOM_FIT, 1<<closeup, 0);
1082     const float cur_scale = dt_dev_get_zoom_scale(self->dev, zoom, 1<<closeup, 0);
1083 
1084     // if we are zoomed in more than just a little bit, we try to use the canned grid of the preview pipeline
1085     if(cur_scale > 1.05f * min_scale)
1086     {
1087       if(!dt_dev_sync_pixelpipe_hash(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &self->gui_lock, &g->hash))
1088         dt_control_log(_("inconsistent output"));
1089 
1090       dt_iop_gui_enter_critical_section(self);
1091       can = g->can;
1092       dt_iop_gui_leave_critical_section(self);
1093     }
1094   }
1095 
1096   if(can)
1097   {
1098     b = dt_iop_colorreconstruct_bilateral_thaw_cl(can, piece->pipe->devid, gd);
1099     if(!b) goto error;
1100   }
1101   else
1102   {
1103     b = dt_iop_colorreconstruct_bilateral_init_cl(piece->pipe->devid, gd, roi_in, piece->iscale, sigma_s, sigma_r);
1104     if(!b) goto error;
1105     err = dt_iop_colorreconstruct_bilateral_splat_cl(b, dev_in, d->threshold, d->precedence, params);
1106     if(err != CL_SUCCESS) goto error;
1107     err = dt_iop_colorreconstruct_bilateral_blur_cl(b);
1108     if(err != CL_SUCCESS) goto error;
1109   }
1110 
1111   err = dt_iop_colorreconstruct_bilateral_slice_cl(b, dev_in, dev_out, d->threshold, roi_in, piece->iscale);
1112   if(err != CL_SUCCESS) goto error;
1113 
1114   if(self->dev->gui_attached && g && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW) == DT_DEV_PIXELPIPE_PREVIEW)
1115   {
1116     uint64_t hash = dt_dev_hash_plus(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL);
1117     dt_iop_gui_enter_critical_section(self);
1118     dt_iop_colorreconstruct_bilateral_dump(g->can);
1119     g->can = dt_iop_colorreconstruct_bilateral_freeze_cl(b);
1120     g->hash = hash;
1121     dt_iop_gui_leave_critical_section(self);
1122   }
1123 
1124   dt_iop_colorreconstruct_bilateral_free_cl(b);
1125   return TRUE;
1126 
1127 error:
1128   dt_iop_colorreconstruct_bilateral_free_cl(b);
1129   dt_print(DT_DEBUG_OPENCL, "[opencl_colorreconstruction] couldn't enqueue kernel! %d\n", err);
1130   return FALSE;
1131 }
1132 #endif
1133 
1134 
dt_iop_colorreconstruct_bilateral_memory_use(const int width,const int height,const float sigma_s,const float sigma_r)1135 static size_t dt_iop_colorreconstruct_bilateral_memory_use(const int width,     // width of input image
1136                                                            const int height,    // height of input image
1137                                                            const float sigma_s, // spatial sigma (blur pixel coords)
1138                                                            const float sigma_r) // range sigma (blur luma values)
1139 {
1140   float _x = roundf(width / sigma_s);
1141   float _y = roundf(height / sigma_s);
1142   float _z = roundf(100.0f / sigma_r);
1143   size_t size_x = CLAMPS((int)_x, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
1144   size_t size_y = CLAMPS((int)_y, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
1145   size_t size_z = CLAMPS((int)_z, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_R) + 1;
1146 
1147   return size_x * size_y * size_z * 4 * sizeof(float) * 2;   // in fact only the OpenCL path needs a second tmp buffer
1148 }
1149 
1150 
dt_iop_colorreconstruct_bilateral_singlebuffer_size(const int width,const int height,const float sigma_s,const float sigma_r)1151 static size_t dt_iop_colorreconstruct_bilateral_singlebuffer_size(const int width,     // width of input image
1152                                                                   const int height,    // height of input image
1153                                                                   const float sigma_s, // spatial sigma (blur pixel coords)
1154                                                                   const float sigma_r) // range sigma (blur luma values)
1155 {
1156   float _x = roundf(width / sigma_s);
1157   float _y = roundf(height / sigma_s);
1158   float _z = roundf(100.0f / sigma_r);
1159   size_t size_x = CLAMPS((int)_x, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
1160   size_t size_y = CLAMPS((int)_y, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_S) + 1;
1161   size_t size_z = CLAMPS((int)_z, 4, DT_COLORRECONSTRUCT_BILATERAL_MAX_RES_R) + 1;
1162 
1163   return size_x * size_y * size_z * 4 * sizeof(float);
1164 }
1165 
tiling_callback(struct dt_iop_module_t * self,struct dt_dev_pixelpipe_iop_t * piece,const dt_iop_roi_t * roi_in,const dt_iop_roi_t * roi_out,struct dt_develop_tiling_t * tiling)1166 void tiling_callback(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
1167                      const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out,
1168                      struct dt_develop_tiling_t *tiling)
1169 {
1170   dt_iop_colorreconstruct_data_t *d = (dt_iop_colorreconstruct_data_t *)piece->data;
1171   // the total scale is composed of scale before input to the pipeline (iscale),
1172   // and the scale of the roi.
1173   const float scale = piece->iscale / roi_in->scale;
1174   const float sigma_r = fmax(d->range, 0.1f);
1175   const float sigma_s = fmax(d->spatial, 1.0f) / scale;
1176 
1177   const int width = roi_in->width;
1178   const int height = roi_in->height;
1179   const int channels = piece->colors;
1180 
1181   const size_t basebuffer = sizeof(float) * channels * width * height;
1182 
1183   tiling->factor = 2.0f + (float)dt_iop_colorreconstruct_bilateral_memory_use(width, height, sigma_s, sigma_r) / basebuffer;
1184   tiling->maxbuf
1185       = fmax(1.0f, (float)dt_iop_colorreconstruct_bilateral_singlebuffer_size(width, height, sigma_s, sigma_r) / basebuffer);
1186   tiling->overhead = 0;
1187   tiling->overlap = ceilf(4 * sigma_s);
1188   tiling->xalign = 1;
1189   tiling->yalign = 1;
1190   return;
1191 }
1192 
1193 
gui_changed(dt_iop_module_t * self,GtkWidget * w,void * previous)1194 void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1195 {
1196   dt_iop_colorreconstruct_params_t *p = (dt_iop_colorreconstruct_params_t *)self->params;
1197   dt_iop_colorreconstruct_gui_data_t *g = (dt_iop_colorreconstruct_gui_data_t *)self->gui_data;
1198   if(w == g->precedence)
1199   {
1200     gtk_widget_set_visible(g->hue, p->precedence == COLORRECONSTRUCT_PRECEDENCE_HUE);
1201   }
1202 }
1203 
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1204 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
1205                    dt_dev_pixelpipe_iop_t *piece)
1206 {
1207   dt_iop_colorreconstruct_params_t *p = (dt_iop_colorreconstruct_params_t *)p1;
1208   dt_iop_colorreconstruct_data_t *d = (dt_iop_colorreconstruct_data_t *)piece->data;
1209 
1210   d->threshold = p->threshold;
1211   d->spatial = p->spatial;
1212   d->range = p->range;
1213   d->precedence = p->precedence;
1214   d->hue = p->hue;
1215 
1216 #ifdef HAVE_OPENCL
1217   piece->process_cl_ready = (piece->process_cl_ready && !(darktable.opencl->avoid_atomics));
1218 #endif
1219 }
1220 
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1221 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1222 {
1223   dt_iop_colorreconstruct_data_t *d = (dt_iop_colorreconstruct_data_t *)calloc(1, sizeof(dt_iop_colorreconstruct_data_t));
1224   piece->data = (void *)d;
1225 }
1226 
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1227 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1228 {
1229   free(piece->data);
1230   piece->data = NULL;
1231 }
1232 
gui_update(struct dt_iop_module_t * self)1233 void gui_update(struct dt_iop_module_t *self)
1234 {
1235   dt_iop_colorreconstruct_gui_data_t *g = (dt_iop_colorreconstruct_gui_data_t *)self->gui_data;
1236   dt_iop_colorreconstruct_params_t *p = (dt_iop_colorreconstruct_params_t *)self->params;
1237 
1238   dt_bauhaus_slider_set(g->threshold, p->threshold);
1239   dt_bauhaus_slider_set(g->spatial, p->spatial);
1240   dt_bauhaus_slider_set(g->range, p->range);
1241   dt_bauhaus_combobox_set(g->precedence, p->precedence);
1242   dt_bauhaus_slider_set(g->hue, p->hue);
1243 
1244   gtk_widget_set_visible(g->hue, p->precedence == COLORRECONSTRUCT_PRECEDENCE_HUE);
1245 
1246   dt_iop_gui_enter_critical_section(self);
1247   dt_iop_colorreconstruct_bilateral_dump(g->can);
1248   g->can = NULL;
1249   g->hash = 0;
1250   dt_iop_gui_leave_critical_section(self);
1251 }
1252 
init_global(dt_iop_module_so_t * module)1253 void init_global(dt_iop_module_so_t *module)
1254 {
1255   dt_iop_colorreconstruct_global_data_t *gd
1256       = (dt_iop_colorreconstruct_global_data_t *)malloc(sizeof(dt_iop_colorreconstruct_global_data_t));
1257   module->data = gd;
1258   const int program = 13; // colorcorrection.cl, from programs.conf
1259   gd->kernel_colorreconstruct_zero = dt_opencl_create_kernel(program, "colorreconstruction_zero");
1260   gd->kernel_colorreconstruct_splat = dt_opencl_create_kernel(program, "colorreconstruction_splat");
1261   gd->kernel_colorreconstruct_blur_line = dt_opencl_create_kernel(program, "colorreconstruction_blur_line");
1262   gd->kernel_colorreconstruct_slice = dt_opencl_create_kernel(program, "colorreconstruction_slice");
1263 }
1264 
cleanup_global(dt_iop_module_so_t * module)1265 void cleanup_global(dt_iop_module_so_t *module)
1266 {
1267   dt_iop_colorreconstruct_global_data_t *gd = (dt_iop_colorreconstruct_global_data_t *)module->data;
1268   dt_opencl_free_kernel(gd->kernel_colorreconstruct_zero);
1269   dt_opencl_free_kernel(gd->kernel_colorreconstruct_splat);
1270   dt_opencl_free_kernel(gd->kernel_colorreconstruct_blur_line);
1271   dt_opencl_free_kernel(gd->kernel_colorreconstruct_slice);
1272   free(module->data);
1273   module->data = NULL;
1274 }
1275 
1276 
gui_init(struct dt_iop_module_t * self)1277 void gui_init(struct dt_iop_module_t *self)
1278 {
1279   dt_iop_colorreconstruct_gui_data_t *g = IOP_GUI_ALLOC(colorreconstruct);
1280 
1281   g->can = NULL;
1282   g->hash = 0;
1283 
1284   g->threshold = dt_bauhaus_slider_from_params(self, N_("threshold"));
1285   dt_bauhaus_slider_set_step(g->threshold, 0.1f);
1286   g->spatial = dt_bauhaus_slider_from_params(self, N_("spatial"));
1287   g->range = dt_bauhaus_slider_from_params(self, N_("range"));
1288   dt_bauhaus_slider_set_step(g->range, 0.1f);
1289   g->precedence = dt_bauhaus_combobox_from_params(self, N_("precedence"));
1290   g->hue = dt_bauhaus_slider_from_params(self, N_("hue"));
1291   dt_bauhaus_slider_set_factor(g->hue, 360.0f);
1292   dt_bauhaus_slider_set_format(g->hue, "%.2f°");
1293   dt_bauhaus_slider_set_feedback(g->hue, 0);
1294   dt_bauhaus_slider_set_stop(g->hue, 0.0f,   1.0f, 0.0f, 0.0f);
1295   dt_bauhaus_slider_set_stop(g->hue, 0.166f, 1.0f, 1.0f, 0.0f);
1296   dt_bauhaus_slider_set_stop(g->hue, 0.322f, 0.0f, 1.0f, 0.0f);
1297   dt_bauhaus_slider_set_stop(g->hue, 0.498f, 0.0f, 1.0f, 1.0f);
1298   dt_bauhaus_slider_set_stop(g->hue, 0.664f, 0.0f, 0.0f, 1.0f);
1299   dt_bauhaus_slider_set_stop(g->hue, 0.830f, 1.0f, 0.0f, 1.0f);
1300   dt_bauhaus_slider_set_stop(g->hue, 1.0f,   1.0f, 0.0f, 0.0f);
1301 
1302   gtk_widget_show_all(g->hue);
1303   gtk_widget_set_no_show_all(g->hue, TRUE);
1304 
1305   gtk_widget_set_tooltip_text(g->threshold, _("pixels with lightness values above this threshold are corrected"));
1306   gtk_widget_set_tooltip_text(g->spatial, _("how far to look for replacement colors in spatial dimensions"));
1307   gtk_widget_set_tooltip_text(g->range, _("how far to look for replacement colors in the luminance dimension"));
1308   gtk_widget_set_tooltip_text(g->precedence, _("if and how to give precedence to specific replacement colors"));
1309   gtk_widget_set_tooltip_text(g->hue, _("the hue tone which should be given precedence over other hue tones"));
1310 }
1311 
gui_cleanup(struct dt_iop_module_t * self)1312 void gui_cleanup(struct dt_iop_module_t *self)
1313 {
1314   dt_iop_colorreconstruct_gui_data_t *g = (dt_iop_colorreconstruct_gui_data_t *)self->gui_data;
1315   dt_iop_colorreconstruct_bilateral_dump(g->can);
1316 
1317   IOP_GUI_FREE;
1318 }
1319 
1320 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1321 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1322 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1323