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