1 /*
2     This file is part of darktable,
3     Copyright (C) 2013-2020 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 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 #include "bauhaus/bauhaus.h"
22 #include "common/bilateral.h"
23 #include "common/bilateralcl.h"
24 #include "common/colorspaces.h"
25 #include "common/imagebuf.h"
26 #include "common/opencl.h"
27 #include "common/points.h"
28 #include "control/control.h"
29 #include "develop/develop.h"
30 #include "develop/imageop.h"
31 #include "develop/imageop_math.h"
32 #include "develop/imageop_gui.h"
33 #include "develop/tiling.h"
34 #include "dtgtk/drawingarea.h"
35 #include "dtgtk/resetlabel.h"
36 #include "gui/accelerators.h"
37 #include "gui/gtk.h"
38 #include "iop/iop_api.h"
39 
40 #include <gtk/gtk.h>
41 #include <inttypes.h>
42 #include <math.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <strings.h>
46 
47 /**
48  * color transfer somewhat based on the glorious paper `color transfer between images'
49  * by erik reinhard, michael ashikhmin, bruce gooch, and peter shirley, 2001.
50  * chosen because it officially cites the playboy.
51  *
52  * workflow:
53  * - open the target image, press acquire button
54  * - right click store as preset
55  * - open image you want to transfer the color to
56  * - right click and apply the preset
57  */
58 
59 DT_MODULE_INTROSPECTION(1, dt_iop_colormapping_params_t)
60 
61 #define HISTN (1 << 11)
62 #define MAXN 5
63 
64 typedef float float2[2];
65 
66 typedef enum dt_iop_colormapping_flags_t
67 {
68   NEUTRAL = 0,
69   HAS_SOURCE = 1 << 0,
70   HAS_TARGET = 1 << 1,
71   HAS_SOURCE_TARGET = HAS_SOURCE | HAS_TARGET,
72   ACQUIRE = 1 << 2,
73   GET_SOURCE = 1 << 3,
74   GET_TARGET = 1 << 4
75 } dt_iop_colormapping_flags_t;
76 
77 typedef struct dt_iop_colormapping_flowback_t
78 {
79   float hist[HISTN];
80   // n-means (max 5?) with mean/variance
81   float2 mean[MAXN];
82   float2 var[MAXN];
83   float weight[MAXN];
84   // number of gaussians used.
85   int n; // $MIN: 1 $MAX: 5 $DEFAULT: 1 $DESCRIPTION: "number of clusters"
86 } dt_iop_colormapping_flowback_t;
87 
88 typedef struct dt_iop_colormapping_params_t
89 {
90   dt_iop_colormapping_flags_t flag; // $DEFAULT: NEUTRAL
91   // number of gaussians used.
92   int n; // $MIN: 1 $MAX: 5 $DEFAULT: 3 $DESCRIPTION: "number of clusters"
93 
94   // relative importance of color dominance vs. color proximity
95   float dominance; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "color dominance"
96 
97   // level of histogram equalization
98   float equalization; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "histogram equalization"
99 
100   // hist matching table for source image
101   float source_ihist[HISTN];
102   // n-means (max 5) with mean/variance for source image
103   float2 source_mean[MAXN];
104   float2 source_var[MAXN];
105   float source_weight[MAXN];
106 
107   // hist matching table for destination image
108   int target_hist[HISTN];
109   // n-means (max 5) with mean/variance for source image
110   float2 target_mean[MAXN];
111   float2 target_var[MAXN];
112   float target_weight[MAXN];
113 } dt_iop_colormapping_params_t;
114 
115 /** and pixelpipe data is just the same */
116 typedef struct dt_iop_colormapping_params_t dt_iop_colormapping_data_t;
117 
118 
119 typedef struct dt_iop_colormapping_gui_data_t
120 {
121   int flag;
122   float *buffer;
123   int width;
124   int height;
125   int ch;
126   int flowback_set;
127   dt_iop_colormapping_flowback_t flowback;
128   GtkWidget *acquire_source_button;
129   GtkWidget *acquire_target_button;
130   GtkWidget *source_area;
131   GtkWidget *target_area;
132   GtkWidget *clusters;
133   GtkWidget *dominance;
134   GtkWidget *equalization;
135   cmsHTRANSFORM xform;
136 } dt_iop_colormapping_gui_data_t;
137 
138 typedef struct dt_iop_colormapping_global_data_t
139 {
140   int kernel_histogram;
141   int kernel_mapping;
142 } dt_iop_colormapping_global_data_t;
143 
144 
name()145 const char *name()
146 {
147   return _("color mapping");
148 }
149 
description(struct dt_iop_module_t * self)150 const char *description(struct dt_iop_module_t *self)
151 {
152   return dt_iop_set_description(self, _("transfer a color palette and tonal repartition from one image to another"),
153                                       _("creative"),
154                                       _("linear or non-linear, Lab, display-referred"),
155                                       _("non-linear, Lab"),
156                                       _("non-linear, Lab, display-referred"));
157 }
158 
default_group()159 int default_group()
160 {
161   return IOP_GROUP_EFFECT | IOP_GROUP_EFFECTS;
162 }
163 
flags()164 int flags()
165 {
166   return IOP_FLAGS_ONE_INSTANCE | IOP_FLAGS_SUPPORTS_BLENDING;
167 }
168 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)169 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
170 {
171   return iop_cs_Lab;
172 }
173 
capture_histogram(const float * col,const int width,const int height,int * hist)174 static void capture_histogram(const float *col, const int width, const int height, int *hist)
175 {
176   // build separate histogram
177   memset(hist, 0, sizeof(int) * HISTN);
178   for(int k = 0; k < height; k++)
179     for(int i = 0; i < width; i++)
180     {
181       const int bin = CLAMP(HISTN * col[4 * (k * width + i) + 0] / 100.0, 0, HISTN - 1);
182       hist[bin]++;
183     }
184 
185   // accumulated start distribution of G1 G2
186   for(int k = 1; k < HISTN; k++) hist[k] += hist[k - 1];
187   for(int k = 0; k < HISTN; k++)
188     hist[k] = (int)CLAMP(hist[k] * (HISTN / (float)hist[HISTN - 1]), 0, HISTN - 1);
189   // for(int i=0;i<100;i++) printf("#[%d] %d \n", (int)CLAMP(HISTN*i/100.0, 0, HISTN-1),
190   // hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]);
191 }
192 
invert_histogram(const int * hist,float * inv_hist)193 static void invert_histogram(const int *hist, float *inv_hist)
194 {
195 // invert non-normalised accumulated hist
196 #if 0
197   int last = 0;
198   for(int i=0; i<HISTN; i++) for(int k=last; k<HISTN; k++)
199       if(hist[k] >= i)
200       {
201         last = k;
202         inv_hist[i] = 100.0*k/(float)HISTN;
203         break;
204       }
205 #else
206   int last = 31;
207   for(int i = 0; i <= last; i++) inv_hist[i] = 100.0f * i / (float)HISTN;
208   for(int i = last + 1; i < HISTN; i++)
209     for(int k = last; k < HISTN; k++)
210       if(hist[k] >= i)
211       {
212         last = k;
213         inv_hist[i] = 100.0f * k / (float)HISTN;
214         break;
215       }
216 #endif
217 
218   // printf("inv histogram debug:\n");
219   // for(int i=0;i<100;i++) printf("%d => %f\n", i, inv_hist[hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]]);
220   // for(int i=0;i<100;i++) printf("[%d] %f => %f\n", (int)CLAMP(HISTN*i/100.0, 0, HISTN-1),
221   // hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]/(float)HISTN, inv_hist[(int)CLAMP(HISTN*i/100.0, 0,
222   // HISTN-1)]);
223 }
224 
get_cluster_mapping(const int n,float2 * mi,const float * wi,float2 * mo,const float * wo,const float dominance,int * mapio)225 static void get_cluster_mapping(const int n, float2 *mi, const float *wi, float2 *mo, const float *wo,
226                                 const float dominance, int *mapio)
227 {
228   const float weightscale = 10000.0f;
229 
230   for(int ki = 0; ki < n; ki++)
231   {
232     // for each input cluster
233     float mdist = FLT_MAX;
234     for(int ko = 0; ko < n; ko++)
235     {
236       // find the best target cluster (the same could be used more than once)
237       const float colordist = (mo[ko][0] - mi[ki][0]) * (mo[ko][0] - mi[ki][0])
238                               + (mo[ko][1] - mi[ki][1]) * (mo[ko][1] - mi[ki][1]);
239       const float weightdist = weightscale * (wo[ko] - wi[ki]) * (wo[ko] - wi[ki]);
240       const float dist = colordist * (1.0f - dominance) + weightdist * dominance;
241       if(dist < mdist)
242       {
243         // printf("[%d] => [%d] dominance: %f, colordist: %f, weightdist: %f, dist: %f\n", ki, ko, dominance,
244         // colordist, weightdist, dist);
245         mdist = dist;
246         mapio[ki] = ko;
247       }
248     }
249   }
250 
251   // printf("cluster mapping:\n");
252   // for(int i=0;i<n;i++) printf("[%d] => [%d]\n", i, mapio[i]);
253 }
254 
255 
256 // inverse distant weighting according to D. Shepard's method; with power parameter 2.0
get_clusters(const float * col,const int n,float2 * mean,float * weight)257 static void get_clusters(const float *col, const int n, float2 *mean, float *weight)
258 {
259   float mdist = FLT_MAX;
260   for(int k = 0; k < n; k++)
261   {
262     const float dist2 = (col[1] - mean[k][0]) * (col[1] - mean[k][0])
263                         + (col[2] - mean[k][1]) * (col[2] - mean[k][1]); // dist^2
264     weight[k] = dist2 > 1.0e-6f ? 1.0f / dist2 : -1.0f;                  // direct hits marked as -1
265     if(dist2 < mdist) mdist = dist2;
266   }
267   if(mdist < 1.0e-6f)
268     for(int k = 0; k < n; k++)
269       weight[k] = weight[k] < 0.0f ? 1.0f : 0.0f; // correction in case of direct hits
270   float sum = 0.0f;
271   for(int k = 0; k < n; k++) sum += weight[k];
272   if(sum > 0.0f)
273     for(int k = 0; k < n; k++) weight[k] /= sum;
274 }
275 
276 
get_cluster(const float * col,const int n,float2 * mean)277 static int get_cluster(const float *col, const int n, float2 *mean)
278 {
279   float mdist = FLT_MAX;
280   int cluster = 0;
281   for(int k = 0; k < n; k++)
282   {
283     const float dist = (col[1] - mean[k][0]) * (col[1] - mean[k][0])
284                        + (col[2] - mean[k][1]) * (col[2] - mean[k][1]);
285     if(dist < mdist)
286     {
287       mdist = dist;
288       cluster = k;
289     }
290   }
291   return cluster;
292 }
293 
kmeans(const float * col,const int width,const int height,const int n,float2 * mean_out,float2 * var_out,float * weight_out)294 static void kmeans(const float *col, const int width, const int height, const int n, float2 *mean_out,
295                    float2 *var_out, float *weight_out)
296 {
297   const int nit = 40;                       // number of iterations
298   const int samples = width * height * 0.2; // samples: only a fraction of the buffer.
299 
300   float2 *const mean = malloc(sizeof(float2) * n);
301   float2 *const var = malloc(sizeof(float2) * n);
302   int *const cnt = malloc(sizeof(int) * n);
303   int count;
304 
305   float a_min = FLT_MAX, b_min = FLT_MAX, a_max = FLT_MIN, b_max = FLT_MIN;
306 
307   for(int s = 0; s < samples; s++)
308   {
309     const int j = CLAMP(dt_points_get() * height, 0, height - 1);
310     const int i = CLAMP(dt_points_get() * width, 0, width - 1);
311 
312     const float a = col[4 * (width * j + i) + 1];
313     const float b = col[4 * (width * j + i) + 2];
314 
315     a_min = fminf(a, a_min);
316     a_max = fmaxf(a, a_max);
317     b_min = fminf(b, b_min);
318     b_max = fmaxf(b, b_max);
319   }
320 
321   // init n clusters for a, b channels at random
322   for(int k = 0; k < n; k++)
323   {
324     mean_out[k][0] = 0.9f * (a_min + (a_max - a_min) * dt_points_get());
325     mean_out[k][1] = 0.9f * (b_min + (b_max - b_min) * dt_points_get());
326     var_out[k][0] = var_out[k][1] = weight_out[k] = 0.0f;
327     mean[k][0] = mean[k][1] = var[k][0] = var[k][1] = 0.0f;
328   }
329   for(int it = 0; it < nit; it++)
330   {
331     for(int k = 0; k < n; k++) cnt[k] = 0;
332 // randomly sample col positions inside roi
333 #ifdef _OPENMP
334 #pragma omp parallel for default(none) \
335     dt_omp_firstprivate(cnt, height, mean, n, samples, var, width) \
336     shared(col, mean_out) \
337     schedule(static)
338 #endif
339     for(int s = 0; s < samples; s++)
340     {
341       const int j = CLAMP(dt_points_get() * height, 0, height - 1);
342       const int i = CLAMP(dt_points_get() * width, 0, width - 1);
343       // for each sample: determine cluster, update new mean, update var
344       for(int k = 0; k < n; k++)
345       {
346         const float L = col[4 * (width * j + i)];
347         const float Lab[3] = { L, col[4 * (width * j + i) + 1], col[4 * (width * j + i) + 2] };
348         // determine dist to mean_out
349         const int c = get_cluster(Lab, n, mean_out);
350 #ifdef _OPENMP
351 #pragma omp atomic
352 #endif
353         cnt[c]++;
354 // update mean, var
355 #ifdef _OPENMP
356 #pragma omp atomic
357 #endif
358         var[c][0] += Lab[1] * Lab[1];
359 #ifdef _OPENMP
360 #pragma omp atomic
361 #endif
362         var[c][1] += Lab[2] * Lab[2];
363 #ifdef _OPENMP
364 #pragma omp atomic
365 #endif
366         mean[c][0] += Lab[1];
367 #ifdef _OPENMP
368 #pragma omp atomic
369 #endif
370         mean[c][1] += Lab[2];
371       }
372     }
373     // swap old/new means
374     for(int k = 0; k < n; k++)
375     {
376       if(cnt[k] == 0) continue;
377       mean_out[k][0] = mean[k][0] / cnt[k];
378       mean_out[k][1] = mean[k][1] / cnt[k];
379       var_out[k][0] = var[k][0] / cnt[k] - mean_out[k][0] * mean_out[k][0];
380       var_out[k][1] = var[k][1] / cnt[k] - mean_out[k][1] * mean_out[k][1];
381       mean[k][0] = mean[k][1] = var[k][0] = var[k][1] = 0.0f;
382     }
383 
384     // determine weight of clusters
385     count = 0;
386     for(int k = 0; k < n; k++) count += cnt[k];
387     for(int k = 0; k < n; k++) weight_out[k] = (count > 0) ? (float)cnt[k] / count : 0.0f;
388 
389     // printf("it %d  %d means:\n", it, n);
390     // for(int k=0;k<n;k++) printf("mean %f %f -- var %f %f -- weight %f\n", mean_out[k][0], mean_out[k][1],
391     // var_out[k][0], var_out[k][1], weight_out[k]);
392   }
393 
394   free(cnt);
395   free(var);
396   free(mean);
397 
398   for(int k = 0; k < n; k++)
399   {
400     // "eliminate" clusters with a variance of zero
401     if(var_out[k][0] == 0.0f || var_out[k][1] == 0.0f)
402       mean_out[k][0] = mean_out[k][1] = var_out[k][0] = var_out[k][1] = weight_out[k] = 0;
403 
404     // we actually want the std deviation.
405     var_out[k][0] = sqrtf(var_out[k][0]);
406     var_out[k][1] = sqrtf(var_out[k][1]);
407   }
408 
409   // simple bubblesort of clusters in order of ascending weight: just a convenience for the user to keep
410   // cluster display a bit more consistent in GUI
411   for(int i = 0; i < n - 1; i++)
412   {
413     for(int j = 0; j < n - 1 - i; j++)
414     {
415       if(weight_out[j] > weight_out[j + 1])
416       {
417         float2 temp_mean = { mean_out[j + 1][0], mean_out[j + 1][1] };
418         float2 temp_var = { var_out[j + 1][0], var_out[j + 1][1] };
419         float temp_weight = weight_out[j + 1];
420 
421         mean_out[j + 1][0] = mean_out[j][0];
422         mean_out[j + 1][1] = mean_out[j][1];
423         var_out[j + 1][0] = var_out[j][0];
424         var_out[j + 1][1] = var_out[j][1];
425         weight_out[j + 1] = weight_out[j];
426 
427         mean_out[j][0] = temp_mean[0];
428         mean_out[j][1] = temp_mean[1];
429         var_out[j][0] = temp_var[0];
430         var_out[j][1] = temp_var[1];
431         weight_out[j] = temp_weight;
432       }
433     }
434   }
435 }
436 
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)437 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
438              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
439 {
440   dt_iop_colormapping_data_t *const restrict data = (dt_iop_colormapping_data_t *)piece->data;
441   dt_iop_colormapping_gui_data_t *const restrict g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
442   float *const restrict in = (float *)ivoid;
443   float *const restrict out = (float *)ovoid;
444 
445   const int width = roi_in->width;
446   const int height = roi_in->height;
447   if (!dt_iop_have_required_input_format(4 /*we need full-color pixels*/, self, piece->colors,
448                                          in, out, roi_in, roi_out))
449     return; // image has been copied through to output and module's trouble flag has been updated
450 
451   const float scale = piece->iscale / roi_in->scale;
452   const float sigma_s = 50.0f / scale;
453   const float sigma_r = 8.0f; // does not depend on scale
454 
455   // save a copy of preview input buffer so we can get histogram and color statistics out of it
456   if(self->dev->gui_attached && g && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW) == DT_DEV_PIXELPIPE_PREVIEW && (data->flag & ACQUIRE))
457   {
458     dt_iop_gui_enter_critical_section(self);
459     if(g->buffer) dt_free_align(g->buffer);
460 
461     g->buffer = dt_iop_image_alloc(width, height, 4);
462     g->width = width;
463     g->height = height;
464     g->ch = 4;
465 
466     if(g->buffer) dt_iop_image_copy_by_size(g->buffer, in, width, height, 4);
467 
468     dt_iop_gui_leave_critical_section(self);
469   }
470 
471   // process image if all mapping information is present in the parameter set
472   if(data->flag & HAS_TARGET && data->flag & HAS_SOURCE)
473   {
474     // for all pixels: find input cluster, transfer to mapped target cluster and apply histogram
475 
476     const float dominance = data->dominance / 100.0f;
477     const float equalization = data->equalization / 100.0f;
478 
479     // get mapping from input clusters to target clusters
480     int *const mapio = malloc(sizeof(int) * data->n);
481 
482     get_cluster_mapping(data->n, data->target_mean, data->target_weight, data->source_mean,
483                         data->source_weight, dominance, mapio);
484 
485     float2 *const var_ratio = malloc(sizeof(float2) * data->n);
486 
487     for(int i = 0; i < data->n; i++)
488     {
489       var_ratio[i][0]
490           = (data->target_var[i][0] > 0.0f) ? data->source_var[mapio[i]][0] / data->target_var[i][0] : 0.0f;
491       var_ratio[i][1]
492           = (data->target_var[i][1] > 0.0f) ? data->source_var[mapio[i]][1] / data->target_var[i][1] : 0.0f;
493     }
494 
495     const size_t npixels = (size_t)height * width;
496 // first get delta L of equalized L minus original image L, scaled to fit into [0 .. 100]
497 #ifdef _OPENMP
498 #pragma omp parallel for default(none) \
499     dt_omp_firstprivate(npixels) \
500     dt_omp_sharedconst(in, out, data, equalization)        \
501     schedule(static)
502 #endif
503     for(size_t k = 0; k < npixels * 4; k += 4)
504     {
505       const float L = in[k];
506       out[k] = 0.5f * ((L * (1.0f - equalization)
507                         + data->source_ihist[data->target_hist[(int)CLAMP(
508                               HISTN * L / 100.0f, 0.0f, (float)HISTN - 1.0f)]] * equalization) - L) + 50.0f;
509       out[k] = CLAMP(out[k], 0.0f, 100.0f);
510     }
511 
512     if(equalization > 0.001f)
513     {
514       // bilateral blur of delta L to avoid artifacts caused by limited histogram resolution
515       dt_bilateral_t *b = dt_bilateral_init(width, height, sigma_s, sigma_r);
516       if(!b)
517       {
518         free(var_ratio);
519         free(mapio);
520         return;
521       }
522       dt_bilateral_splat(b, out);
523       dt_bilateral_blur(b);
524       dt_bilateral_slice(b, out, out, -1.0f);
525       dt_bilateral_free(b);
526     }
527 
528     size_t allocsize;
529     float *const weight_buf = dt_alloc_perthread(data->n, sizeof(float), &allocsize);
530 
531 #ifdef _OPENMP
532 #pragma omp parallel default(none) \
533     dt_omp_firstprivate(npixels, mapio, var_ratio, weight_buf, allocsize) \
534     dt_omp_sharedconst(data, in, out, equalization)
535 #endif
536     {
537       // get a thread-private scratch buffer; do this before the actual loop so we don't have to look it up for
538       // every single pixel
539       float *const restrict weight = dt_get_perthread(weight_buf,allocsize);
540 #ifdef _OPENMP
541 #pragma omp for schedule(static)
542 #endif
543       for(size_t j = 0; j < 4*npixels; j += 4)
544       {
545         const float L = in[j];
546         const float Lab[3] = { L, in[j + 1], in[j + 2] };
547 
548         // transfer back scaled and blurred delta L to output L
549         out[j] = 2.0f * (out[j] - 50.0f) + L;
550         out[j] = CLAMP(out[j], 0.0f, 100.0f);
551 
552         get_clusters(in + j, data->n, data->target_mean, weight);
553         // zero the 'a' and 'b' channels
554         out[j + 1] = out[j + 2] = 0.0f;
555         // then accumulate a weighted average for a and b
556         for(int c = 0; c < data->n; c++)
557         {
558           out[j + 1] += weight[c] * ((Lab[1] - data->target_mean[c][0]) * var_ratio[c][0]
559                                      + data->source_mean[mapio[c]][0]);
560           out[j + 2] += weight[c] * ((Lab[2] - data->target_mean[c][1]) * var_ratio[c][1]
561                                      + data->source_mean[mapio[c]][1]);
562         }
563         // pass through the alpha channel
564         out[j + 3] = in[j + 3];
565       }
566     }
567 
568     dt_free_align(weight_buf);
569     free(var_ratio);
570     free(mapio);
571   }
572   // incomplete parameter set -> do nothing
573   else
574   {
575     dt_iop_image_copy_by_size(out, in, width, height, 4);
576   }
577 }
578 
579 
580 #ifdef HAVE_OPENCL
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)581 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
582                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
583 {
584   dt_iop_colormapping_data_t *data = (dt_iop_colormapping_data_t *)piece->data;
585   dt_iop_colormapping_global_data_t *gd = (dt_iop_colormapping_global_data_t *)self->global_data;
586   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
587 
588   cl_int err = -999;
589   const int devid = piece->pipe->devid;
590 
591   const int width = roi_in->width;
592   const int height = roi_in->height;
593   const int ch = piece->colors;
594 
595   const float scale = piece->iscale / roi_in->scale;
596   const float sigma_s = 50.0f / scale;
597   const float sigma_r = 8.0f; // does not depend on scale
598 
599   float dominance = data->dominance / 100.0f;
600   float equalization = data->equalization / 100.0f;
601 
602   dt_bilateral_cl_t *b = NULL;
603   cl_mem dev_tmp = NULL;
604   cl_mem dev_target_hist = NULL;
605   cl_mem dev_source_ihist = NULL;
606   cl_mem dev_target_mean = NULL;
607   cl_mem dev_source_mean = NULL;
608   cl_mem dev_var_ratio = NULL;
609   cl_mem dev_mapio = NULL;
610 
611 
612   // save a copy of preview input buffer so we can get histogram and color statistics out of it
613   if(self->dev->gui_attached && g && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW) == DT_DEV_PIXELPIPE_PREVIEW && (data->flag & ACQUIRE))
614   {
615     dt_iop_gui_enter_critical_section(self);
616     dt_free_align(g->buffer);
617 
618     g->buffer = dt_iop_image_alloc(width, height, ch);
619     g->width = width;
620     g->height = height;
621     g->ch = ch;
622 
623     if(g->buffer)
624       err = dt_opencl_copy_device_to_host(devid, g->buffer, dev_in, width, height, ch * sizeof(float));
625 
626     dt_iop_gui_leave_critical_section(self);
627 
628     if(err != CL_SUCCESS) goto error;
629   }
630 
631 
632   // process image if all mapping information is present in the parameter set
633   if(data->flag & HAS_TARGET && data->flag & HAS_SOURCE)
634   {
635     // get mapping from input clusters to target clusters
636     int mapio[MAXN];
637     get_cluster_mapping(data->n, data->target_mean, data->target_weight, data->source_mean,
638                         data->source_weight, dominance, mapio);
639 
640     float2 var_ratio[MAXN];
641     for(int i = 0; i < data->n; i++)
642     {
643       var_ratio[i][0]
644           = (data->target_var[i][0] > 0.0f) ? data->source_var[mapio[i]][0] / data->target_var[i][0] : 0.0f;
645       var_ratio[i][1]
646           = (data->target_var[i][1] > 0.0f) ? data->source_var[mapio[i]][1] / data->target_var[i][1] : 0.0f;
647     }
648 
649     dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
650     if(dev_tmp == NULL) goto error;
651 
652     dev_target_hist = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * HISTN, data->target_hist);
653     if(dev_target_hist == NULL) goto error;
654 
655     dev_source_ihist
656         = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * HISTN, data->source_ihist);
657     if(dev_source_ihist == NULL) goto error;
658 
659     dev_target_mean
660         = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * MAXN * 2, data->target_mean);
661     if(dev_target_mean == NULL) goto error;
662 
663     dev_source_mean
664         = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * MAXN * 2, data->source_mean);
665     if(dev_source_mean == NULL) goto error;
666 
667     dev_var_ratio = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * MAXN * 2, var_ratio);
668     if(dev_var_ratio == NULL) goto error;
669 
670     dev_mapio = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * MAXN, mapio);
671     if(dev_mapio == NULL) goto error;
672 
673     size_t sizes[3] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
674 
675     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 0, sizeof(cl_mem), (void *)&dev_in);
676     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 1, sizeof(cl_mem), (void *)&dev_out);
677     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 2, sizeof(int), (void *)&width);
678     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 3, sizeof(int), (void *)&height);
679     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 4, sizeof(float), (void *)&equalization);
680     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 5, sizeof(cl_mem), (void *)&dev_target_hist);
681     dt_opencl_set_kernel_arg(devid, gd->kernel_histogram, 6, sizeof(cl_mem), (void *)&dev_source_ihist);
682     err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_histogram, sizes);
683     if(err != CL_SUCCESS) goto error;
684 
685     if(equalization > 0.001f)
686     {
687       b = dt_bilateral_init_cl(devid, width, height, sigma_s, sigma_r);
688       if(!b) goto error;
689       err = dt_bilateral_splat_cl(b, dev_out);
690       if(err != CL_SUCCESS) goto error;
691       err = dt_bilateral_blur_cl(b);
692       if(err != CL_SUCCESS) goto error;
693       err = dt_bilateral_slice_cl(b, dev_out, dev_tmp, -1.0f);
694       if(err != CL_SUCCESS) goto error;
695       dt_bilateral_free_cl(b);
696       b = NULL; // make sure we don't clean it up twice
697     }
698     else
699     {
700       size_t origin[] = { 0, 0, 0 };
701       size_t region[] = { width, height, 1 };
702       err = dt_opencl_enqueue_copy_image(devid, dev_out, dev_tmp, origin, origin, region);
703       if(err != CL_SUCCESS) goto error;
704     }
705 
706     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 0, sizeof(cl_mem), (void *)&dev_in);
707     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 1, sizeof(cl_mem), (void *)&dev_tmp);
708     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 2, sizeof(cl_mem), (void *)&dev_out);
709     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 3, sizeof(int), (void *)&width);
710     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 4, sizeof(int), (void *)&height);
711     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 5, sizeof(int), (void *)&data->n);
712     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 6, sizeof(cl_mem), (void *)&dev_target_mean);
713     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 7, sizeof(cl_mem), (void *)&dev_source_mean);
714     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 8, sizeof(cl_mem), (void *)&dev_var_ratio);
715     dt_opencl_set_kernel_arg(devid, gd->kernel_mapping, 9, sizeof(cl_mem), (void *)&dev_mapio);
716     err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_mapping, sizes);
717     if(err != CL_SUCCESS) goto error;
718 
719     dt_opencl_release_mem_object(dev_tmp);
720     dt_opencl_release_mem_object(dev_target_hist);
721     dt_opencl_release_mem_object(dev_source_ihist);
722     dt_opencl_release_mem_object(dev_target_mean);
723     dt_opencl_release_mem_object(dev_source_mean);
724     dt_opencl_release_mem_object(dev_var_ratio);
725     dt_opencl_release_mem_object(dev_mapio);
726     return TRUE;
727   }
728   else
729   {
730     size_t origin[] = { 0, 0, 0 };
731     size_t region[] = { width, height, 1 };
732     err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, origin, origin, region);
733     if(err != CL_SUCCESS) goto error;
734     return TRUE;
735   }
736 
737 error:
738   if(b != NULL) dt_bilateral_free_cl(b);
739   dt_opencl_release_mem_object(dev_tmp);
740   dt_opencl_release_mem_object(dev_target_hist);
741   dt_opencl_release_mem_object(dev_source_ihist);
742   dt_opencl_release_mem_object(dev_target_mean);
743   dt_opencl_release_mem_object(dev_source_mean);
744   dt_opencl_release_mem_object(dev_var_ratio);
745   dt_opencl_release_mem_object(dev_mapio);
746   dt_print(DT_DEBUG_OPENCL, "[opencl_colormapping] couldn't enqueue kernel! %d\n", err);
747   return FALSE;
748 }
749 #endif
750 
751 
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)752 void tiling_callback(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
753                      const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out,
754                      struct dt_develop_tiling_t *tiling)
755 {
756   const float scale = piece->iscale / roi_in->scale;
757   const float sigma_s = 50.0f / scale;
758   const float sigma_r = 8.0f; // does not depend on scale
759 
760   const int width = roi_in->width;
761   const int height = roi_in->height;
762   const int channels = piece->colors;
763 
764   const size_t basebuffer = sizeof(float) * channels * width * height;
765 
766   tiling->factor = 3.0f + (float)dt_bilateral_memory_use(width, height, sigma_s, sigma_r) / basebuffer;
767   tiling->maxbuf
768       = fmaxf(1.0f, (float)dt_bilateral_singlebuffer_size(width, height, sigma_s, sigma_r) / basebuffer);
769   tiling->overhead = 0;
770   tiling->overlap = ceilf(4 * sigma_s);
771   tiling->xalign = 1;
772   tiling->yalign = 1;
773 }
774 
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)775 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
776                    dt_dev_pixelpipe_iop_t *piece)
777 {
778   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)p1;
779   dt_iop_colormapping_data_t *d = (dt_iop_colormapping_data_t *)piece->data;
780 
781   memcpy(d, p, sizeof(dt_iop_colormapping_params_t));
782 #ifdef HAVE_OPENCL
783   if(d->equalization > 0.1f)
784     piece->process_cl_ready = (piece->process_cl_ready && !(darktable.opencl->avoid_atomics));
785 #endif
786 }
787 
gui_changed(dt_iop_module_t * self,GtkWidget * w,void * previous)788 void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
789 {
790   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)self->params;
791   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
792 
793   if(w == g->clusters)
794   {
795     // only reset source/target when changing number of clusters
796     memset(p->source_ihist, 0, sizeof(float) * HISTN);
797     memset(p->source_mean, 0, sizeof(float) * MAXN * 2);
798     memset(p->source_var, 0, sizeof(float) * MAXN * 2);
799     memset(p->source_weight, 0, sizeof(float) * MAXN);
800     memset(p->target_hist, 0, sizeof(int) * HISTN);
801     memset(p->target_mean, 0, sizeof(float) * MAXN * 2);
802     memset(p->target_var, 0, sizeof(float) * MAXN * 2);
803     memset(p->target_weight, 0, sizeof(float) * MAXN);
804     p->flag = NEUTRAL;
805     dt_control_queue_redraw_widget(g->source_area);
806     dt_control_queue_redraw_widget(g->target_area);
807   }
808 }
809 
acquire_source_button_pressed(GtkButton * button,dt_iop_module_t * self)810 static void acquire_source_button_pressed(GtkButton *button, dt_iop_module_t *self)
811 {
812   if(darktable.gui->reset) return;
813   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)self->params;
814   p->flag |= ACQUIRE;
815   p->flag |= GET_SOURCE;
816   p->flag &= ~HAS_SOURCE;
817   dt_iop_request_focus(self);
818   dt_dev_add_history_item(darktable.develop, self, TRUE);
819 }
820 
acquire_target_button_pressed(GtkButton * button,dt_iop_module_t * self)821 static void acquire_target_button_pressed(GtkButton *button, dt_iop_module_t *self)
822 {
823   if(darktable.gui->reset) return;
824   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)self->params;
825   p->flag |= ACQUIRE;
826   p->flag |= GET_TARGET;
827   p->flag &= ~HAS_TARGET;
828   dt_iop_request_focus(self);
829   dt_dev_add_history_item(darktable.develop, self, TRUE);
830 }
831 
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)832 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
833 {
834   piece->data = malloc(sizeof(dt_iop_colormapping_data_t));
835 }
836 
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)837 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
838 {
839   free(piece->data);
840   piece->data = NULL;
841 }
842 
gui_update(struct dt_iop_module_t * self)843 void gui_update(struct dt_iop_module_t *self)
844 {
845   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)self->params;
846   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
847   dt_bauhaus_slider_set(g->clusters, p->n);
848   dt_bauhaus_slider_set(g->dominance, p->dominance);
849   dt_bauhaus_slider_set(g->equalization, p->equalization);
850   dt_control_queue_redraw_widget(self->widget);
851 }
852 
init_global(dt_iop_module_so_t * module)853 void init_global(dt_iop_module_so_t *module)
854 {
855   const int program = 8; // extended.cl, from programs.conf
856   dt_iop_colormapping_global_data_t *gd
857       = (dt_iop_colormapping_global_data_t *)malloc(sizeof(dt_iop_colormapping_global_data_t));
858   module->data = gd;
859   gd->kernel_histogram = dt_opencl_create_kernel(program, "colormapping_histogram");
860   gd->kernel_mapping = dt_opencl_create_kernel(program, "colormapping_mapping");
861 }
862 
cleanup_global(dt_iop_module_so_t * module)863 void cleanup_global(dt_iop_module_so_t *module)
864 {
865   dt_iop_colormapping_global_data_t *gd = (dt_iop_colormapping_global_data_t *)module->data;
866   dt_opencl_free_kernel(gd->kernel_histogram);
867   dt_opencl_free_kernel(gd->kernel_mapping);
868   free(module->data);
869   module->data = NULL;
870 }
871 
reload_defaults(dt_iop_module_t * module)872 void reload_defaults(dt_iop_module_t *module)
873 {
874   dt_iop_colormapping_params_t *d = module->default_params;
875 
876   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)module->gui_data;
877   if(module->dev->gui_attached && g && g->flowback_set)
878   {
879     memcpy(d->source_ihist, g->flowback.hist, sizeof(float) * HISTN);
880     memcpy(d->source_mean, g->flowback.mean, sizeof(float) * MAXN * 2);
881     memcpy(d->source_var, g->flowback.var, sizeof(float) * MAXN * 2);
882     memcpy(d->source_weight, g->flowback.weight, sizeof(float) * MAXN);
883     d->n = g->flowback.n;
884     d->flag = HAS_SOURCE;
885   }
886 }
887 
888 
cluster_preview_draw(GtkWidget * widget,cairo_t * crf,dt_iop_module_t * self)889 static gboolean cluster_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
890 {
891   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)self->params;
892   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
893 
894   float2 *mean;
895   float2 *var;
896 
897   if(widget == g->source_area)
898   {
899     mean = p->source_mean;
900     var = p->source_var;
901   }
902   else
903   {
904     mean = p->target_mean;
905     var = p->target_var;
906   }
907 
908 
909   GtkAllocation allocation;
910   gtk_widget_get_allocation(widget, &allocation);
911   const int inset = 5;
912   int width = allocation.width, height = allocation.height;
913   cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
914   cairo_t *cr = cairo_create(cst);
915   cairo_set_source_rgb(cr, .2, .2, .2);
916   cairo_paint(cr);
917 
918   cairo_translate(cr, inset, inset);
919   width -= 2 * inset;
920   height -= 2 * inset;
921 
922 
923   const float sep = DT_PIXEL_APPLY_DPI(2.0);
924   const float qwd = (width - (p->n - 1) * sep) / (float)p->n;
925   for(int cl = 0; cl < p->n; cl++)
926   {
927     // draw cluster
928     for(int j = -1; j <= 1; j++)
929       for(int i = -1; i <= 1; i++)
930       {
931         // draw 9x9 grid showing mean and variance of this cluster.
932         double rgb[3] = { 0.5, 0.5, 0.5 };
933         cmsCIELab Lab;
934         Lab.L = 53.390011;
935         Lab.a = (mean[cl][0] + i * var[cl][0]);
936         Lab.b = (mean[cl][1] + j * var[cl][1]);
937         cmsDoTransform(g->xform, &Lab, rgb, 1);
938         cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
939         cairo_rectangle(cr, qwd * (i + 1) / 3.0, height * (j + 1) / 3.0, qwd / 3.0 - DT_PIXEL_APPLY_DPI(.5),
940                         height / 3.0 - DT_PIXEL_APPLY_DPI(.5));
941         cairo_fill(cr);
942       }
943     cairo_translate(cr, qwd + sep, 0);
944   }
945 
946   cairo_destroy(cr);
947   cairo_set_source_surface(crf, cst, 0, 0);
948   cairo_paint(crf);
949   cairo_surface_destroy(cst);
950   return TRUE;
951 }
952 
953 
process_clusters(gpointer instance,gpointer user_data)954 static void process_clusters(gpointer instance, gpointer user_data)
955 {
956   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
957   dt_iop_colormapping_params_t *p = (dt_iop_colormapping_params_t *)self->params;
958   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
959   int new_source_clusters = 0;
960 
961   if(!g || !g->buffer) return;
962   if(!(p->flag & ACQUIRE)) return;
963 
964   ++darktable.gui->reset;
965 
966   dt_iop_gui_enter_critical_section(self);
967   const int width = g->width;
968   const int height = g->height;
969   const int ch = g->ch;
970   float *const restrict buffer = dt_iop_image_alloc(width, height, ch);
971   if(!buffer)
972   {
973     dt_iop_gui_leave_critical_section(self);
974     return;
975   }
976   dt_iop_image_copy_by_size(buffer, g->buffer, width, height, ch);
977   dt_iop_gui_leave_critical_section(self);
978 
979   if(p->flag & GET_SOURCE)
980   {
981     int hist[HISTN];
982 
983     // get histogram of L
984     capture_histogram(buffer, width, height, hist);
985 
986     // invert histogram
987     invert_histogram(hist, p->source_ihist);
988 
989     // get n color clusters
990     kmeans(buffer, width, height, p->n, p->source_mean, p->source_var, p->source_weight);
991 
992     p->flag |= HAS_SOURCE;
993     new_source_clusters = 1;
994 
995     dt_control_queue_redraw_widget(g->source_area);
996   }
997   else if(p->flag & GET_TARGET)
998   {
999     // get histogram of L
1000     capture_histogram(buffer, width, height, p->target_hist);
1001 
1002     // get n color clusters
1003     kmeans(buffer, width, height, p->n, p->target_mean, p->target_var, p->target_weight);
1004 
1005     p->flag |= HAS_TARGET;
1006 
1007     dt_control_queue_redraw_widget(g->target_area);
1008   }
1009 
1010   dt_free_align(buffer);
1011 
1012   if(new_source_clusters)
1013   {
1014     memcpy(g->flowback.hist, p->source_ihist, sizeof(float) * HISTN);
1015     memcpy(g->flowback.mean, p->source_mean, sizeof(float) * MAXN * 2);
1016     memcpy(g->flowback.var, p->source_var, sizeof(float) * MAXN * 2);
1017     memcpy(g->flowback.weight, p->source_weight, sizeof(float) * MAXN);
1018     g->flowback.n = p->n;
1019     g->flowback_set = 1;
1020     FILE *f = g_fopen("/tmp/dt_colormapping_loaded", "wb");
1021     if(f)
1022     {
1023       if(fwrite(&g->flowback, sizeof(g->flowback), 1, f) < 1)
1024         fprintf(stderr, "[colormapping] could not write flowback file /tmp/dt_colormapping_loaded\n");
1025       fclose(f);
1026     }
1027   }
1028 
1029   p->flag &= ~(GET_TARGET | GET_SOURCE | ACQUIRE);
1030   --darktable.gui->reset;
1031 
1032   if(p->flag & HAS_SOURCE) dt_dev_add_history_item(darktable.develop, self, TRUE);
1033 
1034   dt_control_queue_redraw();
1035 }
1036 
1037 
gui_init(struct dt_iop_module_t * self)1038 void gui_init(struct dt_iop_module_t *self)
1039 {
1040   dt_iop_colormapping_gui_data_t *g = IOP_GUI_ALLOC(colormapping);
1041 
1042   g->flag = NEUTRAL;
1043   g->flowback_set = 0;
1044   cmsHPROFILE hsRGB = dt_colorspaces_get_profile(DT_COLORSPACE_SRGB, "", DT_PROFILE_DIRECTION_IN)->profile;
1045   cmsHPROFILE hLab = dt_colorspaces_get_profile(DT_COLORSPACE_LAB, "", DT_PROFILE_DIRECTION_ANY)->profile;
1046   g->xform = cmsCreateTransform(hLab, TYPE_Lab_DBL, hsRGB, TYPE_RGB_DBL, INTENT_PERCEPTUAL, 0);
1047   g->buffer = NULL;
1048 
1049   self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE));
1050 
1051   gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_label_new(_("source clusters:")), TRUE, TRUE, 0);
1052 
1053   g->source_area = dtgtk_drawing_area_new_with_aspect_ratio(1.0 / 3.0);
1054   gtk_box_pack_start(GTK_BOX(self->widget), g->source_area, TRUE, TRUE, 0);
1055   g_signal_connect(G_OBJECT(g->source_area), "draw", G_CALLBACK(cluster_preview_draw), self);
1056 
1057   gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_label_new(_("target clusters:")), TRUE, TRUE, 0);
1058 
1059   g->target_area = dtgtk_drawing_area_new_with_aspect_ratio(1.0 / 3.0);
1060   gtk_box_pack_start(GTK_BOX(self->widget), g->target_area, TRUE, TRUE, 0);
1061   g_signal_connect(G_OBJECT(g->target_area), "draw", G_CALLBACK(cluster_preview_draw), self);
1062 
1063   GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1064   gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
1065 
1066   g->acquire_source_button = dt_iop_button_new(self, N_("acquire as source"),
1067                                                G_CALLBACK(acquire_source_button_pressed), FALSE, 0, 0,
1068                                                NULL, 0, box);
1069   gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->acquire_source_button))), PANGO_ELLIPSIZE_START);
1070   gtk_widget_set_tooltip_text(g->acquire_source_button, _("analyze this image as a source image"));
1071 
1072   g->acquire_target_button = dt_iop_button_new(self, N_("acquire as target"),
1073                                                G_CALLBACK(acquire_target_button_pressed), FALSE, 0, 0,
1074                                                NULL, 0, box);
1075   gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->acquire_target_button))), PANGO_ELLIPSIZE_START);
1076   gtk_widget_set_tooltip_text(g->acquire_target_button, _("analyze this image as a target image"));
1077 
1078   g->clusters = dt_bauhaus_slider_from_params(self, "n");
1079   gtk_widget_set_tooltip_text(g->clusters, _("number of clusters to find in image. value change resets all clusters"));
1080 
1081   g->dominance = dt_bauhaus_slider_from_params(self, "dominance");
1082   gtk_widget_set_tooltip_text(g->dominance, _("how clusters are mapped. low values: based on color "
1083                                               "proximity, high values: based on color dominance"));
1084   dt_bauhaus_slider_set_format(g->dominance, "%.02f%%");
1085 
1086   g->equalization = dt_bauhaus_slider_from_params(self, "equalization");
1087   gtk_widget_set_tooltip_text(g->equalization, _("level of histogram equalization"));
1088   dt_bauhaus_slider_set_format(g->equalization, "%.02f%%");
1089 
1090   /* add signal handler for preview pipe finished: process clusters if requested */
1091   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED,
1092                             G_CALLBACK(process_clusters), self);
1093 
1094   FILE *f = g_fopen("/tmp/dt_colormapping_loaded", "rb");
1095   if(f)
1096   {
1097     if(fread(&g->flowback, sizeof(g->flowback), 1, f) > 0) g->flowback_set = 1;
1098     fclose(f);
1099   }
1100 }
1101 
gui_cleanup(struct dt_iop_module_t * self)1102 void gui_cleanup(struct dt_iop_module_t *self)
1103 {
1104   dt_iop_colormapping_gui_data_t *g = (dt_iop_colormapping_gui_data_t *)self->gui_data;
1105 
1106   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(process_clusters), self);
1107 
1108   cmsDeleteTransform(g->xform);
1109   dt_free_align(g->buffer);
1110 
1111   IOP_GUI_FREE;
1112 }
1113 
1114 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1115 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1116 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1117