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