"Fossies" - the Fresh Open Source Software Archive

Member "darktable-2.6.3/src/iop/colorchecker.c" (20 Oct 2019, 56729 Bytes) of package /linux/misc/darktable-2.6.3.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "colorchecker.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.2_vs_2.6.3.

    1 /*
    2     This file is part of darktable,
    3     copyright (c) 2017 johannes hanika.
    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 #include "bauhaus/bauhaus.h"
   19 #include "common/colorspaces.h"
   20 #include "common/opencl.h"
   21 #include "common/exif.h"
   22 #include "control/control.h"
   23 #include "develop/develop.h"
   24 #include "develop/imageop.h"
   25 #include "develop/imageop_math.h"
   26 #include "develop/tiling.h"
   27 #include "dtgtk/drawingarea.h"
   28 #include "gui/accelerators.h"
   29 #include "gui/gtk.h"
   30 #include "gui/presets.h"
   31 #include "iop/iop_api.h"
   32 #include "common/iop_group.h"
   33 #include "iop/gaussian_elimination.h"
   34 #include "libs/colorpicker.h"
   35 #include <assert.h>
   36 #include <math.h>
   37 #include <stdlib.h>
   38 #include <string.h>
   39 
   40 #include <gtk/gtk.h>
   41 #include <inttypes.h>
   42 
   43 DT_MODULE_INTROSPECTION(2, dt_iop_colorchecker_params_t)
   44 
   45 static const int colorchecker_patches = 24;
   46 static const float colorchecker_Lab[] =
   47 { // from argyll ColorChecker.cie
   48  37.99,   13.56,  14.06, // dark skin
   49  65.71,   18.13,  17.81, // light skin
   50  49.93,   -4.88, -21.93, // blue sky
   51  43.14,  -13.10,  21.91, // foliage
   52  55.11,    8.84, -25.40, // blue flower
   53  70.72,  -33.40, -0.20 , // bluish green
   54  62.66,   36.07,  57.10, // orange
   55  40.02,   10.41, -45.96, // purple red
   56  51.12,   48.24,  16.25, // moderate red
   57  30.33,   22.98, -21.59, // purple
   58  72.53,  -23.71,  57.26, // yellow green
   59  71.94,  19.36 ,  67.86, // orange yellow
   60  28.78,  14.18 , -50.30, // blue
   61  55.26,  -38.34,  31.37, // green
   62  42.10,  53.38 ,  28.19, // red
   63  81.73,  4.04  ,  79.82, // yellow
   64  51.94,  49.99 , -14.57, // magenta
   65  51.04,  -28.63, -28.64, // cyan
   66  96.54,  -0.43 ,  1.19 , // white
   67  81.26,  -0.64 , -0.34 , // neutral 8
   68  66.77,  -0.73 , -0.50 , // neutral 65
   69  50.87,  -0.15 , -0.27 , // neutral 5
   70  35.66,  -0.42 , -1.23 , // neutral 35
   71  20.46,  -0.08 , -0.97   // black
   72 };
   73 
   74 // we came to the conclusion that more than 7x7 patches will not be
   75 // manageable in the gui. the fitting experiments show however that you
   76 // can do significantly better with 49 than you can with 24 patches,
   77 // especially when considering max delta E.
   78 #define MAX_PATCHES 49
   79 typedef struct dt_iop_colorchecker_params_t
   80 {
   81   float source_L[MAX_PATCHES];
   82   float source_a[MAX_PATCHES];
   83   float source_b[MAX_PATCHES];
   84   float target_L[MAX_PATCHES];
   85   float target_a[MAX_PATCHES];
   86   float target_b[MAX_PATCHES];
   87   int32_t num_patches;
   88 } dt_iop_colorchecker_params_t;
   89 
   90 typedef struct dt_iop_colorchecker_gui_data_t
   91 {
   92   GtkWidget *area, *combobox_patch, *scale_L, *scale_a, *scale_b, *scale_C, *combobox_target;
   93   int patch, drawn_patch;
   94   cmsHTRANSFORM xform;
   95   int absolute_target; // 0: show relative offsets in sliders, 1: show absolute Lab values
   96 } dt_iop_colorchecker_gui_data_t;
   97 
   98 typedef struct dt_iop_colorchecker_data_t
   99 {
  100   int32_t num_patches;
  101   float source_Lab[3*MAX_PATCHES];
  102   float coeff_L[MAX_PATCHES+4];
  103   float coeff_a[MAX_PATCHES+4];
  104   float coeff_b[MAX_PATCHES+4];
  105 } dt_iop_colorchecker_data_t;
  106 
  107 typedef struct dt_iop_colorchecker_global_data_t
  108 {
  109   int kernel_colorchecker;
  110 } dt_iop_colorchecker_global_data_t;
  111 
  112 
  113 const char *name()
  114 {
  115   return _("color look up table");
  116 }
  117 
  118 int groups()
  119 {
  120   return dt_iop_get_group("color look up table", IOP_GROUP_COLOR);
  121 }
  122 
  123 int flags()
  124 {
  125   return IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_ALLOW_TILING;
  126 }
  127 
  128 int legacy_params(
  129     dt_iop_module_t  *self,
  130     const void *const old_params,
  131     const int         old_version,
  132     void             *new_params,
  133     const int         new_version)
  134 {
  135   static const float colorchecker_Lab_v1[] = {
  136     39.19, 13.76,  14.29,  // dark skin
  137     65.18, 19.00,  17.32,  // light skin
  138     49.46, -4.23,  -22.95, // blue sky
  139     42.85, -13.33, 22.12,  // foliage
  140     55.18, 9.44,   -24.94, // blue flower
  141     70.36, -32.77, -0.04,  // bluish green
  142     62.92, 35.49,  57.10,  // orange
  143     40.75, 11.41,  -46.03, // purple red
  144     52.10, 48.11,  16.89,  // moderate red
  145     30.67, 21.19,  -20.81, // purple
  146     73.08, -23.55, 56.97,  // yellow green
  147     72.43, 17.48,  68.20,  // orange yellow
  148     30.97, 12.67,  -46.30, // blue
  149     56.43, -40.66, 31.94,  // green
  150     43.40, 50.68,  28.84,  // red
  151     82.45, 2.41,   80.25,  // yellow
  152     51.98, 50.68,  -14.84, // magenta
  153     51.02, -27.63, -28.03, // cyan
  154     95.97, -0.40,  1.24,   // white
  155     81.10, -0.83,  -0.43,  // neutral 8
  156     66.81, -1.08,  -0.70,  // neutral 65
  157     50.98, -0.19,  -0.30,  // neutral 5
  158     35.72, -0.69,  -1.11,  // neutral 35
  159     21.46, 0.06,   -0.95,  // black
  160   };
  161 
  162   typedef struct dt_iop_colorchecker_params_v1_t
  163   {
  164     float target_L[24];
  165     float target_a[24];
  166     float target_b[24];
  167   } dt_iop_colorchecker_params_v1_t;
  168 
  169   if(old_version == 1 && new_version == 2)
  170   {
  171     dt_iop_colorchecker_params_v1_t *p1 = (dt_iop_colorchecker_params_v1_t *)old_params;
  172     dt_iop_colorchecker_params_t  *p2 = (dt_iop_colorchecker_params_t  *)new_params;
  173 
  174     p2->num_patches = 24;
  175     for(int k=0;k<24;k++)
  176     {
  177       p2->target_L[k] = p1->target_L[k];
  178       p2->target_a[k] = p1->target_a[k];
  179       p2->target_b[k] = p1->target_b[k];
  180       p2->source_L[k] = colorchecker_Lab_v1[3 * k + 0];
  181       p2->source_a[k] = colorchecker_Lab_v1[3 * k + 1];
  182       p2->source_b[k] = colorchecker_Lab_v1[3 * k + 2];
  183     }
  184     return 0;
  185   }
  186   return 1;
  187 }
  188 
  189 void init_presets(dt_iop_module_so_t *self)
  190 {
  191   dt_iop_colorchecker_params_t p;
  192   memset(&p, 0, sizeof(p));
  193   p.num_patches = 24;
  194   p.target_L[ 0] = p.source_L[ 0] = 17.460945129394531;
  195   p.target_L[ 1] = p.source_L[ 1] = 26.878498077392578;
  196   p.target_L[ 2] = p.source_L[ 2] = 34.900054931640625;
  197   p.target_L[ 3] = p.source_L[ 3] = 21.692604064941406;
  198   p.target_L[ 4] = p.source_L[ 4] = 32.18853759765625;
  199   p.target_L[ 5] = p.source_L[ 5] = 62.531227111816406;
  200   p.target_L[ 6] = p.source_L[ 6] = 18.933284759521484;
  201   p.target_L[ 7] = p.source_L[ 7] = 53.936111450195312;
  202   p.target_L[ 8] = p.source_L[ 8] = 69.154266357421875;
  203   p.target_L[ 9] = p.source_L[ 9] = 43.381229400634766;
  204   p.target_L[10] = p.source_L[10] = 57.797889709472656;
  205   p.target_L[11] = p.source_L[11] = 73.27630615234375;
  206   p.target_L[12] = p.source_L[12] = 53.175498962402344;
  207   p.target_L[13] = p.source_L[13] = 49.111373901367188;
  208   p.target_L[14] = p.source_L[14] = 63.169830322265625;
  209   p.target_L[15] = p.source_L[15] = 61.896102905273438;
  210   p.target_L[16] = p.source_L[16] = 67.852409362792969;
  211   p.target_L[17] = p.source_L[17] = 72.489517211914062;
  212   p.target_L[18] = p.source_L[18] = 70.935714721679688;
  213   p.target_L[19] = p.source_L[19] = 70.173004150390625;
  214   p.target_L[20] = p.source_L[20] = 77.78826904296875;
  215   p.target_L[21] = p.source_L[21] = 76.070747375488281;
  216   p.target_L[22] = p.source_L[22] = 68.645004272460938;
  217   p.target_L[23] = p.source_L[23] = 74.502906799316406;
  218   p.target_a[ 0] = p.source_a[ 0] = 8.4928874969482422;
  219   p.target_a[ 1] = p.source_a[ 1] = 27.94782829284668;
  220   p.target_a[ 2] = p.source_a[ 2] = 43.8824462890625;
  221   p.target_a[ 3] = p.source_a[ 3] = 16.723676681518555;
  222   p.target_a[ 4] = p.source_a[ 4] = 39.174972534179688;
  223   p.target_a[ 5] = p.source_a[ 5] = 24.966419219970703;
  224   p.target_a[ 6] = p.source_a[ 6] = 8.8226642608642578;
  225   p.target_a[ 7] = p.source_a[ 7] = 34.451812744140625;
  226   p.target_a[ 8] = p.source_a[ 8] = 18.39008903503418;
  227   p.target_a[ 9] = p.source_a[ 9] = 28.272598266601562;
  228   p.target_a[10] = p.source_a[10] = 10.193824768066406;
  229   p.target_a[11] = p.source_a[11] = 13.241470336914062;
  230   p.target_a[12] = p.source_a[12] = 43.655307769775391;
  231   p.target_a[13] = p.source_a[13] = 23.247600555419922;
  232   p.target_a[14] = p.source_a[14] = 23.308664321899414;
  233   p.target_a[15] = p.source_a[15] = 11.138319969177246;
  234   p.target_a[16] = p.source_a[16] = 18.200069427490234;
  235   p.target_a[17] = p.source_a[17] = 15.363990783691406;
  236   p.target_a[18] = p.source_a[18] = 11.173545837402344;
  237   p.target_a[19] = p.source_a[19] = 11.313735961914062;
  238   p.target_a[20] = p.source_a[20] = 15.059500694274902;
  239   p.target_a[21] = p.source_a[21] = 4.7686996459960938;
  240   p.target_a[22] = p.source_a[22] = 3.0603706836700439;
  241   p.target_a[23] = p.source_a[23] = -3.687053918838501;
  242   p.target_b[ 0] = p.source_b[ 0] = -0.023579597473144531;
  243   p.target_b[ 1] = p.source_b[ 1] = 14.991056442260742;
  244   p.target_b[ 2] = p.source_b[ 2] = 26.443553924560547;
  245   p.target_b[ 3] = p.source_b[ 3] = 7.3905587196350098;
  246   p.target_b[ 4] = p.source_b[ 4] = 23.309671401977539;
  247   p.target_b[ 5] = p.source_b[ 5] = 19.262432098388672;
  248   p.target_b[ 6] = p.source_b[ 6] = 3.136211633682251;
  249   p.target_b[ 7] = p.source_b[ 7] = 31.949621200561523;
  250   p.target_b[ 8] = p.source_b[ 8] = 16.144514083862305;
  251   p.target_b[ 9] = p.source_b[ 9] = 25.893926620483398;
  252   p.target_b[10] = p.source_b[10] = 12.271202087402344;
  253   p.target_b[11] = p.source_b[11] = 16.763805389404297;
  254   p.target_b[12] = p.source_b[12] = 53.904998779296875;
  255   p.target_b[13] = p.source_b[13] = 36.537342071533203;
  256   p.target_b[14] = p.source_b[14] = 32.930683135986328;
  257   p.target_b[15] = p.source_b[15] = 19.008804321289062;
  258   p.target_b[16] = p.source_b[16] = 32.259223937988281;
  259   p.target_b[17] = p.source_b[17] = 25.815582275390625;
  260   p.target_b[18] = p.source_b[18] = 26.509498596191406;
  261   p.target_b[19] = p.source_b[19] = 40.572704315185547;
  262   p.target_b[20] = p.source_b[20] = 88.354469299316406;
  263   p.target_b[21] = p.source_b[21] = 33.434604644775391;
  264   p.target_b[22] = p.source_b[22] = 9.5750093460083008;
  265   p.target_b[23] = p.source_b[23] = 41.285167694091797;
  266   dt_gui_presets_add_generic(_("it8 skin tones"), self->op, self->version(), &p, sizeof(p), 1);
  267 
  268   // helmholtz/kohlrausch effect applied to black and white conversion.
  269   // implemented by wmader as an iop and matched as a clut for increased
  270   // flexibility. this was done using darktable-chart and this is copied
  271   // from the resulting dtstyle output file:
  272   const char *hk_params_input =
  273     "9738b84231c098426fb8814234a82d422ac41d422e3fa04100004843f7daa24257e09a422a1a984225113842f89cc9410836ca4295049542ad1c9242887370427cb32b427c512242b5a40742545bd141808740412cc6964262e484429604c44100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef6d3bc152c2acc1ef6566c093a522c2e7d4e4c1a87c7cc100000000b4c4dd407af09e40d060df418afc7d421dadd0413ec5124097d79041fcba2642fc9f484183eb92415d6b7040fcdcdc41b8fe2f42b64a1740fc8612c1276defc144432ec100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d237eb4022a72842f5639742396d1442a2660d411c338b40000000006e35ca408df2054289658d4132327a4118427741d4cf08c0f8a4d5c03abed7c13fac36c23b41a6c03c2230c07d5088c26caff7c1e0e9c6bff14ecec073b028c29e0accc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000085f2b642a4ba9a423c9a8442a6493c428baf28425667b64100004843a836a142a84e9b4226719d421cb15d424c22ee4175fcca4211ae96426e6d9a4243878142ef45354222f82542629527420280ff416c2066417e3996420d838e424182e3410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c8b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004837000000000000c8b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000";
  274   int params_len = 0;
  275   uint8_t *hk_params = dt_exif_xmp_decode(
  276       hk_params_input, strlen(hk_params_input), &params_len);
  277   assert(params_len == sizeof(dt_iop_colorchecker_params_t));
  278   assert(hk_params);
  279   dt_gui_presets_add_generic(_("helmholtz/kohlrausch monochrome"), self->op, self->version(), hk_params, params_len, 1);
  280   free(hk_params);
  281 }
  282 
  283 // fast logarithms stolen from paul mineiro http://fastapprox.googlecode.com/svn/trunk/fastapprox/src/fastonebigheader.h
  284 #if 0//def __SSE2__
  285 #include <xmmintrin.h>
  286 
  287 typedef __m128 v4sf;
  288 typedef __m128i v4si;
  289 
  290 #define v4si_to_v4sf _mm_cvtepi32_ps
  291 #define v4sf_to_v4si _mm_cvttps_epi32
  292 
  293 #define v4sfl(x) ((const v4sf) { (x), (x), (x), (x) })
  294 #define v2dil(x) ((const v4si) { (x), (x) })
  295 #define v4sil(x) v2dil((((unsigned long long) (x)) << 32) | (x))
  296 static inline v4sf
  297 vfastlog2 (v4sf x)
  298 {
  299   union { v4sf f; v4si i; } vx = { x };
  300   union { v4si i; v4sf f; } mx; mx.i = (vx.i & v4sil (0x007FFFFF)) | v4sil (0x3f000000);
  301   v4sf y = v4si_to_v4sf (vx.i);
  302   y *= v4sfl (1.1920928955078125e-7f);
  303 
  304   const v4sf c_124_22551499 = v4sfl (124.22551499f);
  305   const v4sf c_1_498030302 = v4sfl (1.498030302f);
  306   const v4sf c_1_725877999 = v4sfl (1.72587999f);
  307   const v4sf c_0_3520087068 = v4sfl (0.3520887068f);
  308 
  309   return y - c_124_22551499
  310     - c_1_498030302 * mx.f
  311     - c_1_725877999 / (c_0_3520087068 + mx.f);
  312 }
  313 
  314 static inline v4sf
  315 vfastlog (v4sf x)
  316 {
  317   const v4sf c_0_69314718 = v4sfl (0.69314718f);
  318   return c_0_69314718 * vfastlog2 (x);
  319 }
  320 
  321 // thinplate spline kernel \phi(r) = 2 r^2 ln(r)
  322 static inline v4sf kerneldist4(const float *x, const float *y)
  323 {
  324   const float r2 =
  325       (x[0]-y[0])*(x[0]-y[0])+
  326       (x[1]-y[1])*(x[1]-y[1])+
  327       (x[2]-y[2])*(x[2]-y[2]);
  328   return r2 * fastlog(MAX(1e-8f,r2));
  329 }
  330 #endif
  331 
  332 static inline float
  333 fastlog2 (float x)
  334 {
  335   union { float f; uint32_t i; } vx = { x };
  336   union { uint32_t i; float f; } mx = { (vx.i & 0x007FFFFF) | 0x3f000000 };
  337   float y = vx.i;
  338   y *= 1.1920928955078125e-7f;
  339 
  340   return y - 124.22551499f
  341     - 1.498030302f * mx.f
  342     - 1.72587999f / (0.3520887068f + mx.f);
  343 }
  344 
  345 static inline float
  346 fastlog (float x)
  347 {
  348   return 0.69314718f * fastlog2 (x);
  349 }
  350 
  351 // static inline float
  352 // fasterlog(float x)
  353 // {
  354 //   union { float f; uint32_t i; } vx = { x };
  355 //   float y = vx.i;
  356 //   y *= 8.2629582881927490e-8f;
  357 //   return y - 87.989971088f;
  358 // }
  359 
  360 // thinplate spline kernel \phi(r) = 2 r^2 ln(r)
  361 #if defined(_OPENMP) && defined(OPENMP_SIMD_)
  362 #pragma omp declare SIMD()
  363 #endif
  364 static inline float kernel(const float *x, const float *y)
  365 {
  366   // return r*r*logf(MAX(1e-8f,r));
  367   // well damnit, this speedup thing unfortunately shows severe artifacts.
  368   // return r*r*fasterlog(MAX(1e-8f,r));
  369   // this one seems to be a lot better, let's see how it goes:
  370   const float r2 =
  371       (x[0]-y[0])*(x[0]-y[0])+
  372       (x[1]-y[1])*(x[1]-y[1])+
  373       (x[2]-y[2])*(x[2]-y[2]);
  374   return r2*fastlog(MAX(1e-8f,r2));
  375 }
  376 
  377 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
  378              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  379 {
  380   const dt_iop_colorchecker_data_t *const data = (dt_iop_colorchecker_data_t *)piece->data;
  381   const int ch = piece->colors;
  382 #ifdef _OPENMP
  383 #pragma omp parallel for default(none) \
  384   dt_omp_firstprivate(ch, data, ivoid, ovoid, roi_in, roi_out) \
  385   schedule(static) \
  386   collapse(2)
  387 #endif
  388   for(int j=0;j<roi_out->height;j++)
  389   {
  390     for(int i=0;i<roi_out->width;i++)
  391     {
  392       const float *in = ((float *)ivoid) + (size_t)ch * (j * roi_in->width + i);
  393       float *out = ((float *)ovoid) + (size_t)ch * (j * roi_in->width + i);
  394       out[0] = data->coeff_L[data->num_patches];
  395       out[1] = data->coeff_a[data->num_patches];
  396       out[2] = data->coeff_b[data->num_patches];
  397       // polynomial part:
  398       out[0] += data->coeff_L[data->num_patches+1] * in[0] +
  399                 data->coeff_L[data->num_patches+2] * in[1] +
  400                 data->coeff_L[data->num_patches+3] * in[2];
  401       out[1] += data->coeff_a[data->num_patches+1] * in[0] +
  402                 data->coeff_a[data->num_patches+2] * in[1] +
  403                 data->coeff_a[data->num_patches+3] * in[2];
  404       out[2] += data->coeff_b[data->num_patches+1] * in[0] +
  405                 data->coeff_b[data->num_patches+2] * in[1] +
  406                 data->coeff_b[data->num_patches+3] * in[2];
  407 #if defined(_OPENMP) && defined(OPENMP_SIMD_) // <== nice try, i don't think this does anything here
  408 #pragma omp SIMD()
  409 #endif
  410       for(int k=0;k<data->num_patches;k++)
  411       { // rbf from thin plate spline
  412         const float phi = kernel(in, data->source_Lab + 3*k);
  413         out[0] += data->coeff_L[k] * phi;
  414         out[1] += data->coeff_a[k] * phi;
  415         out[2] += data->coeff_b[k] * phi;
  416       }
  417     }
  418   }
  419   if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
  420 }
  421 
  422 #if 0 // TODO:
  423 void process_sse2(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
  424              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  425 {
  426   const dt_iop_colorchecker_data_t *const data = (dt_iop_colorchecker_data_t *)piece->data;
  427   const int ch = piece->colors;
  428   // TODO: swizzle this so we can eval the distance of one point
  429   // TODO: to four patches at the same time
  430   v4sf source_Lab[data->num_patches];
  431   for(int i=0;i<data->num_patches;i++)
  432     source_Lab[i] = _mm_set_ps(1.0,
  433         data->source_Lab[3*i+0],
  434         data->source_Lab[3*i+1],
  435         data->source_Lab[3*i+2]);
  436 #ifdef _OPENMP
  437 #pragma omp parallel for default(none) schedule(static) collapse(2)
  438 #endif
  439   for(int j=0;j<roi_out->height;j++)
  440   {
  441     for(int i=0;i<roi_out->width;i++)
  442     {
  443       const float *in = ((float *)ivoid) + (size_t)ch * (j * roi_in->width + i);
  444       float *out = ((float *)ovoid) + (size_t)ch * (j * roi_in->width + i);
  445       // TODO: do this part in SSE (maybe need to store coeff_L in _mm128 on data struct)
  446       out[0] = data->coeff_L[data->num_patches];
  447       out[1] = data->coeff_a[data->num_patches];
  448       out[2] = data->coeff_b[data->num_patches];
  449       // polynomial part:
  450       out[0] += data->coeff_L[data->num_patches+1] * in[0] +
  451                 data->coeff_L[data->num_patches+2] * in[1] +
  452                 data->coeff_L[data->num_patches+3] * in[2];
  453       out[1] += data->coeff_a[data->num_patches+1] * in[0] +
  454                 data->coeff_a[data->num_patches+2] * in[1] +
  455                 data->coeff_a[data->num_patches+3] * in[2];
  456       out[2] += data->coeff_b[data->num_patches+1] * in[0] +
  457                 data->coeff_b[data->num_patches+2] * in[1] +
  458                 data->coeff_b[data->num_patches+3] * in[2];
  459       for(int k=0;k<data->num_patches;k+=4)
  460       { // rbf from thin plate spline
  461         const v4sf phi = kerneldist4(in, source_Lab[k]);
  462         // TODO: add up 4x output channels
  463         out[0] += data->coeff_L[k] * phi[0];
  464         out[1] += data->coeff_a[k] * phi[0];
  465         out[2] += data->coeff_b[k] * phi[0];
  466       }
  467     }
  468   }
  469   if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
  470 }
  471 #endif
  472 
  473 #ifdef HAVE_OPENCL
  474 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
  475                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  476 {
  477   dt_iop_colorchecker_data_t *d = (dt_iop_colorchecker_data_t *)piece->data;
  478   dt_iop_colorchecker_global_data_t *gd = (dt_iop_colorchecker_global_data_t *)self->data;
  479 
  480   const int devid = piece->pipe->devid;
  481   const int width = roi_out->width;
  482   const int height = roi_out->height;
  483   const int num_patches = d->num_patches;
  484 
  485   cl_int err = -999;
  486   cl_mem dev_params = NULL;
  487 
  488   const size_t params_size = (size_t)(4 * (2 * num_patches + 4)) * sizeof(float);
  489   float *params = malloc(params_size);
  490   float *idx = params;
  491 
  492   // re-arrange data->source_Lab and data->coeff_{L,a,b} into float4
  493   for(int n = 0; n < num_patches; n++, idx += 4)
  494   {
  495     idx[0] = d->source_Lab[3 * n];
  496     idx[1] = d->source_Lab[3 * n + 1];
  497     idx[2] = d->source_Lab[3 * n + 2];
  498     idx[3] = 0.0f;
  499   }
  500 
  501   for(int n = 0; n < num_patches + 4; n++, idx += 4)
  502   {
  503     idx[0] = d->coeff_L[n];
  504     idx[1] = d->coeff_a[n];
  505     idx[2] = d->coeff_b[n];
  506     idx[3] = 0.0f;
  507   }
  508 
  509   dev_params = dt_opencl_copy_host_to_device_constant(devid, params_size, params);
  510   if(dev_params == NULL) goto error;
  511 
  512   size_t sizes[3] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
  513   dt_opencl_set_kernel_arg(devid, gd->kernel_colorchecker, 0, sizeof(cl_mem), (void *)&dev_in);
  514   dt_opencl_set_kernel_arg(devid, gd->kernel_colorchecker, 1, sizeof(cl_mem), (void *)&dev_out);
  515   dt_opencl_set_kernel_arg(devid, gd->kernel_colorchecker, 2, sizeof(int), (void *)&width);
  516   dt_opencl_set_kernel_arg(devid, gd->kernel_colorchecker, 3, sizeof(int), (void *)&height);
  517   dt_opencl_set_kernel_arg(devid, gd->kernel_colorchecker, 4, sizeof(int), (void *)&num_patches);
  518   dt_opencl_set_kernel_arg(devid, gd->kernel_colorchecker, 5, sizeof(cl_mem), (void *)&dev_params);
  519   err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_colorchecker, sizes);
  520   if(err != CL_SUCCESS) goto error;
  521 
  522   dt_opencl_release_mem_object(dev_params);
  523   free(params);
  524   return TRUE;
  525 
  526 error:
  527   free(params);
  528   dt_opencl_release_mem_object(dev_params);
  529   dt_print(DT_DEBUG_OPENCL, "[opencl_colorchecker] couldn't enqueue kernel! %d\n", err);
  530   return FALSE;
  531 }
  532 #endif
  533 
  534 
  535 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
  536                    dt_dev_pixelpipe_iop_t *piece)
  537 {
  538   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)p1;
  539   dt_iop_colorchecker_data_t *d = (dt_iop_colorchecker_data_t *)piece->data;
  540 
  541   d->num_patches = MIN(MAX_PATCHES, p->num_patches);
  542   const int N = d->num_patches, N4 = N + 4;
  543   for(int k = 0; k < N; k++)
  544   {
  545     d->source_Lab[3*k+0] = p->source_L[k];
  546     d->source_Lab[3*k+1] = p->source_a[k];
  547     d->source_Lab[3*k+2] = p->source_b[k];
  548   }
  549 
  550   // initialize coefficients with default values that will be
  551   // used for N<=4 and if coefficient matrix A is singular
  552   for(int i=0;i<4+N;i++)
  553   {
  554     d->coeff_L[i] = 0;
  555     d->coeff_a[i] = 0;
  556     d->coeff_b[i] = 0;
  557   }
  558   d->coeff_L[N + 1] = 1;
  559   d->coeff_a[N + 2] = 1;
  560   d->coeff_b[N + 3] = 1;
  561 
  562   /*
  563       Following
  564 
  565       K. Anjyo, J. P. Lewis, and F. Pighin, "Scattered data
  566       interpolation for computer graphics," ACM SIGGRAPH 2014 Courses
  567       on - SIGGRAPH ’14, 2014.
  568       http://dx.doi.org/10.1145/2614028.2615425
  569       http://scribblethink.org/Courses/ScatteredInterpolation/scatteredinterpcoursenotes.pdf
  570 
  571       construct the system matrix and the vector of function values and
  572       solve the set of linear equations
  573 
  574       / R   P \  / c \   / f \
  575       |       |  |   | = |   |
  576       \ P^t 0 /  \ d /   \ 0 /
  577 
  578       for the coefficient vector (c d)^t.
  579 
  580       By design of the interpolation scheme the interpolation
  581       coefficients c for radial non-linear basis functions (the kernel)
  582       must always vanish for N<=4.  For N<4 the (N+4)x(N+4) coefficient
  583       matrix A is singular, the linear system has non-unique solutions.
  584       Thus the cases with N<=4 need special treatment, unique solutions
  585       are found by setting some of the unknown coefficients to zero and
  586       solving a smaller linear system.
  587   */
  588   switch(N)
  589   {
  590   case 0:
  591     break;
  592   case 1:
  593     // interpolation via constant function
  594     d->coeff_L[N + 1] = p->target_L[0] / p->source_L[0];
  595     d->coeff_a[N + 2] = p->target_a[0] / p->source_a[0];
  596     d->coeff_b[N + 3] = p->target_b[0] / p->source_b[0];
  597     break;
  598   case 2:
  599     // interpolation via single constant function and the linear
  600     // function of the corresponding color channel
  601     {
  602       double A[2 * 2] = { 1, p->source_L[0],
  603                           1, p->source_L[1] };
  604       double b[2] = { p->target_L[0], p->target_L[1] };
  605       if(!gauss_solve(A, b, 2)) break;
  606       d->coeff_L[N + 0] = b[0];
  607       d->coeff_L[N + 1] = b[1];
  608     }
  609     {
  610       double A[2 * 2] = { 1, p->source_a[0],
  611                           1, p->source_a[1] };
  612       double b[2] = { p->target_a[0], p->target_a[1] };
  613       if(!gauss_solve(A, b, 2)) break;
  614       d->coeff_a[N + 0] = b[0];
  615       d->coeff_a[N + 2] = b[1];
  616     }
  617     {
  618       double A[2 * 2] = { 1, p->source_b[0],
  619                           1, p->source_b[1] };
  620       double b[2] = { p->target_b[0], p->target_b[1] };
  621       if(!gauss_solve(A, b, 2)) break;
  622       d->coeff_b[N + 0] = b[0];
  623       d->coeff_b[N + 3] = b[1];
  624     }
  625     break;
  626   case 3:
  627     // interpolation via single constant function, the linear function
  628     // of the corresponding color channel and the linear functions
  629     // of the other two color channels having both the same weight
  630     {
  631       double A[3 * 3] = { 1, p->source_L[0], p->source_a[0] + p->source_b[0],
  632                           1, p->source_L[1], p->source_a[1] + p->source_b[1],
  633                           1, p->source_L[2], p->source_a[2] + p->source_b[2] };
  634       double b[3] = { p->target_L[0], p->target_L[1], p->target_L[2] };
  635       if(!gauss_solve(A, b, 3)) break;
  636       d->coeff_L[N + 0] = b[0];
  637       d->coeff_L[N + 1] = b[1];
  638       d->coeff_L[N + 2] = b[2];
  639       d->coeff_L[N + 3] = b[2];
  640     }
  641     {
  642       double A[3 * 3] = { 1, p->source_a[0], p->source_L[0] + p->source_b[0],
  643                           1, p->source_a[1], p->source_L[1] + p->source_b[1],
  644                           1, p->source_a[2], p->source_L[2] + p->source_b[2] };
  645       double b[3] = { p->target_a[0], p->target_a[1], p->target_a[2] };
  646       if(!gauss_solve(A, b, 3)) break;
  647       d->coeff_a[N + 0] = b[0];
  648       d->coeff_a[N + 1] = b[2];
  649       d->coeff_a[N + 2] = b[1];
  650       d->coeff_a[N + 3] = b[2];
  651     }
  652     {
  653       double A[3 * 3] = { 1, p->source_b[0], p->source_L[0] + p->source_a[0],
  654                           1, p->source_b[1], p->source_L[1] + p->source_a[1],
  655                           1, p->source_b[2], p->source_L[2] + p->source_a[2] };
  656       double b[3] = { p->target_b[0], p->target_b[1], p->target_b[2] };
  657       if(!gauss_solve(A, b, 3)) break;
  658       d->coeff_b[N + 0] = b[0];
  659       d->coeff_b[N + 1] = b[2];
  660       d->coeff_b[N + 2] = b[2];
  661       d->coeff_b[N + 3] = b[1];
  662     }
  663     break;
  664   case 4:
  665   {
  666     // interpolation via constant function and 3 linear functions
  667     double A[4 * 4] = { 1, p->source_L[0], p->source_a[0], p->source_b[0],
  668                         1, p->source_L[1], p->source_a[1], p->source_b[1],
  669                         1, p->source_L[2], p->source_a[2], p->source_b[2],
  670                         1, p->source_L[3], p->source_a[3], p->source_b[3] };
  671     int pivot[4];
  672     if(!gauss_make_triangular(A, pivot, 4)) break;
  673     {
  674       double b[4] = { p->target_L[0], p->target_L[1], p->target_L[2], p->target_L[3] };
  675       gauss_solve_triangular(A, pivot, b, 4);
  676       d->coeff_L[N + 0] = b[0];
  677       d->coeff_L[N + 1] = b[1];
  678       d->coeff_L[N + 2] = b[2];
  679       d->coeff_L[N + 3] = b[3];
  680     }
  681     {
  682       double b[4] = { p->target_a[0], p->target_a[1], p->target_a[2], p->target_a[3] };
  683       gauss_solve_triangular(A, pivot, b, 4);
  684       d->coeff_a[N + 0] = b[0];
  685       d->coeff_a[N + 1] = b[1];
  686       d->coeff_a[N + 2] = b[2];
  687       d->coeff_a[N + 3] = b[3];
  688     }
  689     {
  690       double b[4] = { p->target_b[0], p->target_b[1], p->target_b[2], p->target_b[3] };
  691       gauss_solve_triangular(A, pivot, b, 4);
  692       d->coeff_b[N + 0] = b[0];
  693       d->coeff_b[N + 1] = b[1];
  694       d->coeff_b[N + 2] = b[2];
  695       d->coeff_b[N + 3] = b[3];
  696     }
  697     break;
  698   }
  699   default:
  700   {
  701     // setup linear system of equations
  702     double *A = malloc(N4 * N4 * sizeof(*A));
  703     double *b = malloc(N4 * sizeof(*b));
  704     // coefficients from nonlinear radial kernel functions
  705     for(int j=0;j<N;j++)
  706       for(int i=j;i<N;i++)
  707         A[j*N4+i] = A[i*N4+j] = kernel(d->source_Lab+3*i, d->source_Lab+3*j);
  708     // coefficients from constant and linear functions
  709     for(int i=0;i<N;i++) A[i*N4+N+0] = A[(N+0)*N4+i] = 1;
  710     for(int i=0;i<N;i++) A[i*N4+N+1] = A[(N+1)*N4+i] = d->source_Lab[3*i+0];
  711     for(int i=0;i<N;i++) A[i*N4+N+2] = A[(N+2)*N4+i] = d->source_Lab[3*i+1];
  712     for(int i=0;i<N;i++) A[i*N4+N+3] = A[(N+3)*N4+i] = d->source_Lab[3*i+2];
  713     // lower-right zero block
  714     for(int j=N;j<N4;j++)
  715       for(int i=N;i<N4;i++)
  716         A[j*N4+i] = 0;
  717     // make coefficient matrix triangular
  718     int *pivot = malloc(N4 * sizeof(*pivot));
  719     if (gauss_make_triangular(A, pivot, N4))
  720     {
  721       // calculate coefficients for L channel
  722       for(int i=0;i<N;i++) b[i] = p->target_L[i];
  723       for(int i=N;i<N+4;i++) b[i] = 0;
  724       gauss_solve_triangular(A, pivot, b, N4);
  725       for(int i=0;i<N+4;i++) d->coeff_L[i] = b[i];
  726       // calculate coefficients for a channel
  727       for(int i=0;i<N;i++) b[i] = p->target_a[i];
  728       for(int i=N;i<N+4;i++) b[i] = 0;
  729       gauss_solve_triangular(A, pivot, b, N4);
  730       for(int i=0;i<N+4;i++) d->coeff_a[i] = b[i];
  731       // calculate coefficients for b channel
  732       for(int i=0;i<N;i++) b[i] = p->target_b[i];
  733       for(int i=N;i<N+4;i++) b[i] = 0;
  734       gauss_solve_triangular(A, pivot, b, N4);
  735       for(int i=0;i<N+4;i++) d->coeff_b[i] = b[i];
  736     }
  737     // free resources
  738     free(pivot);
  739     free(b);
  740     free(A);
  741   }
  742   }
  743 }
  744 
  745 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
  746 {
  747   piece->data = malloc(sizeof(dt_iop_colorchecker_data_t));
  748   self->commit_params(self, self->default_params, pipe, piece);
  749 }
  750 
  751 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
  752 {
  753   free(piece->data);
  754   piece->data = NULL;
  755 }
  756 
  757 void gui_reset(struct dt_iop_module_t *self)
  758 {
  759   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
  760   self->request_color_pick = DT_REQUEST_COLORPICK_OFF;
  761   dt_bauhaus_widget_set_quad_active(g->combobox_patch, 0);
  762 }
  763 
  764 void gui_update(struct dt_iop_module_t *self)
  765 {
  766   dt_iop_module_t *module = (dt_iop_module_t *)self;
  767   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
  768   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)module->params;
  769   if(g->patch >= p->num_patches || g->patch < 0) return;
  770   if(dt_bauhaus_combobox_length(g->combobox_patch) != p->num_patches)
  771   {
  772     dt_bauhaus_combobox_clear(g->combobox_patch);
  773     char cboxentry[1024];
  774     for(int k=0;k<p->num_patches;k++)
  775     {
  776       snprintf(cboxentry, sizeof(cboxentry), _("patch #%d"), k);
  777       dt_bauhaus_combobox_add(g->combobox_patch, cboxentry);
  778     }
  779     if(p->num_patches <= 24)
  780       dtgtk_drawing_area_set_aspect_ratio(g->area, 2.0/3.0);
  781     else
  782       dtgtk_drawing_area_set_aspect_ratio(g->area, 1.0);
  783   }
  784   if(g->absolute_target)
  785   {
  786     dt_bauhaus_slider_set(g->scale_L, p->target_L[g->patch]);
  787     dt_bauhaus_slider_set(g->scale_a, p->target_a[g->patch]);
  788     dt_bauhaus_slider_set(g->scale_b, p->target_b[g->patch]);
  789     const float Cout = sqrtf(
  790         p->target_a[g->patch]*p->target_a[g->patch]+
  791         p->target_b[g->patch]*p->target_b[g->patch]);
  792     dt_bauhaus_slider_set(g->scale_C, Cout);
  793   }
  794   else
  795   {
  796     dt_bauhaus_slider_set(g->scale_L, p->target_L[g->patch] - p->source_L[g->patch]);
  797     dt_bauhaus_slider_set(g->scale_a, p->target_a[g->patch] - p->source_a[g->patch]);
  798     dt_bauhaus_slider_set(g->scale_b, p->target_b[g->patch] - p->source_b[g->patch]);
  799     const float Cin = sqrtf(
  800         p->source_a[g->patch]*p->source_a[g->patch] +
  801         p->source_b[g->patch]*p->source_b[g->patch]);
  802     const float Cout = sqrtf(
  803         p->target_a[g->patch]*p->target_a[g->patch]+
  804         p->target_b[g->patch]*p->target_b[g->patch]);
  805     dt_bauhaus_slider_set(g->scale_C, Cout-Cin);
  806   }
  807   gtk_widget_queue_draw(g->area);
  808 
  809   if (self->request_color_pick == DT_REQUEST_COLORPICK_OFF)
  810     dt_bauhaus_widget_set_quad_active(g->combobox_patch, 0);
  811 }
  812 
  813 void init(dt_iop_module_t *module)
  814 {
  815   module->params = calloc(1, sizeof(dt_iop_colorchecker_params_t));
  816   module->default_params = calloc(1, sizeof(dt_iop_colorchecker_params_t));
  817   module->default_enabled = 0;
  818   module->priority = 399; // module order created by iop_dependencies.py, do not edit!
  819   module->params_size = sizeof(dt_iop_colorchecker_params_t);
  820   module->gui_data = NULL;
  821   dt_iop_colorchecker_params_t tmp;
  822   tmp.num_patches = 24;
  823   for(int k=0;k<tmp.num_patches;k++) tmp.source_L[k] = colorchecker_Lab[3*k+0];
  824   for(int k=0;k<tmp.num_patches;k++) tmp.source_a[k] = colorchecker_Lab[3*k+1];
  825   for(int k=0;k<tmp.num_patches;k++) tmp.source_b[k] = colorchecker_Lab[3*k+2];
  826   for(int k=0;k<tmp.num_patches;k++) tmp.target_L[k] = colorchecker_Lab[3*k+0];
  827   for(int k=0;k<tmp.num_patches;k++) tmp.target_a[k] = colorchecker_Lab[3*k+1];
  828   for(int k=0;k<tmp.num_patches;k++) tmp.target_b[k] = colorchecker_Lab[3*k+2];
  829   memcpy(module->params, &tmp, sizeof(dt_iop_colorchecker_params_t));
  830   memcpy(module->default_params, &tmp, sizeof(dt_iop_colorchecker_params_t));
  831 }
  832 
  833 void cleanup(dt_iop_module_t *module)
  834 {
  835   free(module->params);
  836   module->params = NULL;
  837 }
  838 
  839 void init_global(dt_iop_module_so_t *module)
  840 {
  841   dt_iop_colorchecker_global_data_t *gd
  842       = (dt_iop_colorchecker_global_data_t *)malloc(sizeof(dt_iop_colorchecker_global_data_t));
  843   module->data = gd;
  844 
  845   const int program = 8; // extended.cl, from programs.conf
  846   gd->kernel_colorchecker = dt_opencl_create_kernel(program, "colorchecker");
  847 }
  848 
  849 void cleanup_global(dt_iop_module_so_t *module)
  850 {
  851   dt_iop_colorchecker_global_data_t *gd = (dt_iop_colorchecker_global_data_t *)module->data;
  852   dt_opencl_free_kernel(gd->kernel_colorchecker);
  853   free(module->data);
  854   module->data = NULL;
  855 }
  856 
  857 static void picker_callback(GtkWidget *button, gpointer user_data)
  858 {
  859   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  860   if(darktable.gui->reset) return;
  861 
  862   if(self->request_color_pick != DT_REQUEST_COLORPICK_MODULE)
  863     self->request_color_pick = DT_REQUEST_COLORPICK_MODULE;
  864   else
  865     self->request_color_pick = DT_REQUEST_COLORPICK_OFF;
  866 
  867   dt_iop_request_focus(self);
  868 
  869   if(self->request_color_pick != DT_REQUEST_COLORPICK_OFF)
  870     dt_dev_reprocess_all(self->dev);
  871   else
  872     dt_control_queue_redraw();
  873 
  874   if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
  875 }
  876 
  877 static void target_L_callback(GtkWidget *slider, gpointer user_data)
  878 {
  879   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  880   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
  881   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
  882   if(g->patch >= p->num_patches || g->patch < 0) return;
  883   if(g->absolute_target)
  884     p->target_L[g->patch] = dt_bauhaus_slider_get(slider);
  885   else
  886     p->target_L[g->patch] = p->source_L[g->patch] + dt_bauhaus_slider_get(slider);
  887   dt_dev_add_history_item(darktable.develop, self, TRUE);
  888 }
  889 
  890 static void target_a_callback(GtkWidget *slider, gpointer user_data)
  891 {
  892   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  893   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
  894   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
  895   if(g->patch >= p->num_patches || g->patch < 0) return;
  896   if(g->absolute_target)
  897   {
  898     p->target_a[g->patch] = CLAMP(dt_bauhaus_slider_get(slider), -128.0, 128.0);
  899     const float Cout = sqrtf(
  900         p->target_a[g->patch]*p->target_a[g->patch]+
  901         p->target_b[g->patch]*p->target_b[g->patch]);
  902     const int reset = darktable.gui->reset;
  903     darktable.gui->reset = 1; // avoid history item
  904     dt_bauhaus_slider_set(g->scale_C, Cout);
  905     darktable.gui->reset = reset;
  906   }
  907   else
  908   {
  909     p->target_a[g->patch] = CLAMP(p->source_a[g->patch] + dt_bauhaus_slider_get(slider), -128.0, 128.0);
  910     const float Cin = sqrtf(
  911         p->source_a[g->patch]*p->source_a[g->patch] +
  912         p->source_b[g->patch]*p->source_b[g->patch]);
  913     const float Cout = sqrtf(
  914         p->target_a[g->patch]*p->target_a[g->patch]+
  915         p->target_b[g->patch]*p->target_b[g->patch]);
  916     const int reset = darktable.gui->reset;
  917     darktable.gui->reset = 1; // avoid history item
  918     dt_bauhaus_slider_set(g->scale_C, Cout-Cin);
  919     darktable.gui->reset = reset;
  920   }
  921   dt_dev_add_history_item(darktable.develop, self, TRUE);
  922 }
  923 
  924 static void target_b_callback(GtkWidget *slider, gpointer user_data)
  925 {
  926   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  927   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
  928   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
  929   if(g->patch >= p->num_patches || g->patch < 0) return;
  930   if(g->absolute_target)
  931   {
  932     p->target_b[g->patch] = CLAMP(dt_bauhaus_slider_get(slider), -128.0, 128.0);
  933     const float Cout = sqrtf(
  934         p->target_a[g->patch]*p->target_a[g->patch]+
  935         p->target_b[g->patch]*p->target_b[g->patch]);
  936     const int reset = darktable.gui->reset;
  937     darktable.gui->reset = 1; // avoid history item
  938     dt_bauhaus_slider_set(g->scale_C, Cout);
  939     darktable.gui->reset = reset;
  940   }
  941   else
  942   {
  943     p->target_b[g->patch] = CLAMP(p->source_b[g->patch] + dt_bauhaus_slider_get(slider), -128.0, 128.0);
  944     const float Cin = sqrtf(
  945         p->source_a[g->patch]*p->source_a[g->patch] +
  946         p->source_b[g->patch]*p->source_b[g->patch]);
  947     const float Cout = sqrtf(
  948         p->target_a[g->patch]*p->target_a[g->patch]+
  949         p->target_b[g->patch]*p->target_b[g->patch]);
  950     const int reset = darktable.gui->reset;
  951     darktable.gui->reset = 1; // avoid history item
  952     dt_bauhaus_slider_set(g->scale_C, Cout-Cin);
  953     darktable.gui->reset = reset;
  954   }
  955   dt_dev_add_history_item(darktable.develop, self, TRUE);
  956 }
  957 
  958 static void target_C_callback(GtkWidget *slider, gpointer user_data)
  959 {
  960   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  961   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
  962   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
  963   if(g->patch >= p->num_patches || g->patch < 0) return;
  964   const float Cin = sqrtf(
  965       p->source_a[g->patch]*p->source_a[g->patch] +
  966       p->source_b[g->patch]*p->source_b[g->patch]);
  967   const float Cout = MAX(1e-4f, sqrtf(
  968       p->target_a[g->patch]*p->target_a[g->patch]+
  969       p->target_b[g->patch]*p->target_b[g->patch]));
  970 
  971   if(g->absolute_target)
  972   {
  973     const float Cnew = CLAMP(dt_bauhaus_slider_get(slider), 0.01, 128.0);
  974     p->target_a[g->patch] = CLAMP(p->target_a[g->patch]*Cnew/Cout, -128.0, 128.0);
  975     p->target_b[g->patch] = CLAMP(p->target_b[g->patch]*Cnew/Cout, -128.0, 128.0);
  976     const int reset = darktable.gui->reset;
  977     darktable.gui->reset = 1; // avoid history item
  978     dt_bauhaus_slider_set(g->scale_a, p->target_a[g->patch]);
  979     dt_bauhaus_slider_set(g->scale_b, p->target_b[g->patch]);
  980     darktable.gui->reset = reset;
  981   }
  982   else
  983   {
  984     const float Cnew = CLAMP(Cin + dt_bauhaus_slider_get(slider), 0.01, 128.0);
  985     p->target_a[g->patch] = CLAMP(p->target_a[g->patch]*Cnew/Cout, -128.0, 128.0);
  986     p->target_b[g->patch] = CLAMP(p->target_b[g->patch]*Cnew/Cout, -128.0, 128.0);
  987     const int reset = darktable.gui->reset;
  988     darktable.gui->reset = 1; // avoid history item
  989     dt_bauhaus_slider_set(g->scale_a, p->target_a[g->patch] - p->source_a[g->patch]);
  990     dt_bauhaus_slider_set(g->scale_b, p->target_b[g->patch] - p->source_b[g->patch]);
  991     darktable.gui->reset = reset;
  992   }
  993   dt_dev_add_history_item(darktable.develop, self, TRUE);
  994 }
  995 
  996 static void target_callback(GtkWidget *combo, gpointer user_data)
  997 {
  998   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  999   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1000   g->absolute_target = dt_bauhaus_combobox_get(combo);
 1001   // switch off colour picker, it'll interfere with other changes of the patch:
 1002   self->request_color_pick = DT_REQUEST_COLORPICK_OFF;
 1003   self->gui_update(self);
 1004 }
 1005 
 1006 static void patch_callback(GtkWidget *combo, gpointer user_data)
 1007 {
 1008   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
 1009   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1010   g->patch = dt_bauhaus_combobox_get(combo);
 1011   // switch off colour picker, it'll interfere with other changes of the patch:
 1012   self->request_color_pick = DT_REQUEST_COLORPICK_OFF;
 1013   self->gui_update(self);
 1014 }
 1015 
 1016 static gboolean checker_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
 1017 {
 1018   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
 1019   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1020   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
 1021 
 1022   GtkAllocation allocation;
 1023   gtk_widget_get_allocation(widget, &allocation);
 1024   int width = allocation.width, height = allocation.height;
 1025   cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
 1026   cairo_t *cr = cairo_create(cst);
 1027   // clear bg
 1028   cairo_set_source_rgb(cr, .2, .2, .2);
 1029   cairo_paint(cr);
 1030 
 1031   const float *picked_mean = self->picked_color;
 1032   int besti = 0, bestj = 0;
 1033   cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
 1034   int cells_x = 6, cells_y = 4;
 1035   if(p->num_patches > 24)
 1036   {
 1037     cells_x = 7;
 1038     cells_y = 7;
 1039   }
 1040   for(int j = 0; j < cells_y; j++)
 1041   {
 1042     for(int i = 0; i < cells_x; i++)
 1043     {
 1044       double rgb[3] = { 0.5, 0.5, 0.5 }; // Lab: rgb grey converted to Lab
 1045       cmsCIELab Lab;
 1046       const int patch = i + j*cells_x;
 1047       if(patch >= p->num_patches) continue;
 1048       Lab.L = p->source_L[patch];
 1049       Lab.a = p->source_a[patch];
 1050       Lab.b = p->source_b[patch];
 1051       if((self->request_color_pick == DT_REQUEST_COLORPICK_MODULE)
 1052          && ((picked_mean[0] - Lab.L) * (picked_mean[0] - Lab.L)
 1053                  + (picked_mean[1] - Lab.a) * (picked_mean[1] - Lab.a)
 1054                  + (picked_mean[2] - Lab.b) * (picked_mean[2] - Lab.b)
 1055              < (picked_mean[0] - p->source_L[cells_x * bestj + besti])
 1056                        * (picked_mean[0] - p->source_L[cells_x * bestj + besti])
 1057                    + (picked_mean[1] - p->source_a[cells_x * bestj + besti])
 1058                          * (picked_mean[1] - p->source_a[cells_x * bestj + besti])
 1059                    + (picked_mean[2] - p->source_b[cells_x * bestj + besti])
 1060                          * (picked_mean[2] - p->source_b[cells_x * bestj + besti])))
 1061       {
 1062         besti = i;
 1063         bestj = j;
 1064       }
 1065       cmsDoTransform(g->xform, &Lab, rgb, 1);
 1066       cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
 1067       cairo_rectangle(cr, width * i / (float)cells_x, height * j / (float)cells_y,
 1068           width / (float)cells_x - DT_PIXEL_APPLY_DPI(1),
 1069           height / (float)cells_y - DT_PIXEL_APPLY_DPI(1));
 1070       cairo_fill(cr);
 1071       if(fabsf(p->target_L[patch] - p->source_L[patch]) > 1e-5f ||
 1072          fabsf(p->target_a[patch] - p->source_a[patch]) > 1e-5f ||
 1073          fabsf(p->target_b[patch] - p->source_b[patch]) > 1e-5f)
 1074       {
 1075         cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
 1076         cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
 1077         cairo_rectangle(cr,
 1078             width * i / (float)cells_x + DT_PIXEL_APPLY_DPI(1),
 1079             height * j / (float)cells_y + DT_PIXEL_APPLY_DPI(1),
 1080             width / (float)cells_x - DT_PIXEL_APPLY_DPI(3),
 1081             height / (float)cells_y - DT_PIXEL_APPLY_DPI(3));
 1082         cairo_stroke(cr);
 1083         cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
 1084         cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
 1085         cairo_rectangle(cr,
 1086             width * i / (float)cells_x + DT_PIXEL_APPLY_DPI(2),
 1087             height * j / (float)cells_y + DT_PIXEL_APPLY_DPI(2),
 1088             width / (float)cells_x - DT_PIXEL_APPLY_DPI(5),
 1089             height / (float)cells_y - DT_PIXEL_APPLY_DPI(5));
 1090         cairo_stroke(cr);
 1091       }
 1092     }
 1093   }
 1094 
 1095   dt_bauhaus_widget_set_quad_paint(
 1096       g->combobox_patch, dtgtk_cairo_paint_colorpicker,
 1097       (self->request_color_pick == DT_REQUEST_COLORPICK_MODULE ? CPF_ACTIVE : CPF_NONE), NULL);
 1098 
 1099   // highlight patch that is closest to picked colour,
 1100   // or the one selected in the combobox.
 1101   if(self->request_color_pick != DT_REQUEST_COLORPICK_MODULE)
 1102   {
 1103     int i = dt_bauhaus_combobox_get(g->combobox_patch);
 1104     besti = i % cells_x;
 1105     bestj = i / cells_x;
 1106     g->drawn_patch = cells_x * bestj + besti;
 1107   }
 1108   else if(self->request_color_pick == DT_REQUEST_COLORPICK_MODULE)
 1109   {
 1110     // freshly picked, also select it in gui:
 1111     int pick = self->request_color_pick;
 1112     g->drawn_patch = cells_x * bestj + besti;
 1113     darktable.gui->reset = 1;
 1114     dt_bauhaus_combobox_set(g->combobox_patch, g->drawn_patch);
 1115     g->patch = g->drawn_patch;
 1116     self->gui_update(self);
 1117     darktable.gui->reset = 0;
 1118     self->request_color_pick = pick; // restore, the combobox will kill it
 1119   }
 1120   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
 1121   cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
 1122   cairo_rectangle(cr,
 1123       width * besti / (float)cells_x + DT_PIXEL_APPLY_DPI(5),
 1124       height * bestj / (float)cells_y + DT_PIXEL_APPLY_DPI(5),
 1125       width / (float)cells_x - DT_PIXEL_APPLY_DPI(11),
 1126       height / (float)cells_y - DT_PIXEL_APPLY_DPI(11));
 1127   cairo_stroke(cr);
 1128 
 1129   cairo_destroy(cr);
 1130   cairo_set_source_surface(crf, cst, 0, 0);
 1131   cairo_paint(crf);
 1132   cairo_surface_destroy(cst);
 1133   return TRUE;
 1134 }
 1135 
 1136 static gboolean checker_motion_notify(GtkWidget *widget, GdkEventMotion *event,
 1137     gpointer user_data)
 1138 {
 1139   // highlight?
 1140   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
 1141   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1142   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
 1143   GtkAllocation allocation;
 1144   gtk_widget_get_allocation(widget, &allocation);
 1145   int width = allocation.width, height = allocation.height;
 1146   const float mouse_x = CLAMP(event->x, 0, width);
 1147   const float mouse_y = CLAMP(event->y, 0, height);
 1148   int cells_x = 6, cells_y = 4;
 1149   if(p->num_patches > 24)
 1150   {
 1151     cells_x = 7;
 1152     cells_y = 7;
 1153   }
 1154   const float mx = mouse_x * cells_x / (float)width;
 1155   const float my = mouse_y * cells_y / (float)height;
 1156   const int patch = (int)mx + cells_x * (int)my;
 1157   if(patch < 0 || patch >= p->num_patches) return FALSE;
 1158   char tooltip[1024];
 1159   snprintf(tooltip, sizeof(tooltip),
 1160       _("(%2.2f %2.2f %2.2f)\n"
 1161         "altered patches are marked with an outline\n"
 1162         "click to select\n"
 1163         "double click to reset\n"
 1164         "right click to delete patch\n"
 1165         "shift-click while color picking to replace patch"),
 1166       p->source_L[patch], p->source_a[patch], p->source_b[patch]);
 1167   gtk_widget_set_tooltip_text(g->area, tooltip);
 1168   return TRUE;
 1169 }
 1170 
 1171 static gboolean checker_button_press(GtkWidget *widget, GdkEventButton *event,
 1172                                                     gpointer user_data)
 1173 {
 1174   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
 1175   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1176   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
 1177   GtkAllocation allocation;
 1178   gtk_widget_get_allocation(widget, &allocation);
 1179   int width = allocation.width, height = allocation.height;
 1180   const float mouse_x = CLAMP(event->x, 0, width);
 1181   const float mouse_y = CLAMP(event->y, 0, height);
 1182   int cells_x = 6, cells_y = 4;
 1183   if(p->num_patches > 24)
 1184   {
 1185     cells_x = 7;
 1186     cells_y = 7;
 1187   }
 1188   const float mx = mouse_x * cells_x / (float)width;
 1189   const float my = mouse_y * cells_y / (float)height;
 1190   int patch = (int)mx + cells_x*(int)my;
 1191   if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
 1192   { // reset on double click
 1193     if(patch < 0 || patch >= p->num_patches) return FALSE;
 1194     p->target_L[patch] = p->source_L[patch];
 1195     p->target_a[patch] = p->source_a[patch];
 1196     p->target_b[patch] = p->source_b[patch];
 1197     dt_dev_add_history_item(darktable.develop, self, TRUE);
 1198     self->gui_update(self);
 1199     return TRUE;
 1200   }
 1201   else if(event->button == 3 && (patch < p->num_patches))
 1202   {
 1203     // right click: delete patch, move others up
 1204     if(patch < 0 || patch >= p->num_patches) return FALSE;
 1205     memmove(p->target_L+patch, p->target_L+patch+1, sizeof(float)*(p->num_patches-1-patch));
 1206     memmove(p->target_a+patch, p->target_a+patch+1, sizeof(float)*(p->num_patches-1-patch));
 1207     memmove(p->target_b+patch, p->target_b+patch+1, sizeof(float)*(p->num_patches-1-patch));
 1208     memmove(p->source_L+patch, p->source_L+patch+1, sizeof(float)*(p->num_patches-1-patch));
 1209     memmove(p->source_a+patch, p->source_a+patch+1, sizeof(float)*(p->num_patches-1-patch));
 1210     memmove(p->source_b+patch, p->source_b+patch+1, sizeof(float)*(p->num_patches-1-patch));
 1211     p->num_patches--;
 1212     dt_dev_add_history_item(darktable.develop, self, TRUE);
 1213     self->gui_update(self);
 1214     return TRUE;
 1215   }
 1216   else if((event->button == 1) &&
 1217           ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) &&
 1218           (self->request_color_pick == DT_REQUEST_COLORPICK_MODULE))
 1219   {
 1220     // shift-left while colour picking: replace source colour
 1221     // if clicked outside the valid patches: add new one
 1222 
 1223     // color channels should be nonzero to avoid numerical issues
 1224     int new_color_valid = fabsf(self->picked_color[0]) > 1.e-3f &&
 1225                           fabsf(self->picked_color[1]) > 1.e-3f &&
 1226                           fabsf(self->picked_color[2]) > 1.e-3f;
 1227     // check if the new color is very close to some color already in the colorchecker
 1228     for(int i=0;i<p->num_patches;++i)
 1229     {
 1230       float color[] = { p->source_L[i], p->source_a[i], p->source_b[i] };
 1231       if(fabsf(self->picked_color[0] - color[0]) < 1.e-3f && fabsf(self->picked_color[1] - color[1]) < 1.e-3f
 1232          && fabsf(self->picked_color[2] - color[2]) < 1.e-3f)
 1233         new_color_valid = FALSE;
 1234     }
 1235     if(new_color_valid)
 1236     {
 1237       if(p->num_patches < 24 && (patch < 0 || patch >= p->num_patches))
 1238       {
 1239         p->num_patches = MIN(MAX_PATCHES, p->num_patches + 1);
 1240         patch = p->num_patches - 1;
 1241       }
 1242       p->target_L[patch] = p->source_L[patch] = self->picked_color[0];
 1243       p->target_a[patch] = p->source_a[patch] = self->picked_color[1];
 1244       p->target_b[patch] = p->source_b[patch] = self->picked_color[2];
 1245       dt_dev_add_history_item(darktable.develop, self, TRUE);
 1246       self->gui_update(self);
 1247     }
 1248     return TRUE;
 1249   }
 1250   if(patch >= p->num_patches) patch = p->num_patches-1;
 1251   dt_bauhaus_combobox_set(g->combobox_patch, patch);
 1252   return FALSE;
 1253 }
 1254 
 1255 static gboolean checker_leave_notify(GtkWidget *widget, GdkEventCrossing *event,
 1256                                                     gpointer user_data)
 1257 {
 1258   return FALSE; // ?
 1259 }
 1260 
 1261 void gui_init(struct dt_iop_module_t *self)
 1262 {
 1263   self->gui_data = malloc(sizeof(dt_iop_colorchecker_gui_data_t));
 1264   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1265   dt_iop_colorchecker_params_t *p = (dt_iop_colorchecker_params_t *)self->params;
 1266 
 1267   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
 1268   dt_gui_add_help_link(self->widget, dt_get_help_url(self->op));
 1269 
 1270   // custom 24-patch widget in addition to combo box
 1271   g->area = dtgtk_drawing_area_new_with_aspect_ratio(4.0/6.0);
 1272   gtk_box_pack_start(GTK_BOX(self->widget), g->area, TRUE, TRUE, 0);
 1273 
 1274   gtk_widget_add_events(GTK_WIDGET(g->area), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
 1275                                              | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
 1276                                              | GDK_LEAVE_NOTIFY_MASK | GDK_SCROLL_MASK);
 1277   g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(checker_draw), self);
 1278   g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(checker_button_press), self);
 1279   g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(checker_motion_notify), self);
 1280   g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(checker_leave_notify), self);
 1281 
 1282   g->patch = 0;
 1283   g->drawn_patch = -1;
 1284   g->combobox_patch = dt_bauhaus_combobox_new(self);
 1285   dt_bauhaus_widget_set_label(g->combobox_patch, NULL, _("patch"));
 1286   gtk_widget_set_tooltip_text(g->combobox_patch, _("color checker patch"));
 1287   char cboxentry[1024];
 1288   for(int k=0;k<p->num_patches;k++)
 1289   {
 1290     snprintf(cboxentry, sizeof(cboxentry), _("patch #%d"), k);
 1291     dt_bauhaus_combobox_add(g->combobox_patch, cboxentry);
 1292   }
 1293   self->request_color_pick = DT_REQUEST_COLORPICK_OFF;
 1294   dt_bauhaus_widget_set_quad_paint(g->combobox_patch, dtgtk_cairo_paint_colorpicker, CPF_NONE, NULL);
 1295 
 1296   g->scale_L = dt_bauhaus_slider_new_with_range(self, -100.0, 200.0, 1.0, 0.0f, 2);
 1297   gtk_widget_set_tooltip_text(g->scale_L, _("lightness offset"));
 1298   dt_bauhaus_widget_set_label(g->scale_L, NULL, _("lightness"));
 1299 
 1300   g->scale_a = dt_bauhaus_slider_new_with_range(self, -256.0, 256.0, 1.0, 0.0f, 2);
 1301   gtk_widget_set_tooltip_text(g->scale_a, _("chroma offset green/red"));
 1302   dt_bauhaus_widget_set_label(g->scale_a, NULL, _("green/red"));
 1303   dt_bauhaus_slider_set_stop(g->scale_a, 0.0, 0.0, 1.0, 0.2);
 1304   dt_bauhaus_slider_set_stop(g->scale_a, 0.5, 1.0, 1.0, 1.0);
 1305   dt_bauhaus_slider_set_stop(g->scale_a, 1.0, 1.0, 0.0, 0.2);
 1306 
 1307   g->scale_b = dt_bauhaus_slider_new_with_range(self, -256.0, 256.0, 1.0, 0.0f, 2);
 1308   gtk_widget_set_tooltip_text(g->scale_b, _("chroma offset blue/yellow"));
 1309   dt_bauhaus_widget_set_label(g->scale_b, NULL, _("blue/yellow"));
 1310   dt_bauhaus_slider_set_stop(g->scale_b, 0.0, 0.0, 0.0, 1.0);
 1311   dt_bauhaus_slider_set_stop(g->scale_b, 0.5, 1.0, 1.0, 1.0);
 1312   dt_bauhaus_slider_set_stop(g->scale_b, 1.0, 1.0, 1.0, 0.0);
 1313 
 1314   g->scale_C = dt_bauhaus_slider_new_with_range(self, -128.0, 128.0, 1.0f, 0.0f, 2);
 1315   gtk_widget_set_tooltip_text(g->scale_C, _("saturation offset"));
 1316   dt_bauhaus_widget_set_label(g->scale_C, NULL, _("saturation"));
 1317 
 1318   g->absolute_target = 0;
 1319   g->combobox_target = dt_bauhaus_combobox_new(self);
 1320   dt_bauhaus_widget_set_label(g->combobox_target, 0, _("target color"));
 1321   gtk_widget_set_tooltip_text(g->combobox_target, _("control target color of the patches via relative offsets or via absolute Lab values"));
 1322   dt_bauhaus_combobox_add(g->combobox_target, _("relative"));
 1323   dt_bauhaus_combobox_add(g->combobox_target, _("absolute"));
 1324 
 1325   gtk_box_pack_start(GTK_BOX(self->widget), g->combobox_patch, TRUE, TRUE, 0);
 1326   gtk_box_pack_start(GTK_BOX(self->widget), g->scale_L, TRUE, TRUE, 0);
 1327   gtk_box_pack_start(GTK_BOX(self->widget), g->scale_a, TRUE, TRUE, 0);
 1328   gtk_box_pack_start(GTK_BOX(self->widget), g->scale_b, TRUE, TRUE, 0);
 1329   gtk_box_pack_start(GTK_BOX(self->widget), g->scale_C, TRUE, TRUE, 0);
 1330   gtk_box_pack_start(GTK_BOX(self->widget), g->combobox_target, TRUE, TRUE, 0);
 1331 
 1332   g_signal_connect(G_OBJECT(g->combobox_patch), "value-changed", G_CALLBACK(patch_callback), self);
 1333   g_signal_connect(G_OBJECT(g->combobox_patch), "quad-pressed", G_CALLBACK(picker_callback), self);
 1334   g_signal_connect(G_OBJECT(g->scale_L), "value-changed", G_CALLBACK(target_L_callback), self);
 1335   g_signal_connect(G_OBJECT(g->scale_a), "value-changed", G_CALLBACK(target_a_callback), self);
 1336   g_signal_connect(G_OBJECT(g->scale_b), "value-changed", G_CALLBACK(target_b_callback), self);
 1337   g_signal_connect(G_OBJECT(g->scale_C), "value-changed", G_CALLBACK(target_C_callback), self);
 1338   g_signal_connect(G_OBJECT(g->combobox_target), "value-changed", G_CALLBACK(target_callback), self);
 1339 
 1340   cmsHPROFILE hsRGB = dt_colorspaces_get_profile(DT_COLORSPACE_SRGB, "", DT_PROFILE_DIRECTION_IN)->profile;
 1341   cmsHPROFILE hLab = dt_colorspaces_get_profile(DT_COLORSPACE_LAB, "", DT_PROFILE_DIRECTION_ANY)->profile;
 1342   g->xform = cmsCreateTransform(hLab, TYPE_Lab_DBL, hsRGB, TYPE_RGB_DBL, INTENT_PERCEPTUAL,
 1343                                 0); // cmsFLAGS_NOTPRECALC);
 1344 }
 1345 
 1346 void gui_cleanup(struct dt_iop_module_t *self)
 1347 {
 1348   dt_iop_colorchecker_gui_data_t *g = (dt_iop_colorchecker_gui_data_t *)self->gui_data;
 1349   cmsDeleteTransform(g->xform);
 1350   free(self->gui_data);
 1351   self->gui_data = NULL;
 1352 }
 1353 
 1354 #undef MAX_PATCHES
 1355 
 1356 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
 1357 // vim: shiftwidth=2 expandtab tabstop=2 cindent
 1358 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;