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