Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
channelmixerrgb.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2020-2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2020-2022 Chris Elston.
5 Copyright (C) 2020-2022 Diederik Ter Rahe.
6 Copyright (C) 2020 EdgarLux.
7 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
8 Copyright (C) 2020 Heiko Bauke.
9 Copyright (C) 2020-2021 Hubert Kowalski.
10 Copyright (C) 2020-2021 Nicolas Auffray.
11 Copyright (C) 2020-2022 Pascal Obry.
12 Copyright (C) 2020-2021 Ralf Brown.
13 Copyright (C) 2021 luzpaz.
14 Copyright (C) 2021 Marco Carrarini.
15 Copyright (C) 2021 Mark-64.
16 Copyright (C) 2021 Olivier Samyn 🎻.
17 Copyright (C) 2021 Paolo DePetrillo.
18 Copyright (C) 2021 paolodepetrillo.
19 Copyright (C) 2021-2022 Sakari Kapanen.
20 Copyright (C) 2021-2022 Victor Forsiuk.
21 Copyright (C) 2022 Aldric Renaudin.
22 Copyright (C) 2022 Hanno Schwalm.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Philipp Lutz.
25 Copyright (C) 2023-2024 Alynx Zhou.
26 Copyright (C) 2023 Luca Zulberti.
27 Copyright (C) 2025-2026 Guillaume Stutin.
28
29 Ansel is free software: you can redistribute it and/or modify
30 it under the terms of the GNU General Public License as published by
31 the Free Software Foundation, either version 3 of the License, or
32 (at your option) any later version.
33
34 Ansel is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
38
39 You should have received a copy of the GNU General Public License
40 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
41*/
42
43#ifdef HAVE_CONFIG_H
44#include "common/darktable.h"
45#include "config.h"
46#endif
47#include "bauhaus/bauhaus.h"
48#include "chart/common.h"
51#include "common/colorchecker.h"
52#include "common/matrices.h"
54#include "common/illuminants.h"
55#include "common/imagebuf.h"
56#include "common/iop_profile.h"
57#include "common/opencl.h"
58#include "control/control.h"
59#include "develop/imageop_gui.h"
62#include "dtgtk/drawingarea.h"
65#include "gui/gtk.h"
66#include "gui/presets.h"
68#include "iop/iop_api.h"
69
70// Keep the shared implementation in this translation unit to avoid
71// duplicate globals from a separate compiled object.
73
74#include <assert.h>
75#include <float.h>
76#include <gtk/gtk.h>
77#include <glib.h>
78#include <inttypes.h>
79#include <math.h>
80#include <stdlib.h>
81#include <string.h>
82#include <time.h>
83
85
86#define CHANNEL_SIZE 4
87#define INVERSE_SQRT_3 0.5773502691896258f
88#define COLOR_MIN -2.0
89#define COLOR_MAX 2.0
90#define ILLUM_X_MAX 360.0
91#define ILLUM_Y_MAX 300.0
92#define LIGHTNESS_MAX 100.0
93#define HUE_MAX 360.0
94#define CHROMA_MAX 128.0
95#define TEMP_MIN 1667.
96#define TEMP_MAX 25000.
97#define DT_CHANNELMIXERRGB_SIMPLE_MODE_CONF "plugins/darkroom/channelmixerrgb/mixer_mode"
98#define DT_CHANNELMIXERRGB_SIMPLE_TAN_SCALE DT_IOP_CHANNELMIXER_SHARED_SIMPLE_TAN_SCALE
99#define DT_CHANNELMIXERRGB_SIMPLE_EPS DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS
100#define DT_CHANNELMIXERRGB_SIMPLE_CHROMA_PROBE DT_IOP_CHANNELMIXER_SHARED_SIMPLE_CHROMA_PROBE
101
103{
104 CHANNELMIXERRGB_V_1 = 0, // $DESCRIPTION: "version 1 (2020)"
105 CHANNELMIXERRGB_V_2 = 1, // $DESCRIPTION: "version 2 (2021)"
106 CHANNELMIXERRGB_V_3 = 2, // $DESCRIPTION: "version 3 (Apr 2021)"
108
110{
111 /* params of v1 and v2 */
112 float red[CHANNEL_SIZE]; // $MIN: COLOR_MIN $MAX: COLOR_MAX
113 float green[CHANNEL_SIZE]; // $MIN: COLOR_MIN $MAX: COLOR_MAX
114 float blue[CHANNEL_SIZE]; // $MIN: COLOR_MIN $MAX: COLOR_MAX
115 float saturation[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
116 float lightness[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
117 float grey[CHANNEL_SIZE]; // $MIN: -2.0 $MAX: 2.0
118 gboolean normalize_R, normalize_G, normalize_B, normalize_sat, normalize_light, normalize_grey; // $DESCRIPTION: "normalize channels"
119 dt_illuminant_t illuminant; // $DEFAULT: DT_ILLUMINANT_D
120 dt_illuminant_fluo_t illum_fluo; // $DEFAULT: DT_ILLUMINANT_FLUO_F3 $DESCRIPTION: "F source"
121 dt_illuminant_led_t illum_led; // $DEFAULT: DT_ILLUMINANT_LED_B5 $DESCRIPTION: "LED source"
122 dt_adaptation_t adaptation; // $DEFAULT: DT_ADAPTATION_CAT16
123 float x, y; // $DEFAULT: 0.333
124 float temperature; // $MIN: TEMP_MIN $MAX: TEMP_MAX $DEFAULT: 5003.
125 float gamut; // $MIN: 0.0 $MAX: 12.0 $DEFAULT: 1.0 $DESCRIPTION: "gamut compression"
126 gboolean clip; // $DEFAULT: TRUE $DESCRIPTION: "clip negative RGB from gamut"
127
128 /* params of v3 */
129 dt_iop_channelmixer_rgb_version_t version; // $DEFAULT: CHANNELMIXERRGB_V_3 $DESCRIPTION: "saturation algorithm"
130
131 /* always add new params after this so we can import legacy params with memcpy on the common part of the struct */
132
134
135
147
148
155
162
167
168#define DT_CHANNELMIXERRGB_SIMPLE_PROBE_ROTATION DT_IOP_CHANNELMIXER_SHARED_SIMPLE_PROBE_ROTATION
169#define DT_CHANNELMIXERRGB_SIMPLE_PROBE_AXIS_1 DT_IOP_CHANNELMIXER_SHARED_SIMPLE_PROBE_AXIS_1
170#define DT_CHANNELMIXERRGB_SIMPLE_PROBE_AXIS_2 DT_IOP_CHANNELMIXER_SHARED_SIMPLE_PROBE_AXIS_2
171#define DT_CHANNELMIXERRGB_PRIMARIES_BASIS_RGB DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_RGB
172#define DT_CHANNELMIXERRGB_PRIMARIES_BASIS_XYZ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_XYZ
173#define DT_CHANNELMIXERRGB_PRIMARIES_BASIS_BRADFORD DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_BRADFORD
174#define DT_CHANNELMIXERRGB_PRIMARIES_BASIS_CAT16 DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_CAT16
175
177{
178 GtkNotebook *notebook;
193 float xy[2];
194 float XYZ[4];
195
196 point_t box[4]; // the current coordinates, possibly non rectangle, of the bounding box for the color checker
197 point_t ideal_box[4]; // the desired coordinates of the perfect rectangle bounding box for the color checker
198 point_t center_box; // the barycenter of both boxes
199 gboolean active_node[4]; // true if the cursor is close to a node (node = corner of the bounding box)
200 gboolean is_cursor_close; // do we have the cursor close to a node ?
201 gboolean drag_drop; // are we currently dragging and dropping a node ?
202 point_t click_start; // the coordinates where the drag and drop started
203 point_t click_end; // the coordinates where the drag and drop started
207
208 float homography[9]; // the perspective correction matrix
209 float inverse_homography[9]; // The inverse perspective correction matrix
210 gboolean run_profile; // order a profiling at next pipeline recompute
211 gboolean run_validation; // order a profile validation at next pipeline recompute
212 gboolean profile_ready; // notify that a profile is ready to be applied
213 gboolean checker_ready; // notify that a checker bounding box is ready to be used
215
218
219 GList *colorcheckers_all_color; // list of CGATS files
220 GList *colorcheckers_color; // list of CGATS files with the same number of patches as the current checker
222 GtkWidget *checker_msg; // message label
223
226
228
230
233
241
256
265
266
270
271const char *name()
272{
273 return _("color _calibration");
274}
275
276const char *aliases()
277{
278 return _("channel mixer|white balance|monochrome");
279}
280
281const char **description(struct dt_iop_module_t *self)
282{
283 return dt_iop_set_description(self, _("perform color space corrections\n"
284 "such as white balance, channels mixing\n"
285 "and conversions to monochrome emulating film"),
286 _("corrective or creative"),
287 _("linear, RGB, scene-referred"),
288 _("linear, RGB or XYZ"),
289 _("linear, RGB, scene-referred"));
290}
291
296
298{
299 return IOP_GROUP_COLOR;
300}
301
303{
304 return IOP_CS_RGB;
305}
306
309{
310 default_input_format(self, pipe, piece, dsc);
311 dsc->channels = 4;
312 dsc->datatype = TYPE_FLOAT;
313}
314
315int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
316 const int new_version)
317{
318 if(old_version == 1 && new_version == 3)
319 {
320 // V1 and V2 use the same param structure but the normalize_grey param had no effect since commit_params
321 // forced normalization no matter what. So we re-import the params and force the param to TRUE to keep edits.
322 memcpy(new_params, old_params, sizeof(dt_iop_channelmixer_rgb_params_t));
325
326 // V2 and V3 use the same param structure but these :
327
328 // swap the saturation parameters for R and B to put them in natural order
329 const float R = n->saturation[0];
330 const float B = n->saturation[2];
331 n->saturation[0] = B;
332 n->saturation[2] = R;
333
334 // say that these params were created with legacy code
335 n->version = CHANNELMIXERRGB_V_1;
336
337 return 0;
338 }
339 if(old_version == 2 && new_version == 3)
340 {
341 typedef struct dt_iop_channelmixer_rgb_params_v2_t
342 {
343 float red[CHANNEL_SIZE]; // $MIN: -2.0 $MAX: 2.0
344 float green[CHANNEL_SIZE]; // $MIN: -2.0 $MAX: 2.0
345 float blue[CHANNEL_SIZE]; // $MIN: -2.0 $MAX: 2.0
346 float saturation[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
347 float lightness[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
348 float grey[CHANNEL_SIZE]; // $MIN: 0.0 $MAX: 1.0
349 gboolean normalize_R, normalize_G, normalize_B, normalize_sat, normalize_light, normalize_grey; // $DESCRIPTION: "normalize channels"
350 dt_illuminant_t illuminant; // $DEFAULT: DT_ILLUMINANT_D
351 dt_illuminant_fluo_t illum_fluo; // $DEFAULT: DT_ILLUMINANT_FLUO_F3 $DESCRIPTION: "F source"
352 dt_illuminant_led_t illum_led; // $DEFAULT: DT_ILLUMINANT_LED_B5 $DESCRIPTION: "LED source"
353 dt_adaptation_t adaptation; // $DEFAULT: DT_ADAPTATION_LINEAR_BRADFORD
354 float x, y; // $DEFAULT: 0.333
355 float temperature; // $MIN: 1667. $MAX: 25000. $DEFAULT: 5003.
356 float gamut; // $MIN: 0.0 $MAX: 4.0 $DEFAULT: 1.0 $DESCRIPTION: "gamut compression"
357 gboolean clip; // $DEFAULT: TRUE $DESCRIPTION: "clip negative RGB from gamut"
358 } dt_iop_channelmixer_rgb_params_v2_t;
359
360 memcpy(new_params, old_params, sizeof(dt_iop_channelmixer_rgb_params_v2_t));
362
363 // swap the saturation parameters for R and B to put them in natural order
364 const float R = n->saturation[0];
365 const float B = n->saturation[2];
366 n->saturation[0] = B;
367 n->saturation[2] = R;
368
369 // say that these params were created with legacy code
370 n->version = CHANNELMIXERRGB_V_1;
371
372 return 0;
373 }
374 return 1;
375}
376
378{
380 memset(&p, 0, sizeof(p));
381
382 p.version = CHANNELMIXERRGB_V_3;
383
384 // bypass adaptation
385 p.illuminant = DT_ILLUMINANT_PIPE;
386 p.adaptation = DT_ADAPTATION_XYZ;
387
388 // set everything to no-op
389 p.gamut = 0.f;
390 p.clip = FALSE;
391 p.illum_fluo = DT_ILLUMINANT_FLUO_F3;
392 p.illum_led = DT_ILLUMINANT_LED_B5;
393 p.temperature = 5003.f;
395
396 p.red[0] = 1.f;
397 p.red[1] = 0.f;
398 p.red[2] = 0.f;
399 p.green[0] = 0.f;
400 p.green[1] = 1.f;
401 p.green[2] = 0.f;
402 p.blue[0] = 0.f;
403 p.blue[1] = 0.f;
404 p.blue[2] = 1.f;
405
406 p.saturation[0] = 0.f;
407 p.saturation[1] = 0.f;
408 p.saturation[2] = 0.f;
409 p.lightness[0] = 0.f;
410 p.lightness[1] = 0.f;
411 p.lightness[2] = 0.f;
412 p.grey[0] = 0.f;
413 p.grey[1] = 0.f;
414 p.grey[2] = 0.f;
415
416 p.normalize_R = TRUE;
417 p.normalize_G = TRUE;
418 p.normalize_B = TRUE;
419 p.normalize_sat = FALSE;
420 p.normalize_light = FALSE;
421 p.normalize_grey = TRUE;
422
423 // Create B&W presets
424 p.clip = TRUE;
425 p.grey[0] = 0.f;
426 p.grey[1] = 1.f;
427 p.grey[2] = 0.f;
428
429 dt_gui_presets_add_generic(_("B&W: luminance-based"), self->op,
430 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
431
432 // film emulations
433
434 /* These emulations are built using spectral sensitivies provided by film manufacturers for tungsten light,
435 * corrected in spectral domain for D50 illuminant, and integrated in spectral space against CIE 2° 1931 XYZ
436 * color matching functions in the Python lib Colour, with the following code :
437 *
438 import colour
439 import numpy as np
440
441 XYZ = np.zeros((3))
442
443 for l in range(360, 830):
444 XYZ += film_CMF[l] * colour.colorimetry.STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'][l] / colour.ILLUMINANTS_SDS['A'][l] * colour.ILLUMINANTS_SDS['D50'][l]
445
446 XYZ / np.sum(XYZ)
447 *
448 * The film CMF is visually approximated from the graph. It is still more accurate than bullshit factors
449 * in legacy channel mixer that don't even say in which RGB space they are supposed to be applied.
450 */
451
452 // ILFORD HP5 +
453 // https://www.ilfordphoto.com/amfile/file/download/file/1903/product/695/
454 p.grey[0] = 0.25304098f;
455 p.grey[1] = 0.25958747f;
456 p.grey[2] = 0.48737156f;
457
458 dt_gui_presets_add_generic(_("B&W: ILFORD HP5+"), self->op,
459 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
460
461 // ILFORD Delta 100
462 // https://www.ilfordphoto.com/amfile/file/download/file/3/product/681/
463 p.grey[0] = 0.24552374f;
464 p.grey[1] = 0.25366007f;
465 p.grey[2] = 0.50081619f;
466
467 dt_gui_presets_add_generic(_("B&W: ILFORD DELTA 100"), self->op,
468 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
469
470 // ILFORD Delta 400 and 3200 - they have the same curve
471 // https://www.ilfordphoto.com/amfile/file/download/file/1915/product/685/
472 // https://www.ilfordphoto.com/amfile/file/download/file/1913/product/683/
473 p.grey[0] = 0.24376712f;
474 p.grey[1] = 0.23613559f;
475 p.grey[2] = 0.52009729f;
476
477 dt_gui_presets_add_generic(_("B&W: ILFORD DELTA 400 - 3200"), self->op,
478 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
479
480 // ILFORD FP4+
481 // https://www.ilfordphoto.com/amfile/file/download/file/1919/product/690/
482 p.grey[0] = 0.24149085f;
483 p.grey[1] = 0.22149272f;
484 p.grey[2] = 0.53701643f;
485
486 dt_gui_presets_add_generic(_("B&W: ILFORD FP4+"), self->op,
487 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
488
489 // Fuji Acros 100
490 // https://dacnard.wordpress.com/2013/02/15/the-real-shades-of-gray-bw-film-is-a-matter-of-heart-pt-1/
491 p.grey[0] = 0.333f;
492 p.grey[1] = 0.313f;
493 p.grey[2] = 0.353f;
494
495 dt_gui_presets_add_generic(_("B&W: Fuji Acros 100"), self->op,
496 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
497
498 // Kodak ?
499 // can't find spectral sensitivity curves and the illuminant under which they are produced,
500 // so ¯\_(ツ)_/¯
501
502 // basic channel-mixer
503 p.adaptation = DT_ADAPTATION_RGB; // bypass adaptation
504 p.grey[0] = 0.f;
505 p.grey[1] = 0.f;
506 p.grey[2] = 0.f;
507 p.normalize_R = TRUE;
508 p.normalize_G = TRUE;
509 p.normalize_B = TRUE;
510 p.normalize_grey = FALSE;
511 p.clip = FALSE;
512 dt_gui_presets_add_generic(_("basic channel mixer"), self->op,
513 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
514
515 // swap G-B
516 p.red[0] = 1.f;
517 p.red[1] = 0.f;
518 p.red[2] = 0.f;
519 p.green[0] = 0.f;
520 p.green[1] = 0.f;
521 p.green[2] = 1.f;
522 p.blue[0] = 0.f;
523 p.blue[1] = 1.f;
524 p.blue[2] = 0.f;
525 dt_gui_presets_add_generic(_("swap G and B"), self->op,
526 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
527
528 // swap G-R
529 p.red[0] = 0.f;
530 p.red[1] = 1.f;
531 p.red[2] = 0.f;
532 p.green[0] = 1.f;
533 p.green[1] = 0.f;
534 p.green[2] = 0.f;
535 p.blue[0] = 0.f;
536 p.blue[1] = 0.f;
537 p.blue[2] = 1.f;
538 dt_gui_presets_add_generic(_("swap G and R"), self->op,
539 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
540
541 // swap R-B
542 p.red[0] = 0.f;
543 p.red[1] = 0.f;
544 p.red[2] = 1.f;
545 p.green[0] = 0.f;
546 p.green[1] = 1.f;
547 p.green[2] = 0.f;
548 p.blue[0] = 1.f;
549 p.blue[1] = 0.f;
550 p.blue[2] = 0.f;
551 dt_gui_presets_add_generic(_("swap R and B"), self->op,
552 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
553}
554
555
558{
559 // Init output with a no-op
560 for(size_t k = 0; k < 4; k++) custom_wb[k] = 1.f;
561
563 {
564 dt_iop_fmt_log(self, "get_white_balance_coeff: class=%s matrix_supported=0 -> custom_wb=no-op (CAT uses identity)",
566 return 1;
567 }
568
569 // First, get the D65-ish coeffs from the input matrix
570 // keep this in synch with calculate_bogus_daylight_wb from temperature.c !
571 // predicts the bogus D65 that temperature.c will compute for the camera input matrix
572 double bwb[4];
573
575 NULL, NULL,
577 {
578 // normalize green:
579 const double green = bwb[1];
580 bwb[0] /= green;
581 bwb[2] /= green;
582 bwb[1] = 1.0;
583 // bwb[3] is the SECOND green. For non-4-colour sensors the matrix's 4th coefficient is bogus
584 // (often huge or inf, see the temperature.c "usually NAN for RGB" note): both green sites
585 // share the first green, so force it to the green reference instead of propagating garbage.
586 bwb[3] = (self->dev->image_storage.flags & DT_IMAGE_4BAYER) ? bwb[3] / green : 1.0;
587 }
588 else
589 {
590 return 1;
591 }
592
593 // Second, if the temperature module is not using these, for example because they are wrong
594 // and user made a correct preset, find the WB adaptation ratio
595 if(self->dev->proxy.wb_coeffs[0] != 0.f)
596 {
597 for(size_t k = 0; k < 4; k++)
598 {
599 // Guard the second-green ratio: proxy.wb_coeffs[3] may be 0 (non-RGBG sensor), which would
600 // yield inf. Mirror the first-green ratio in that case so custom_wb stays finite.
601 const float denom = self->dev->proxy.wb_coeffs[k];
602 custom_wb[k] = (k == 3 && !isnormal(denom)) ? custom_wb[1] : bwb[k] / denom;
603 }
604 }
605
606 return 0;
607}
608
609
610static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
611gamut_mapping(const dt_aligned_pixel_simd_t input, const float compression, const int clip)
612{
613 // Get the sum XYZ
614 const float sum = input[0] + input[1] + input[2];
615 const float Y = input[1];
616
617 if(sum > 0.f && Y > 0.f)
618 {
619 // Convert to xyY
620 float x = input[0] / sum;
621 float y = input[1] / sum;
622
623 // Convert to uvY
624 const float uv_denominator = -2.f * x + 12.f * y + 3.f;
625 float u = 4.f * x / uv_denominator;
626 float v = 9.f * y / uv_denominator;
627
628 // Get the chromaticity difference with white point uv
629 const float D50[2] DT_ALIGNED_PIXEL = { 0.20915914598542354f, 0.488075320769787f };
630 const float delta[2] DT_ALIGNED_PIXEL = { D50[0] - u, D50[1] - v };
631 const float Delta = Y * (sqf(delta[0]) + sqf(delta[1]));
632
633 // Compress chromaticity (move toward white point)
634 const float correction = (compression == 0.0f) ? 0.f : powf(Delta, compression);
635 // Ensure the correction does not bring our uyY vector the other side of D50
636 // that would switch to the opposite color, so we clip at D50
637 const float tmp_u = DT_FMA(correction, delta[0], u);
638 const float tmp_v = DT_FMA(correction, delta[1], v);
639 u = (u > D50[0]) ? fmaxf(tmp_u, D50[0]) : fminf(tmp_u, D50[0]);
640 v = (v > D50[1]) ? fmaxf(tmp_v, D50[1]) : fminf(tmp_v, D50[1]);
641
642 // Convert back to xyY
643 const float xy_denominator = 6.f * u - 16.f * v + 12.f;
644 x = 9.f * u / xy_denominator;
645 y = 4.f * v / xy_denominator;
646
647 // Clip upon request
648 if(clip)
649 {
650 x = fmaxf(x, 0.0f);
651 y = fmaxf(y, 0.0f);
652 }
653
654 // Check sanity of y
655 // since we later divide by y, it can't be zero
656 y = fmaxf(y, NORM_MIN);
657
658 // Check sanity of x and y :
659 // since Z = Y (1 - x - y) / y, if x + y >= 1, Z will be negative
660 const float scale = x + y;
661 const int sanitize = (scale >= 1.f);
662 if(sanitize)
663 {
664 x /= scale;
665 y /= scale;
666 }
667
668 return (dt_aligned_pixel_simd_t){ Y * x / y, Y, Y * (1.f - x - y) / y, 0.f };
669 }
670 else
671 {
672 // sum of channels == 0, and/or Y == 0 so we have black
673 return (dt_aligned_pixel_simd_t){ 0.f };
674 }
675}
676__OMP_DECLARE_SIMD__(aligned(input, saturation, lightness, output:16) uniform(version))
677static inline __attribute__((always_inline)) void
678luma_chroma(const dt_aligned_pixel_t input, const dt_aligned_pixel_t saturation,
679 const dt_aligned_pixel_t lightness, dt_aligned_pixel_t output,
681{
682 // Compute euclidean norm
683 float norm = euclidean_norm(input);
684 const float avg = fmaxf((input[0] + input[1] + input[2]) / 3.0f, NORM_MIN);
685
686 if(norm > 0.f && avg > 0.f)
687 {
688 // Compute flat lightness adjustment
689 const float mix = scalar_product(input, lightness);
690
691 // Compensate the norm to get color ratios (R, G, B) = (1, 1, 1) for grey (colorless) pixels.
692 if(version == CHANNELMIXERRGB_V_3) norm *= INVERSE_SQRT_3;
693
694 // Ratios
695 for(size_t c = 0; c < 3; c++) output[c] = input[c] / norm;
696
697 // Compute ratios and a flat colorfulness adjustment for the whole pixel
698 float coeff_ratio = 0.f;
699
700 if(version == CHANNELMIXERRGB_V_1)
701 {
702 for(size_t c = 0; c < 3; c++)
703 coeff_ratio += sqf(1.0f - output[c]) * saturation[c];
704 }
705 else
706 coeff_ratio = scalar_product(output, saturation) / 3.f;
707
708 // Adjust the RGB ratios with the pixel correction
709 for(size_t c = 0; c < 3; c++)
710 {
711 // if the ratio was already invalid (negative), we accept the result to be invalid too
712 // otherwise bright saturated blues end up solid black
713 const float min_ratio = (output[c] < 0.0f) ? output[c] : 0.0f;
714 const float output_inverse = 1.0f - output[c];
715 output[c] = fmaxf(DT_FMA(output_inverse, coeff_ratio, output[c]),
716 min_ratio); // output_inverse * coeff_ratio + output
717 }
718
719 // The above interpolation between original pixel ratios and (1, 1, 1) might change the norm of the
720 // ratios. Compensate for that.
721 if(version == CHANNELMIXERRGB_V_3) norm /= euclidean_norm(output) * INVERSE_SQRT_3;
722
723 // Apply colorfulness adjustment channel-wise and repack with lightness to get LMS back
724 norm *= fmaxf(1.f + mix / avg, 0.f);
725 for(size_t c = 0; c < 3; c++) output[c] *= norm;
726 }
727 else
728 {
729 // we have black, 0 stays 0, no luminance = no color
730 for(size_t c = 0; c < 3; c++) output[c] = input[c];
731 }
732}
733
735static inline void loop_switch(const float *const restrict in, float *const restrict out,
736 const size_t width, const size_t height, const size_t ch,
737 const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t RGB_to_XYZ,
739 const dt_aligned_pixel_t saturation, const dt_aligned_pixel_t lightness,
740 const dt_aligned_pixel_t grey, const float p, const float gamut,
741 const int clip, const int apply_grey, const dt_adaptation_t kind,
743{
744 dt_colormatrix_t RGB_to_XYZ_t;
745 dt_colormatrix_t XYZ_to_RGB_t;
746 dt_colormatrix_t MIX_t;
747 transpose_3xSSE(RGB_to_XYZ, RGB_to_XYZ_t);
748 transpose_3xSSE(XYZ_to_RGB, XYZ_to_RGB_t);
749 transpose_3xSSE(MIX, MIX_t);
750 const dt_aligned_pixel_simd_t rgb_to_xyz0 = dt_colormatrix_row_to_simd(RGB_to_XYZ_t, 0);
751 const dt_aligned_pixel_simd_t rgb_to_xyz1 = dt_colormatrix_row_to_simd(RGB_to_XYZ_t, 1);
752 const dt_aligned_pixel_simd_t rgb_to_xyz2 = dt_colormatrix_row_to_simd(RGB_to_XYZ_t, 2);
753 const dt_aligned_pixel_simd_t xyz_to_rgb0 = dt_colormatrix_row_to_simd(XYZ_to_RGB_t, 0);
754 const dt_aligned_pixel_simd_t xyz_to_rgb1 = dt_colormatrix_row_to_simd(XYZ_to_RGB_t, 1);
755 const dt_aligned_pixel_simd_t xyz_to_rgb2 = dt_colormatrix_row_to_simd(XYZ_to_RGB_t, 2);
756 const dt_aligned_pixel_simd_t mix0 = dt_colormatrix_row_to_simd(MIX_t, 0);
757 const dt_aligned_pixel_simd_t mix1 = dt_colormatrix_row_to_simd(MIX_t, 1);
758 const dt_aligned_pixel_simd_t mix2 = dt_colormatrix_row_to_simd(MIX_t, 2);
759 const dt_aligned_pixel_simd_t illuminant_v = dt_load_simd_aligned(illuminant);
761 for(size_t k = 0; k < height * width * 4; k += 4)
762 {
763 const dt_aligned_pixel_simd_t in_v = dt_load_simd_aligned(in + k);
764 dt_aligned_pixel_simd_t temp_one_v = { 0.f };
765 dt_aligned_pixel_simd_t temp_two_v = clip ? dt_simd_max_zero(in_v) : in_v;
766
767 /* WE START IN PIPELINE RGB */
768
769 switch(kind)
770 {
772 {
773 // Convert from RGB to XYZ
774 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
775 const float Y = temp_one_v[1];
776
777 // Convert to LMS
778 temp_two_v = _downscale_vector_simd(convert_XYZ_to_bradford_LMS(temp_one_v), Y);
779 // Do white balance
780 temp_one_v = _upscale_vector_simd(bradford_adapt_D50(temp_two_v, illuminant_v, p, TRUE), Y);
781 // Compute the 3D mix - this is a rotation + homothety of the vector base
782 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, mix0, mix1, mix2);
783 temp_one_v = convert_bradford_LMS_to_XYZ(temp_two_v);
784
785 break;
786 }
788 {
789 // Convert from RGB to XYZ
790 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
791 const float Y = temp_one_v[1];
792
793 // Convert to LMS
794 temp_two_v = _downscale_vector_simd(convert_XYZ_to_bradford_LMS(temp_one_v), Y);
795 // Do white balance
796 temp_one_v = _upscale_vector_simd(bradford_adapt_D50(temp_two_v, illuminant_v, p, FALSE), Y);
797 // Compute the 3D mix - this is a rotation + homothety of the vector base
798 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, mix0, mix1, mix2);
799 temp_one_v = convert_bradford_LMS_to_XYZ(temp_two_v);
800
801 break;
802 }
804 {
805 // Convert from RGB to XYZ
806 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
807 const float Y = temp_one_v[1];
808
809 // Convert to LMS
810 temp_two_v = _downscale_vector_simd(convert_XYZ_to_CAT16_LMS(temp_one_v), Y);
811 // Do white balance
812 temp_one_v = _upscale_vector_simd(CAT16_adapt_D50(temp_two_v, illuminant_v, 1.0f, TRUE), Y);
813 // Compute the 3D mix - this is a rotation + homothety of the vector base
814 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, mix0, mix1, mix2);
815 temp_one_v = convert_CAT16_LMS_to_XYZ(temp_two_v);
816
817 break;
818 }
820 {
821 // Convert from RGB to XYZ
822 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
823 const float Y = temp_one_v[1];
824
825 // Do white balance in XYZ
826 temp_two_v = _upscale_vector_simd(XYZ_adapt_D50(_downscale_vector_simd(temp_one_v, Y), illuminant_v), Y);
827 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, mix0, mix1, mix2);
828
829 break;
830 }
833 default:
834 {
835 // No white balance.
836
837 // Compute the 3D mix in RGB - this is a rotation + homothety of the vector base
838 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, mix0, mix1, mix2);
839 temp_one_v = dt_mat3x4_mul_vec4(temp_one_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
840 break;
841 }
842 }
843
844 /* FROM HERE WE ARE MANDATORILY IN XYZ - DATA IS IN temp_one */
845
846 // Gamut mapping happens in XYZ space no matter what
847 temp_two_v = gamut_mapping(temp_one_v, gamut, clip);
848
849 // convert to LMS, XYZ or pipeline RGB
850 switch(kind)
851 {
856 {
857 temp_one_v = convert_any_XYZ_to_LMS(temp_two_v, kind);
858 break;
859 }
862 default:
863 {
864 // Convert from XYZ to RGB
865 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, xyz_to_rgb0, xyz_to_rgb1, xyz_to_rgb2);
866 break;
867 }
868 }
869
870 /* FROM HERE WE ARE IN LMS, XYZ OR PIPELINE RGB depending on user param - DATA IS IN temp_one */
871
872 // Clip in LMS
873 if(clip) temp_one_v = dt_simd_max_zero(temp_one_v);
874
875 // Apply lightness / saturation adjustment
876 dt_aligned_pixel_t luma_input;
877 dt_aligned_pixel_t luma_output;
878 dt_store_simd_aligned(luma_input, temp_one_v);
879 luma_chroma(luma_input, saturation, lightness, luma_output, version);
880 temp_two_v = dt_load_simd_aligned(luma_output);
881
882 // Clip in LMS
883 if(clip) temp_two_v = dt_simd_max_zero(temp_two_v);
884
885 // Save
886 if(apply_grey)
887 {
888 // Turn LMS, XYZ or pipeline RGB into monochrome
889 const float grey_mix = fmaxf(temp_two_v[0] * grey[0] + temp_two_v[1] * grey[1] + temp_two_v[2] * grey[2], 0.0f);
890 dt_store_simd_nontemporal(out + k, (dt_aligned_pixel_simd_t){ grey_mix, grey_mix, grey_mix, in_v[3] });
891 }
892 else
893 {
894 // Convert back to XYZ
895 switch(kind)
896 {
901 {
902 temp_one_v = convert_any_LMS_to_XYZ(temp_two_v, kind);
903 break;
904 }
907 default:
908 {
909 // Convert from RBG to XYZ
910 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
911 break;
912 }
913 }
914
915 /* FROM HERE WE ARE MANDATORILY IN XYZ - DATA IS IN temp_one */
916
917 // Clip in XYZ
918 if(clip) temp_one_v = dt_simd_max_zero(temp_one_v);
919
920 // Convert back to RGB
921 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, xyz_to_rgb0, xyz_to_rgb1, xyz_to_rgb2);
922 if(clip) temp_two_v = dt_simd_max_zero(temp_two_v);
923
924 dt_store_simd_nontemporal(out + k, (dt_aligned_pixel_simd_t){ temp_two_v[0], temp_two_v[1], temp_two_v[2], in_v[3] });
925 }
926 }
927 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
928}
929
930// util to shift pixel index without headache
931#define SHF(ii, jj, c) ((i + ii) * width + j + jj) * ch + c
932#define OFF 4
933
935static inline int auto_detect_WB(const float *const restrict in, dt_illuminant_t illuminant,
936 const size_t width, const size_t height, const size_t ch,
937 const dt_colormatrix_t RGB_to_XYZ, dt_aligned_pixel_t xyz)
938{
952 float *const restrict temp = dt_pixelpipe_cache_alloc_align_float_cache(width * height * ch, 0);
953 if(IS_NULL_PTR(temp)) return 1;
954
955 // Convert RGB to xy
956 __OMP_PARALLEL_FOR__(collapse(2) )
957 for(size_t i = 0; i < height; i++)
958 for(size_t j = 0; j < width; j++)
959 {
960 const size_t index = (i * width + j) * ch;
963
964 // Clip negatives
965 for_each_channel(c,aligned(in))
966 RGB[c] = fmaxf(in[index + c], 0.0f);
967
968 // Convert to XYZ
969 dot_product(RGB, RGB_to_XYZ, XYZ);
970
971 // Convert to xyY
972 const float sum = fmaxf(XYZ[0] + XYZ[1] + XYZ[2], NORM_MIN);
973 XYZ[0] /= sum; // x
974 XYZ[2] = XYZ[1]; // Y
975 XYZ[1] /= sum; // y
976
977 // Shift the chromaticity plane so the D50 point (target) becomes the origin
978 const float D50[2] = { 0.34567f, 0.35850f };
979 const float norm = dt_fast_hypotf(D50[0], D50[1]);
980
981 temp[index ] = (XYZ[0] - D50[0]) / norm;
982 temp[index + 1] = (XYZ[1] - D50[1]) / norm;
983 temp[index + 2] = XYZ[2];
984 }
985
986 float elements = 0.f;
987 dt_aligned_pixel_t xyY = { 0.f };
988
990 {
991 __OMP_PARALLEL_FOR__(reduction(+:xyY, elements))
992 for(size_t i = 2 * OFF; i < height - 4 * OFF; i += OFF)
993 for(size_t j = 2 * OFF; j < width - 4 * OFF; j += OFF)
994 {
995 float DT_ALIGNED_PIXEL central_average[2];
996
997 for(size_t c = 0; c < 2; c++)
998 {
999 // B-spline local average / blur
1000 central_average[c] = ( temp[SHF(-OFF, -OFF, c)] + 2.f * temp[SHF(-OFF, 0, c)] + temp[SHF(-OFF, +OFF, c)] +
1001 2.f * temp[SHF( 0, -OFF, c)] + 4.f * temp[SHF( 0, 0, c)] + 2.f * temp[SHF( 0, +OFF, c)] +
1002 temp[SHF(+OFF, -OFF, c)] + 2.f * temp[SHF(+OFF, 0, c)] + temp[SHF(+OFF, +OFF, c)]) / 16.0f;
1003 central_average[c] = fmaxf(central_average[c], 0.0f);
1004 }
1005
1006 dt_aligned_pixel_t var = { 0.f };
1007
1008 // compute patch-wise variance
1009 // If variance = 0, we are on a flat surface and want to discard that patch.
1010 for(size_t c = 0; c < 2; c++)
1011 {
1012 var[c] = (
1013 sqf(temp[SHF(-OFF, -OFF, c)] - central_average[c]) +
1014 sqf(temp[SHF(-OFF, 0, c)] - central_average[c]) +
1015 sqf(temp[SHF(-OFF, +OFF, c)] - central_average[c]) +
1016 sqf(temp[SHF(0, -OFF, c)] - central_average[c]) +
1017 sqf(temp[SHF(0, 0, c)] - central_average[c]) +
1018 sqf(temp[SHF(0, +OFF, c)] - central_average[c]) +
1019 sqf(temp[SHF(+OFF, -OFF, c)] - central_average[c]) +
1020 sqf(temp[SHF(+OFF, 0, c)] - central_average[c]) +
1021 sqf(temp[SHF(+OFF, +OFF, c)] - central_average[c])
1022 ) / 9.0f;
1023 }
1024
1025 // Compute the patch-wise chroma covariance.
1026 // If covariance = 0, chroma channels are not correlated and we either have noise or chromatic aberrations.
1027 // Both ways, we want to discard that patch from the chroma average.
1028 var[2] = (
1029 (temp[SHF(-OFF, -OFF, 0)] - central_average[0]) * (temp[SHF(-OFF, -OFF, 1)] - central_average[1]) +
1030 (temp[SHF(-OFF, 0, 0)] - central_average[0]) * (temp[SHF(-OFF, 0, 1)] - central_average[1]) +
1031 (temp[SHF(-OFF, +OFF, 0)] - central_average[0]) * (temp[SHF(-OFF, +OFF, 1)] - central_average[1]) +
1032 (temp[SHF( 0, -OFF, 0)] - central_average[0]) * (temp[SHF( 0, -OFF, 1)] - central_average[1]) +
1033 (temp[SHF( 0, 0, 0)] - central_average[0]) * (temp[SHF( 0, 0, 1)] - central_average[1]) +
1034 (temp[SHF( 0, +OFF, 0)] - central_average[0]) * (temp[SHF( 0, +OFF, 1)] - central_average[1]) +
1035 (temp[SHF(+OFF, -OFF, 0)] - central_average[0]) * (temp[SHF(+OFF, -OFF, 1)] - central_average[1]) +
1036 (temp[SHF(+OFF, 0, 0)] - central_average[0]) * (temp[SHF(+OFF, 0, 1)] - central_average[1]) +
1037 (temp[SHF(+OFF, +OFF, 0)] - central_average[0]) * (temp[SHF(+OFF, +OFF, 1)] - central_average[1])
1038 ) / 9.0f;
1039
1040 // Compute the Minkowski p-norm for regularization
1041 const float p = 8.f;
1042 const float p_norm
1043 = powf(powf(fabsf(central_average[0]), p) + powf(fabsf(central_average[1]), p), 1.f / p) + NORM_MIN;
1044 const float weight = var[0] * var[1] * var[2];
1045
1046 for(size_t c = 0; c < 2; c++) xyY[c] += central_average[c] * weight / p_norm;
1047 elements += weight / p_norm;
1048 }
1049 }
1051 {
1052 __OMP_PARALLEL_FOR__(reduction(+:xyY, elements))
1053 for(size_t i = 2 * OFF; i < height - 4 * OFF; i += OFF)
1054 for(size_t j = 2 * OFF; j < width - 4 * OFF; j += OFF)
1055 {
1056 float DT_ALIGNED_PIXEL dd[2];
1057 float DT_ALIGNED_PIXEL central_average[2];
1058
1059 for(size_t c = 0; c < 2; c++)
1060 {
1061 // B-spline local average / blur
1062 central_average[c] = ( temp[SHF(-OFF, -OFF, c)] + 2.f * temp[SHF(-OFF, 0, c)] + temp[SHF(-OFF, +OFF, c)] +
1063 2.f * temp[SHF( 0, -OFF, c)] + 4.f * temp[SHF( 0, 0, c)] + 2.f * temp[SHF( 0, +OFF, c)] +
1064 temp[SHF(+OFF, -OFF, c)] + 2.f * temp[SHF(+OFF, 0, c)] + temp[SHF(+OFF, +OFF, c)]) / 16.0f;
1065
1066 // image - blur = laplacian = edges
1067 dd[c] = temp[SHF(0, 0, c)] - central_average[c];
1068 }
1069
1070 // Compute the Minkowski p-norm for regularization
1071 const float p = 8.f;
1072 const float p_norm = powf(powf(fabsf(dd[0]), p) + powf(fabsf(dd[1]), p), 1.f / p) + NORM_MIN;
1073
1074 for(size_t c = 0; c < 2; c++) xyY[c] -= dd[c] / p_norm;
1075 elements += 1.f;
1076 }
1077 }
1078
1079 const float D50[2] = { 0.34567f, 0.35850 };
1080 const float norm_D50 = dt_fast_hypotf(D50[0], D50[1]);
1081
1082 for(size_t c = 0; c < 2; c++)
1083 xyz[c] = norm_D50 * (xyY[c] / elements) + D50[c];
1084
1086 return 0;
1087}
1088
1090static void declare_cat_on_pipe(struct dt_iop_module_t *self, gboolean preset)
1091{
1092 // Advertise to the pipeline that we are doing chromatic adaptation here
1093 // preset = TRUE allows to capture the CAT a priori at init time
1095
1096 if((self->enabled && !(p->adaptation == DT_ADAPTATION_RGB || p->illuminant == DT_ILLUMINANT_PIPE)) || preset)
1097 {
1098 // We do CAT here so we need to register this instance as CAT-handler.
1100 {
1101 // We are the first to try to register, let's go !
1102 self->dev->proxy.chroma_adaptation = self;
1103 }
1104 else if(self->dev->proxy.chroma_adaptation == self)
1105 {
1106 }
1107 else
1108 {
1109 // Another instance already registered.
1110 // If we are lower in the pipe than it, register in its place.
1111 if(dt_iop_is_first_instance(self->dev->iop, self))
1112 self->dev->proxy.chroma_adaptation = self;
1113 }
1114 }
1115 else
1116 {
1118 {
1119 // We do NOT do CAT here.
1120 // Deregister this instance as CAT-handler if it previously registered
1121 if(self->dev->proxy.chroma_adaptation == self)
1122 self->dev->proxy.chroma_adaptation = NULL;
1123 }
1124 }
1125}
1126
1127static inline gboolean _is_another_module_cat_on_pipe(struct dt_iop_module_t *self)
1128{
1130 if(IS_NULL_PTR(g)) return FALSE;
1131 return self->dev->proxy.chroma_adaptation && self->dev->proxy.chroma_adaptation != self;
1132}
1133
1134
1135static void update_illuminants(struct dt_iop_module_t *self);
1136static void update_approx_cct(struct dt_iop_module_t *self);
1137static void update_illuminant_color(struct dt_iop_module_t *self);
1138
1139
1140static inline __attribute__((always_inline)) void check_if_close_to_daylight(const float x, const float y, float *temperature,
1142{
1143 /* Check if a chromaticity x, y is close to daylight within 2.5 % error margin.
1144 * If so, we enable the daylight GUI for better ergonomics
1145 * Otherwise, we default to direct x, y control for better accuracy
1146 *
1147 * Note : The use of CCT is discouraged if dE > 5 % in CIE 1960 Yuv space
1148 * reference : https://onlinelibrary.wiley.com/doi/abs/10.1002/9780470175637.ch3
1149 */
1150
1151 // Get the correlated color temperature (CCT)
1152 float t = xy_to_CCT(x, y);
1153
1154 // xy_to_CCT is valid only in 3000 - 25000 K. We need another model below
1155 if(t < 3000.f && t > 1667.f)
1156 t = CCT_reverse_lookup(x, y);
1157
1158 if(!IS_NULL_PTR(temperature))
1159 *temperature = t;
1160
1161 // Convert to CIE 1960 Yuv space
1162 float xy_ref[2] = { x, y };
1163 float uv_ref[2];
1164 xy_to_uv(xy_ref, uv_ref);
1165
1166 float xy_test[2] = { 0.f };
1167 float uv_test[2];
1168
1169 // Compute the test chromaticity from the daylight model
1170 illuminant_to_xy(DT_ILLUMINANT_D, NULL, NULL, &xy_test[0], &xy_test[1], t, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST);
1171 xy_to_uv(xy_test, uv_test);
1172
1173 // Compute the error between the reference illuminant and the test illuminant derivated from the CCT with daylight model
1174 const float delta_daylight = dt_fast_hypotf((uv_test[0] - uv_ref[0]), (uv_test[1] - uv_ref[1]));
1175
1176 // Compute the test chromaticity from the blackbody model
1177 illuminant_to_xy(DT_ILLUMINANT_BB, NULL, NULL, &xy_test[0], &xy_test[1], t, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST);
1178 xy_to_uv(xy_test, uv_test);
1179
1180 // Compute the error between the reference illuminant and the test illuminant derivated from the CCT with black body model
1181 const float delta_bb = dt_fast_hypotf((uv_test[0] - uv_ref[0]), (uv_test[1] - uv_ref[1]));
1182
1183 // Check the error between original and test chromaticity
1184 if(delta_bb < 0.005f || delta_daylight < 0.005f)
1185 {
1186 if(illuminant)
1187 {
1188 if(delta_bb < delta_daylight)
1190 else
1192 }
1193 }
1194 else
1195 {
1196 // error is too big to use a CCT-based model, we fall back to a custom/freestyle chroma selection for the illuminant
1198 }
1199
1200 // CAT16 is more accurate no matter the illuminant
1202}
1203
1204#define DEG_TO_RAD(x) (x * M_PI / 180.f)
1205#define RAD_TO_DEG(x) (x * 180.f / M_PI)
1206
1207static inline void compute_patches_delta_E(const float *const restrict patches,
1208 const dt_color_checker_t *const checker,
1209 float *const restrict delta_E, float *const restrict avg_delta_E, float *const restrict max_delta_E)
1210{
1211 // Compute the delta E
1212
1213 float dE = 0.f;
1214 float max_dE = 0.f;
1215
1216 for(size_t k = 0; k < checker->patches; k++)
1217 {
1218 // Convert to Lab
1219 dt_aligned_pixel_t Lab_test;
1220 dt_aligned_pixel_t XYZ_test;
1221
1222 // If exposure was normalized, denormalized it before
1223 for(size_t c = 0; c < 4; c++) XYZ_test[c] = patches[k * 4 + c];
1224 dt_XYZ_to_Lab(XYZ_test, Lab_test);
1225
1226 const float *const restrict Lab_ref = checker->values[k].Lab;
1227
1228 // Compute delta E 2000 to make your computer heat
1229 // ref: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
1230 // note : it will only be luck if I didn't mess-up the computation somewhere
1231 const float DL = Lab_ref[0] - Lab_test[0];
1232 const float L_avg = (Lab_ref[0] + Lab_test[0]) / 2.f;
1233 const float C_ref = dt_fast_hypotf(Lab_ref[1], Lab_ref[2]);
1234 const float C_test = dt_fast_hypotf(Lab_test[1], Lab_test[2]);
1235 const float C_avg = (C_ref + C_test) / 2.f;
1236 float C_avg_7 = C_avg * C_avg; // C_avg²
1237 C_avg_7 *= C_avg_7; // C_avg⁴
1238 C_avg_7 *= C_avg_7; // C_avg⁸
1239 C_avg_7 /= C_avg; // C_avg⁷
1240 const float C_avg_7_ratio_sqrt = sqrtf(C_avg_7 / (C_avg_7 + 6103515625.f)); // 25⁷ = 6103515625
1241 const float a_ref_prime = Lab_ref[1] * (1.f + 0.5f * (1.f - C_avg_7_ratio_sqrt));
1242 const float a_test_prime = Lab_test[1] * (1.f + 0.5f * (1.f - C_avg_7_ratio_sqrt));
1243 const float C_ref_prime = dt_fast_hypotf(a_ref_prime, Lab_ref[2]);
1244 const float C_test_prime = dt_fast_hypotf(a_test_prime, Lab_test[2]);
1245 const float DC_prime = C_ref_prime - C_test_prime;
1246 const float C_avg_prime = (C_ref_prime + C_test_prime) / 2.f;
1247 float h_ref_prime = atan2f(Lab_ref[2], a_ref_prime);
1248 float h_test_prime = atan2f(Lab_test[2], a_test_prime);
1249
1250 // Comply with recommendations, h = 0° where C = 0 by convention
1251 if(C_ref_prime == 0.f) h_ref_prime = 0.f;
1252 if(C_test_prime == 0.f) h_test_prime = 0.f;
1253
1254 // Get the hue angles from [-pi ; pi] back to [0 ; 2 pi],
1255 // again, to comply with specifications
1256 if(h_ref_prime < 0.f) h_ref_prime = 2.f * M_PI - h_ref_prime;
1257 if(h_test_prime < 0.f) h_test_prime = 2.f * M_PI - h_test_prime;
1258
1259 // Convert to degrees, again to comply with specs
1260 h_ref_prime = RAD_TO_DEG(h_ref_prime);
1261 h_test_prime = RAD_TO_DEG(h_test_prime);
1262
1263 float Dh_prime = h_test_prime - h_ref_prime;
1264 float Dh_prime_abs = fabsf(Dh_prime);
1265 if(C_test_prime == 0.f || C_ref_prime == 0.f)
1266 Dh_prime = 0.f;
1267 else if(Dh_prime_abs <= 180.f)
1268 ;
1269 else if(Dh_prime_abs > 180.f && (h_test_prime <= h_ref_prime))
1270 Dh_prime += 360.f;
1271 else if(Dh_prime_abs > 180.f && (h_test_prime > h_ref_prime))
1272 Dh_prime -= 360.f;
1273
1274 // update abs(Dh_prime) for later
1275 Dh_prime_abs = fabsf(Dh_prime);
1276
1277 const float DH_prime = 2.f * sqrtf(C_test_prime * C_ref_prime) * sinf(DEG_TO_RAD(Dh_prime) / 2.f);
1278 float H_avg_prime = h_ref_prime + h_test_prime;
1279 if(C_test_prime == 0.f || C_ref_prime == 0.f)
1280 ;
1281 else if(Dh_prime_abs <= 180.f)
1282 H_avg_prime /= 2.f;
1283 else if(Dh_prime_abs > 180.f && (H_avg_prime < 360.f))
1284 H_avg_prime = (H_avg_prime + 360.f) / 2.f;
1285 else if(Dh_prime_abs > 180.f && (H_avg_prime >= 360.f))
1286 H_avg_prime = (H_avg_prime - 360.f) / 2.f;
1287
1288 const float T = 1.f
1289 - 0.17f * cosf(DEG_TO_RAD(H_avg_prime) - DEG_TO_RAD(30.f))
1290 + 0.24f * cosf(2.f * DEG_TO_RAD(H_avg_prime))
1291 + 0.32f * cosf(3.f * DEG_TO_RAD(H_avg_prime) + DEG_TO_RAD(6.f))
1292 - 0.20f * cosf(4.f * DEG_TO_RAD(H_avg_prime) - DEG_TO_RAD(63.f));
1293
1294 const float S_L = 1.f + (0.015f * sqf(L_avg - 50.f)) / sqrtf(20.f + sqf(L_avg - 50.f));
1295 const float S_C = 1.f + 0.045f * C_avg_prime;
1296 const float S_H = 1.f + 0.015f * C_avg_prime * T;
1297 const float R_T = -2.f * C_avg_7_ratio_sqrt
1298 * sinf(DEG_TO_RAD(60.f) * expf(-sqf((H_avg_prime - 275.f) / 25.f)));
1299
1300 // roll the drum, here goes the Delta E, finally...
1301 const float DE = sqrtf(sqf(DL / S_L) + sqf(DC_prime / S_C) + sqf(DH_prime / S_H)
1302 + R_T * (DC_prime / S_C) * (DH_prime / S_H));
1303
1304 // Delta E 1976 for reference :
1305 //float DE = sqrtf(sqf(Lab_test[0] - Lab_ref[0]) + sqf(Lab_test[1] - Lab_ref[1]) + sqf(Lab_test[2] - Lab_ref[2]));
1306
1307 //fprintf(stdout, "patch %s : Lab ref \t= \t%.3f \t%.3f \t%.3f \n", checker->values[k].name, Lab_ref[0], Lab_ref[1], Lab_ref[2]);
1308 //fprintf(stdout, "patch %s : Lab mes \t= \t%.3f \t%.3f \t%.3f \n", checker->values[k].name, Lab_test[0], Lab_test[1], Lab_test[2]);
1309 //fprintf(stdout, "patch %s : dE mes \t= \t%.3f \n", checker->values[k].name, DE);
1310
1311 delta_E[k] = DE;
1312 dE += DE / (float)checker->patches;
1313 if(DE > max_dE) max_dE = DE;
1314 }
1315
1316 *avg_delta_E = dE;
1317 *max_delta_E = max_dE;
1318}
1319
1320#define GET_WEIGHT \
1321 float hue = atan2f(reference[2], reference[1]); \
1322 const float chroma = hypotf(reference[2], reference[1]); \
1323 float delta_hue = hue - ref_hue; \
1324 if(chroma == 0.f) \
1325 delta_hue = 0.f; \
1326 else if(fabsf(delta_hue) <= M_PI) \
1327 ; \
1328 else if(fabsf(delta_hue) > M_PI && (hue <= ref_hue)) \
1329 delta_hue += 2.f * M_PI; \
1330 else if(fabsf(delta_hue) > M_PI && (hue > ref_hue)) \
1331 delta_hue -= 2.f * M_PI; \
1332 w = sqrtf(expf(-sqf(delta_hue) / 2.f));
1333
1334
1335typedef struct {
1336 float black;
1339
1341static int _extract_patches(const float *const restrict in, const dt_iop_roi_t *const roi_in,
1343 const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_CAM,
1344 float *const restrict patches,
1345 const gboolean normalize_exposure,
1346 extraction_result_t *result)
1347{
1348 const size_t width = roi_in->width;
1349 const size_t height = roi_in->height;
1350 const float radius_x = g->checker->radius * hypotf(1.f, g->checker->ratio) * g->safety_margin;
1351 const float radius_y = radius_x / g->checker->ratio;
1352
1353 if(IS_NULL_PTR(g->delta_E_in))
1354 {
1355 g->delta_E_in = dt_alloc_align_float(g->checker->patches);
1356 if(IS_NULL_PTR(g->delta_E_in)) return 1;
1357 }
1358
1359 /* Get the average color over each patch */
1360 for(size_t k = 0; k < g->checker->patches; k++)
1361 {
1362 // center of the patch in the ideal reference
1363 const point_t center = { g->checker->values[k].x, g->checker->values[k].y };
1364
1365 // corners of the patch in the ideal reference
1366 const point_t corners[4] = { {center.x - radius_x, center.y - radius_y},
1367 {center.x + radius_x, center.y - radius_y},
1368 {center.x + radius_x, center.y + radius_y},
1369 {center.x - radius_x, center.y + radius_y} };
1370
1371 // apply patch coordinates transform depending on perspective
1372 point_t new_corners[4];
1373 // find the bounding box of the patch at the same time
1374 size_t x_min = width - 1;
1375 size_t x_max = 0;
1376 size_t y_min = height - 1;
1377 size_t y_max = 0;
1378 for(size_t c = 0; c < 4; c++) {
1379 new_corners[c] = apply_homography(corners[c], g->homography);
1380 x_min = fminf(new_corners[c].x, x_min);
1381 x_max = fmaxf(new_corners[c].x, x_max);
1382 y_min = fminf(new_corners[c].y, y_min);
1383 y_max = fmaxf(new_corners[c].y, y_max);
1384 }
1385
1386 x_min = CLAMP((size_t)floorf(x_min), 0, width - 1);
1387 x_max = CLAMP((size_t)ceilf(x_max), 0, width - 1);
1388 y_min = CLAMP((size_t)floorf(y_min), 0, height - 1);
1389 y_max = CLAMP((size_t)ceilf(y_max), 0, height - 1);
1390
1391 // Get the average color on the patch
1392 patches[k * 4] = patches[k * 4 + 1] = patches[k * 4 + 2] = patches[k * 4 + 3] = 0.f;
1393 size_t num_elem = 0;
1394
1395 // Loop through the rectangular bounding box
1396 for(size_t j = y_min; j < y_max; j++)
1397 for(size_t i = x_min; i < x_max; i++)
1398 {
1399 // Check if this pixel lies inside the sampling area and sample if it does
1400 point_t current_point = { i + 0.5f, j + 0.5f };
1401 current_point = apply_homography(current_point, g->inverse_homography);
1402 current_point.x -= center.x;
1403 current_point.y -= center.y;
1404
1405 if(current_point.x < radius_x && current_point.x > -radius_x &&
1406 current_point.y < radius_y && current_point.y > -radius_y)
1407 {
1408 for(size_t c = 0; c < 3; c++)
1409 {
1410 patches[k * 4 + c] += in[(j * width + i) * 4 + c];
1411
1412 // Debug : inpaint a black square in the preview to ensure the coordanites of
1413 // overlay drawings and actual pixel processing match
1414 // out[(j * width + i) * 4 + c] = 0.f;
1415 }
1416 num_elem++;
1417 }
1418 }
1419
1420 for(size_t c = 0; c < 3; c++) patches[k * 4 + c] /= (float)num_elem;
1421
1422 // Convert to XYZ
1423 dt_aligned_pixel_t XYZ = { 0 };
1424 dot_product(patches + k * 4, RGB_to_XYZ, XYZ);
1425 for(size_t c = 0; c < 3; c++) patches[k * 4 + c] = XYZ[c];
1426 }
1427
1428 // find reference white patch
1429 dt_aligned_pixel_t XYZ_white_ref;
1430 dt_Lab_to_XYZ(g->checker->values[g->checker->white].Lab, XYZ_white_ref);
1431 const float white_ref_norm = euclidean_norm(XYZ_white_ref);
1432
1433 // find test white patch
1434 dt_aligned_pixel_t XYZ_white_test;
1435 for(size_t c = 0; c < 3; c++) XYZ_white_test[c] = patches[g->checker->white * 4 + c];
1436 const float white_test_norm = euclidean_norm(XYZ_white_test);
1437
1438 /* match global exposure */
1439 // white exposure depends on camera settings and raw white point,
1440 // we want our profile to be independent from that
1441 float exposure = white_ref_norm / white_test_norm;
1442
1443 /* Exposure compensation */
1444 // Ensure the relative luminance of the test patch (compared to white patch)
1445 // is the same as the relative luminance of the reference patch.
1446 // This compensate for lighting fall-off and unevenness
1447 if(normalize_exposure)
1448 {
1449 for(size_t k = 0; k < g->checker->patches; k++)
1450 {
1451 float *const sample = patches + k * 4;
1452
1453 dt_aligned_pixel_t XYZ_ref;
1454 dt_Lab_to_XYZ(g->checker->values[k].Lab, XYZ_ref);
1455
1456 const float sample_norm = euclidean_norm(sample);
1457 const float ref_norm = euclidean_norm(XYZ_ref);
1458
1459 const float relative_luminance_test = sample_norm / white_test_norm;
1460 const float relative_luminance_ref = ref_norm / white_ref_norm;
1461
1462 const float luma_correction = relative_luminance_ref / relative_luminance_test;
1463 for(size_t c = 0; c < 3; ++c) sample[c] *= luma_correction * exposure;
1464 }
1465 }
1466
1467 // black point is evaluated by rawspeed on each picture using the dark pixels
1468 // we want our profile to be also independent from its discrepancies
1469 // so we convert back the patches to camera RGB space and search the best fit of
1470 // RGB_ref = exposure * (RGB_test - offset) for offset.
1471 float black = 0.f;
1472
1473 if(XYZ_to_CAM)
1474 {
1475 float mean_ref = 0.f;
1476 float mean_test = 0.f;
1477
1478 for(size_t k = 0; k < g->checker->patches; k++)
1479 {
1480 dt_aligned_pixel_t XYZ_ref, RGB_ref;
1481 dt_aligned_pixel_t XYZ_test, RGB_test;
1482
1483 for(size_t c = 0; c < 3; c++) XYZ_test[c] = patches[k * 4 + c];
1484 dt_Lab_to_XYZ(g->checker->values[k].Lab, XYZ_ref);
1485
1486 dot_product(XYZ_test, XYZ_to_CAM, RGB_test);
1487 dot_product(XYZ_ref, XYZ_to_CAM, RGB_ref);
1488
1489 // From now on, we have all the reference and test data in camera RGB space
1490 // where exposure and black level are applied
1491
1492 for(int c = 0; c < 3; c++)
1493 {
1494 mean_test += RGB_test[c];
1495 mean_ref += RGB_ref[c];
1496 }
1497 }
1498 mean_test /= 3.f * g->checker->patches;
1499 mean_ref /= 3.f * g->checker->patches;
1500
1501 float variance = 0.f;
1502 float covariance = 0.f;
1503
1504 for(size_t k = 0; k < g->checker->patches; k++)
1505 {
1506 dt_aligned_pixel_t XYZ_ref, RGB_ref;
1507 dt_aligned_pixel_t XYZ_test, RGB_test;
1508
1509 for(size_t c = 0; c < 3; c++) XYZ_test[c] = patches[k * 4 + c];
1510 dt_Lab_to_XYZ(g->checker->values[k].Lab, XYZ_ref);
1511
1512 dot_product(XYZ_test, XYZ_to_CAM, RGB_test);
1513 dot_product(XYZ_ref, XYZ_to_CAM, RGB_ref);
1514
1515 for(int c = 0; c < 3; c++)
1516 {
1517 variance += sqf(RGB_test[c] - mean_test);
1518 covariance += (RGB_ref[c] - mean_ref) * (RGB_test[c] - mean_ref);
1519 }
1520 }
1521 variance /= 3.f * g->checker->patches;
1522 covariance /= 3.f * g->checker->patches;
1523
1524 // Here, we solve the least-squares problem RGB_ref = exposure * RGB_test + offset
1525 // using :
1526 // exposure = covariance(RGB_test, RGB_ref) / variance(RGB_test)
1527 // offset = mean(RGB_ref) - exposure * mean(RGB_test)
1528 exposure = covariance / variance;
1529 black = mean_ref - exposure * mean_test;
1530 }
1531
1532 // the exposure module applies output = (input - offset) * exposure
1533 // but we compute output = input * exposure + offset
1534 // so, rescale offset to adapt our offset to exposure module GUI
1535 black /= -exposure;
1536
1537 result->black = black;
1538 result->exposure = exposure;
1539 return 0;
1540}
1541
1543int extract_color_checker(const float *const restrict in, float *const restrict out,
1545 const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_RGB,
1546 const dt_colormatrix_t XYZ_to_CAM,
1547 const dt_adaptation_t kind)
1548{
1549 float *const restrict patches = dt_alloc_align_float(g->checker->patches * 4);
1550 if(IS_NULL_PTR(patches)) return 1;
1551
1552 dt_simd_memcpy(in, out, (size_t)roi_in->width * roi_in->height * 4);
1553
1554 extraction_result_t extraction_result = { 0 };
1555 if(_extract_patches(out, roi_in, g, RGB_to_XYZ, XYZ_to_CAM, patches, TRUE, &extraction_result))
1556 {
1557 dt_free_align(patches);
1558 return 1;
1559 }
1560
1561 // Compute the delta E
1562 float pre_wb_delta_E = 0.f;
1563 float pre_wb_max_delta_E = 0.f;
1564 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &pre_wb_delta_E, &pre_wb_max_delta_E);
1565
1566 /* find the scene illuminant */
1567
1568 // find reference grey patch
1569 dt_aligned_pixel_t XYZ_grey_ref;
1570 dt_Lab_to_XYZ(g->checker->values[g->checker->middle_grey].Lab, XYZ_grey_ref);
1571
1572 // find test grey patch
1573 dt_aligned_pixel_t XYZ_grey_test;
1574 for(size_t c = 0; c < 3; c++) XYZ_grey_test[c] = patches[g->checker->middle_grey * 4 + c];
1575
1576 // compute reference illuminant
1577 dt_aligned_pixel_t D50_XYZ;
1578 illuminant_xy_to_XYZ(0.34567f, 0.35850f, D50_XYZ);
1579
1580 // normalize luminances - note : illuminant is normalized by definition
1581 const float Y_test = XYZ_grey_test[1];
1582 const float Y_ref = XYZ_grey_ref[1];
1583 for(size_t c = 0; c < 3; c++)
1584 {
1585 XYZ_grey_ref[c] /= Y_ref;
1586 XYZ_grey_test[c] /= Y_test;
1587 }
1588
1589 // convert XYZ to LMS
1590 dt_aligned_pixel_t LMS_grey_ref, LMS_grey_test, D50_LMS;
1591 dt_store_simd_aligned(LMS_grey_ref, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_grey_ref), kind));
1592 dt_store_simd_aligned(LMS_grey_test, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_grey_test), kind));
1593 dt_store_simd_aligned(D50_LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(D50_XYZ), kind));
1594
1595 // solve the equation to find the scene illuminant
1596 dt_aligned_pixel_t illuminant = { 0.0f };
1597 for(size_t c = 0; c < 3; c++) illuminant[c] = D50_LMS[c] * LMS_grey_test[c] / LMS_grey_ref[c];
1598
1599 // convert back the illuminant to XYZ then xyY
1600 dt_aligned_pixel_t illuminant_XYZ, illuminant_xyY = { .0f };
1601 dt_store_simd_aligned(illuminant_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(illuminant), kind));
1602 const float Y_illu = illuminant_XYZ[1];
1603 for(size_t c = 0; c < 3; c++) illuminant_XYZ[c] /= Y_illu;
1604 dt_XYZ_to_xyY(illuminant_XYZ, illuminant_xyY);
1605
1606 // save the illuminant in GUI struct for commit
1607 g->xy[0] = illuminant_xyY[0];
1608 g->xy[1] = illuminant_xyY[1];
1609
1610 // and recompute back the LMS to be sure we use the parameters that will be computed later
1611 illuminant_xy_to_XYZ(illuminant_xyY[0], illuminant_xyY[1], illuminant_XYZ);
1612 dt_store_simd_aligned(illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(illuminant_XYZ), kind));
1613 const float p = powf(0.818155f / illuminant[2], 0.0834f);
1614
1615 /* White-balance the patches */
1616 for(size_t k = 0; k < g->checker->patches; k++)
1617 {
1618 // keep in synch with loop_switch() from process()
1619 float *const sample = patches + k * 4;
1620 const float Y = sample[1];
1621 downscale_vector(sample, Y);
1622
1624 dt_store_simd_aligned(LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(sample), kind));
1625
1626 dt_aligned_pixel_t temp;
1627 const dt_aligned_pixel_simd_t LMS_v = dt_load_simd_aligned(LMS);
1628 const dt_aligned_pixel_simd_t illuminant_v = dt_load_simd_aligned(illuminant);
1629
1630 switch(kind)
1631 {
1633 {
1634 dt_store_simd_aligned(temp, bradford_adapt_D50(LMS_v, illuminant_v, p, TRUE));
1635 break;
1636 }
1638 {
1639 dt_store_simd_aligned(temp, bradford_adapt_D50(LMS_v, illuminant_v, 1.f, FALSE));
1640 break;
1641 }
1643 {
1644 dt_store_simd_aligned(temp, CAT16_adapt_D50(LMS_v, illuminant_v, 1.f, TRUE)); // force full-adaptation
1645 break;
1646 }
1647 case DT_ADAPTATION_XYZ:
1648 {
1649 dt_store_simd_aligned(temp, XYZ_adapt_D50(LMS_v, illuminant_v));
1650 break;
1651 }
1652 case DT_ADAPTATION_RGB:
1653 case DT_ADAPTATION_LAST:
1654 default:
1655 {
1656 // No white balance.
1657 for(size_t c = 0; c < 3; ++c) temp[c] = LMS[c];
1658 break;
1659 }
1660 }
1661
1662 dt_store_simd_aligned(sample, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), kind));
1663 upscale_vector(sample, Y);
1664 }
1665
1666 // Compute the delta E
1667 float post_wb_delta_E = 0.f;
1668 float post_wb_max_delta_E = 0.f;
1669 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &post_wb_delta_E, &post_wb_max_delta_E);
1670
1671 /* Compute the matrix of mix */
1672 double *const restrict Y = dt_alloc_align(g->checker->patches * 3 * sizeof(double));
1673 double *const restrict A = dt_alloc_align(g->checker->patches * 3 * 9 * sizeof(double));
1674 if(IS_NULL_PTR(Y) || IS_NULL_PTR(A))
1675 {
1676 dt_free_align(Y);
1678 dt_free_align(patches);
1679 return 1;
1680 }
1681
1682 for(size_t k = 0; k < g->checker->patches; k++)
1683 {
1684 float *const sample = patches + k * 4;
1685 dt_aligned_pixel_t LMS_test;
1686 dt_store_simd_aligned(LMS_test, convert_any_XYZ_to_LMS(dt_load_simd_aligned(sample), kind));
1687
1688 const float *const reference = g->checker->values[k].Lab;
1689 dt_aligned_pixel_t XYZ_ref, LMS_ref;
1690 dt_Lab_to_XYZ(reference, XYZ_ref);
1691 dt_store_simd_aligned(LMS_ref, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_ref), kind));
1692
1693 // get the optimization weights
1694 float w = 1.f;
1695 if(g->optimization == DT_SOLVE_OPTIMIZE_NONE)
1696 w = sqrtf(1.f / (float)g->checker->patches);
1697 else if(g->optimization == DT_SOLVE_OPTIMIZE_HIGH_SAT)
1698 w = sqrtf(hypotf(reference[1] / 128.f, reference[2] / 128.f));
1699 else if(g->optimization == DT_SOLVE_OPTIMIZE_LOW_SAT)
1700 w = sqrtf(1.f - hypotf(reference[1] / 128.f, reference[2] / 128.f));
1701 else if(g->optimization == DT_SOLVE_OPTIMIZE_SKIN)
1702 {
1703 // average skin hue angle is 1.0 rad, hue range is [0.75 ; 1.25]
1704 const float ref_hue = 1.f;
1705 GET_WEIGHT;
1706 }
1707 else if(g->optimization == DT_SOLVE_OPTIMIZE_FOLIAGE)
1708 {
1709 // average foliage hue angle is 2.23 rad, hue range is [1.94 ; 2.44]
1710 const float ref_hue = 2.23f;
1711 GET_WEIGHT;
1712 }
1713 else if(g->optimization == DT_SOLVE_OPTIMIZE_SKY)
1714 {
1715 // average sky/water hue angle is -1.93 rad, hue range is [-1.64 ; -2.41]
1716 const float ref_hue = -1.93f;
1717 GET_WEIGHT;
1718 }
1719 else if(g->optimization == DT_SOLVE_OPTIMIZE_AVG_DELTA_E)
1720 w = sqrtf(sqrtf(1.f / g->delta_E_in[k]));
1721 else if(g->optimization == DT_SOLVE_OPTIMIZE_MAX_DELTA_E)
1722 w = sqrtf(sqrtf(g->delta_E_in[k]));
1723
1724 // fill 3 rows of the y column vector
1725 for(size_t c = 0; c < 3; c++) Y[k * 3 + c] = w * LMS_ref[c];
1726
1727 // fill line one of the A matrix
1728 A[k * 3 * 9 + 0] = w * LMS_test[0];
1729 A[k * 3 * 9 + 1] = w * LMS_test[1];
1730 A[k * 3 * 9 + 2] = w * LMS_test[2];
1731 A[k * 3 * 9 + 3] = 0.f;
1732 A[k * 3 * 9 + 4] = 0.f;
1733 A[k * 3 * 9 + 5] = 0.f;
1734 A[k * 3 * 9 + 6] = 0.f;
1735 A[k * 3 * 9 + 7] = 0.f;
1736 A[k * 3 * 9 + 8] = 0.f;
1737
1738 // fill line two of the A matrix
1739 A[k * 3 * 9 + 9 + 0] = 0.f;
1740 A[k * 3 * 9 + 9 + 1] = 0.f;
1741 A[k * 3 * 9 + 9 + 2] = 0.f;
1742 A[k * 3 * 9 + 9 + 3] = w * LMS_test[0];
1743 A[k * 3 * 9 + 9 + 4] = w * LMS_test[1];
1744 A[k * 3 * 9 + 9 + 5] = w * LMS_test[2];
1745 A[k * 3 * 9 + 9 + 6] = 0.f;
1746 A[k * 3 * 9 + 9 + 7] = 0.f;
1747 A[k * 3 * 9 + 9 + 8] = 0.f;
1748
1749 // fill line three of the A matrix
1750 A[k * 3 * 9 + 18 + 0] = 0.f;
1751 A[k * 3 * 9 + 18 + 1] = 0.f;
1752 A[k * 3 * 9 + 18 + 2] = 0.f;
1753 A[k * 3 * 9 + 18 + 3] = 0.f;
1754 A[k * 3 * 9 + 18 + 4] = 0.f;
1755 A[k * 3 * 9 + 18 + 5] = 0.f;
1756 A[k * 3 * 9 + 18 + 6] = w * LMS_test[0];
1757 A[k * 3 * 9 + 18 + 7] = w * LMS_test[1];
1758 A[k * 3 * 9 + 18 + 8] = w * LMS_test[2];
1759 }
1760
1761 if(pseudo_solve_gaussian(A, Y, g->checker->patches * 3, 9, TRUE) != 0)
1762 {
1763 dt_free_align(Y);
1765 dt_free_align(patches);
1766 return 1;
1767 }
1768
1769 // repack the matrix
1771
1772 dt_free_align(Y);
1774
1775 // apply the matrix mix
1776 for(size_t k = 0; k < g->checker->patches; k++)
1777 {
1778 float *const sample = patches + k * 4;
1779 dt_aligned_pixel_t LMS_test;
1780 dt_aligned_pixel_t temp = { 0.f };
1781
1782 // Restore the original exposure of the patch
1783 for(size_t c = 0; c < 3; c++) temp[c] = sample[c];
1784
1785 dt_store_simd_aligned(LMS_test, convert_any_XYZ_to_LMS(dt_load_simd_aligned(temp), kind));
1786 dot_product(LMS_test, g->mix, temp);
1787 dt_store_simd_aligned(sample, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), kind));
1788 }
1789
1790 // Compute the delta E
1791 float post_mix_delta_E = 0.f;
1792 float post_mix_max_delta_E = 0.f;
1793 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &post_mix_delta_E, &post_mix_max_delta_E);
1794
1795 // get the temperature
1796 float temperature;
1797 dt_illuminant_t test_illuminant;
1798 float x = illuminant_xyY[0];
1799 float y = illuminant_xyY[1];
1800 check_if_close_to_daylight(x, y, &temperature, &test_illuminant, NULL);
1801 gchar *string;
1802 if(test_illuminant == DT_ILLUMINANT_D)
1803 string = _("(daylight)");
1804 else if(test_illuminant == DT_ILLUMINANT_BB)
1805 string = _("(black body)");
1806 else
1807 string = _("(invalid)");
1808
1809 gchar *diagnostic;
1810 if(post_mix_delta_E <= 1.2f)
1811 diagnostic = _("very good");
1812 else if(post_mix_delta_E <= 2.3f)
1813 diagnostic = _("good");
1814 else if(post_mix_delta_E <= 3.4f)
1815 diagnostic = _("passable");
1816 else
1817 diagnostic = _("bad");
1818
1819 g->profile_ready = TRUE;
1820
1821 // Update GUI label
1822 dt_free(g->delta_E_label_text);
1823 g->delta_E_label_text
1824 = g_strdup_printf(_("\n<b>Profile quality report: %s</b>\n"
1825 "input \316\224E: \tavg. %.2f ; \tmax. %.2f\n"
1826 "WB \316\224E: \tavg. %.2f; \tmax. %.2f\n"
1827 "output \316\224E: \tavg. %.2f; \tmax. %.2f\n\n"
1828 "<b>Profile data</b>\n"
1829 "illuminant: \t%.0f K \t%s\n"
1830 "matrix in adaptation space:\n"
1831 "<tt>%+.4f \t%+.4f \t%+.4f\n"
1832 "%+.4f \t%+.4f \t%+.4f\n"
1833 "%+.4f \t%+.4f \t%+.4f</tt>\n\n"
1834 "<b>Normalization values</b>\n"
1835 "exposure compensation: \t%+.2f EV\n"
1836 "black offset: \t%+.4f"
1837 ),
1838 diagnostic, pre_wb_delta_E, pre_wb_max_delta_E, post_wb_delta_E, post_wb_max_delta_E,
1839 post_mix_delta_E, post_mix_max_delta_E, temperature, string, g->mix[0][0], g->mix[0][1],
1840 g->mix[0][2], g->mix[1][0], g->mix[1][1], g->mix[1][2], g->mix[2][0], g->mix[2][1],
1841 g->mix[2][2], log2f(extraction_result.exposure), extraction_result.black);
1842
1843 dt_free_align(patches);
1844 return 0;
1845}
1846
1847int validate_color_checker(const float *const restrict in,
1849 const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t XYZ_to_CAM)
1850{
1851 float *const restrict patches = dt_alloc_align_float(4 * g->checker->patches);
1852 if(IS_NULL_PTR(patches)) return 1;
1853 extraction_result_t extraction_result = { 0 };
1854 if(_extract_patches(in, roi_in, g, RGB_to_XYZ, XYZ_to_CAM, patches, FALSE, &extraction_result))
1855 {
1856 dt_free_align(patches);
1857 return 1;
1858 }
1859
1860 // Compute the delta E
1861 float pre_wb_delta_E = 0.f;
1862 float pre_wb_max_delta_E = 0.f;
1863 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &pre_wb_delta_E, &pre_wb_max_delta_E);
1864
1865 gchar *diagnostic;
1866 if(pre_wb_delta_E <= 1.2f)
1867 diagnostic = _("very good");
1868 else if(pre_wb_delta_E <= 2.3f)
1869 diagnostic = _("good");
1870 else if(pre_wb_delta_E <= 3.4f)
1871 diagnostic = _("passable");
1872 else
1873 diagnostic = _("bad");
1874
1875 // Update GUI label
1876 dt_free(g->delta_E_label_text);
1877 g->delta_E_label_text = g_strdup_printf(_("\n<b>Profile quality report: %s</b>\n"
1878 "output \316\224E: \tavg. %.2f; \tmax. %.2f\n\n"
1879 "<b>Normalization values</b>\n"
1880 "exposure compensation: \t%+.2f EV\n"
1881 "black offset: \t%+.4f"),
1882 diagnostic, pre_wb_delta_E, pre_wb_max_delta_E, log2f(extraction_result.exposure),
1883 extraction_result.black);
1884
1885 dt_free_align(patches);
1886 return 0;
1887}
1888
1889int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1890 const void *const restrict ivoid, void *const restrict ovoid)
1891{
1892 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1893 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1895 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
1896 const struct dt_iop_order_iccprofile_info_t *const input_profile = dt_ioppr_get_pipe_input_profile_info(pipe);
1898
1900
1901 dt_colormatrix_t RGB_to_XYZ;
1902 dt_colormatrix_t XYZ_to_RGB;
1903 dt_colormatrix_t XYZ_to_CAM;
1904
1905 // repack the matrices as flat AVX2-compliant matrice
1906 if(!IS_NULL_PTR(work_profile))
1907 {
1908 // work profile can't be fetched in commit_params since it is not yet initialised
1909 memcpy(RGB_to_XYZ, work_profile->matrix_in, sizeof(RGB_to_XYZ));
1910 memcpy(XYZ_to_RGB, work_profile->matrix_out, sizeof(XYZ_to_RGB));
1911 memcpy(XYZ_to_CAM, input_profile->matrix_out, sizeof(XYZ_to_CAM));
1912 }
1913
1914 const size_t ch = 4;
1915
1916 const float *const restrict in = (const float *const restrict)ivoid;
1917 float *const restrict out = (float *const restrict)ovoid;
1918
1919 // auto-detect WB upon request
1920 if(self->dev->gui_attached && g)
1921 {
1922 gboolean exit = FALSE;
1923
1924 if(g->run_profile && pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
1925 {
1927 const int err = extract_color_checker(in, out, roi_in, g, RGB_to_XYZ, XYZ_to_RGB, XYZ_to_CAM, data->adaptation);
1928 g->run_profile = FALSE;
1931 if(err) return 1;
1932 }
1933
1935 {
1936 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
1937 {
1938 // detection on full image only
1940 const int err = auto_detect_WB(in, data->illuminant_type, roi_in->width, roi_in->height, ch, RGB_to_XYZ, g->XYZ);
1942 if(err) return 1;
1943 }
1944
1945 // passthrough pixels
1946 dt_iop_image_copy_by_size(out, in, roi_in->width, roi_in->height, ch);
1947
1948 dt_control_log(_("auto-detection of white balance completed"));
1949
1950 exit = TRUE;
1951 }
1952
1953 if(exit) return 0;
1954 }
1955
1957 {
1958 // The camera illuminant is a behaviour rather than a preset of values:
1959 // it uses whatever is in the RAW EXIF. But it depends on what temperature.c is doing
1960 // and needs to be updated accordingly, to give a consistent result.
1961 // We initialise the CAT defaults using the temperature coeffs at startup, but if temperature
1962 // is changed later, we get no notification of the change here, so we can't update the defaults.
1963 // So we need to re-run the detection at runtime...
1964 float x, y;
1965 dt_aligned_pixel_t custom_wb;
1966 get_white_balance_coeff(self, custom_wb);
1967
1968 if(find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(x), &(y)))
1969 {
1970 // Convert illuminant from xyY to XYZ
1973
1974 // Convert illuminant from XYZ to Bradford modified LMS
1975 dt_store_simd_aligned(data->illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), data->adaptation));
1976 data->illuminant[3] = 0.f;
1977 }
1978 else
1979 {
1980 // just use whatever was defined in commit_params hoping the defaults work...
1981 }
1982 }
1983
1984 // force loop unswitching in a controlled way
1985 switch(data->adaptation)
1986 {
1988 {
1989 loop_switch(in, out, roi_out->width, roi_out->height, ch,
1990 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
1991 data->illuminant, data->saturation, data->lightness, data->grey,
1992 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_FULL_BRADFORD, data->version);
1993 break;
1994 }
1996 {
1997 loop_switch(in, out, roi_out->width, roi_out->height, ch,
1998 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
1999 data->illuminant, data->saturation, data->lightness, data->grey,
2000 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_LINEAR_BRADFORD, data->version);
2001 break;
2002 }
2004 {
2005 loop_switch(in, out, roi_out->width, roi_out->height, ch,
2006 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
2007 data->illuminant, data->saturation, data->lightness, data->grey,
2008 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_CAT16, data->version);
2009 break;
2010 }
2011 case DT_ADAPTATION_XYZ:
2012 {
2013 loop_switch(in, out, roi_out->width, roi_out->height, ch,
2014 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
2015 data->illuminant, data->saturation, data->lightness, data->grey,
2016 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_XYZ, data->version);
2017 break;
2018 }
2019 case DT_ADAPTATION_RGB:
2020 {
2021 loop_switch(in, out, roi_out->width, roi_out->height, ch,
2022 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
2023 data->illuminant, data->saturation, data->lightness, data->grey,
2024 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_RGB, data->version);
2025 break;
2026 }
2027 case DT_ADAPTATION_LAST:
2028 default:
2029 {
2030 break;
2031 }
2032 }
2033
2034 // run dE validation at output
2035 if(self->dev->gui_attached && g)
2036 if(g->run_validation && pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
2037 {
2038 const int err = validate_color_checker(out, roi_out, g, RGB_to_XYZ, XYZ_to_RGB, XYZ_to_CAM);
2039 g->run_validation = FALSE;
2040 if(err) return 1;
2041 }
2042
2043 return 0;
2044}
2045
2046#if HAVE_OPENCL
2047int 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)
2048{
2049 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2052 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
2053 //dt_iop_channelmixer_rgb_gui_data_t *g = (dt_iop_channelmixer_rgb_gui_data_t *)self->gui_data;
2054
2056
2057 if(d->illuminant_type == DT_ILLUMINANT_CAMERA)
2058 {
2059 // The camera illuminant is a behaviour rather than a preset of values:
2060 // it uses whatever is in the RAW EXIF. But it depends on what temperature.c is doing
2061 // and needs to be updated accordingly, to give a consistent result.
2062 // We initialise the CAT defaults using the temperature coeffs at startup, but if temperature
2063 // is changed later, we get no notification of the change here, so we can't update the defaults.
2064 // So we need to re-run the detection at runtime...
2065 float x, y;
2066 dt_aligned_pixel_t custom_wb;
2067 get_white_balance_coeff(self, custom_wb);
2068
2069 if(find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(x), &(y)))
2070 {
2071 // Convert illuminant from xyY to XYZ
2074
2075 // Convert illuminant from XYZ to Bradford modified LMS
2076 dt_store_simd_aligned(d->illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), d->adaptation));
2077 d->illuminant[3] = 0.f;
2078 }
2079 }
2080
2081 cl_int err = -999;
2082
2083 const int devid = pipe->devid;
2084 const int width = roi_in->width;
2085 const int height = roi_in->height;
2086
2087 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2088
2089 cl_mem input_matrix_cl = NULL;
2090 cl_mem output_matrix_cl = NULL;
2091
2092 float input_matrix_3x4[12];
2093 float output_matrix_3x4[12];
2094 float mix_matrix_3x4[12];
2095 pack_3xSSE_to_3x4(work_profile->matrix_in, input_matrix_3x4);
2096 pack_3xSSE_to_3x4(work_profile->matrix_out, output_matrix_3x4);
2097 pack_3xSSE_to_3x4(d->MIX, mix_matrix_3x4);
2098
2099 input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(input_matrix_3x4), input_matrix_3x4);
2100 output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(output_matrix_3x4), output_matrix_3x4);
2101 cl_mem MIX_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(mix_matrix_3x4), mix_matrix_3x4);
2102
2103 // select the right kernel for the current LMS space
2105
2106 switch(d->adaptation)
2107 {
2109 {
2111 break;
2112 }
2114 {
2116 break;
2117 }
2119 {
2121 break;
2122 }
2123 case DT_ADAPTATION_XYZ:
2124 {
2126 break;
2127 }
2128 case DT_ADAPTATION_RGB:
2129 case DT_ADAPTATION_LAST:
2130 default:
2131 {
2133 break;
2134 }
2135 }
2136
2137 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
2138 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
2139 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
2140 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
2141 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&input_matrix_cl);
2142 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), (void *)&output_matrix_cl);
2143 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), (void *)&MIX_cl);
2144 dt_opencl_set_kernel_arg(devid, kernel, 7, 4 * sizeof(float), (void *)&d->illuminant);
2145 dt_opencl_set_kernel_arg(devid, kernel, 8, 4 * sizeof(float), (void *)&d->saturation);
2146 dt_opencl_set_kernel_arg(devid, kernel, 9, 4 * sizeof(float), (void *)&d->lightness);
2147 dt_opencl_set_kernel_arg(devid, kernel, 10, 4 * sizeof(float), (void *)&d->grey);
2148 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(float), (void *)&d->p);
2149 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(float), (void *)&d->gamut);
2150 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(int), (void *)&d->clip);
2151 dt_opencl_set_kernel_arg(devid, kernel, 14, sizeof(int), (void *)&d->apply_grey);
2152 dt_opencl_set_kernel_arg(devid, kernel, 15, sizeof(int), (void *)&d->version);
2153 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
2154 if(err != CL_SUCCESS) goto error;
2155
2156 dt_opencl_release_mem_object(input_matrix_cl);
2157 dt_opencl_release_mem_object(output_matrix_cl);
2159 return TRUE;
2160
2161error:
2162 dt_opencl_release_mem_object(input_matrix_cl);
2163 dt_opencl_release_mem_object(output_matrix_cl);
2165 dt_print(DT_DEBUG_OPENCL, "[opencl_channelmixerrgb] couldn't enqueue kernel! %d\n", err);
2166 return FALSE;
2167}
2168
2170{
2171 const int program = 32; // extended.cl in programs.conf
2174
2175 module->data = gd;
2176 gd->kernel_channelmixer_rgb_cat16 = dt_opencl_create_kernel(program, "channelmixerrgb_CAT16");
2177 gd->kernel_channelmixer_rgb_bradford_full = dt_opencl_create_kernel(program, "channelmixerrgb_bradford_full");
2178 gd->kernel_channelmixer_rgb_bradford_linear = dt_opencl_create_kernel(program, "channelmixerrgb_bradford_linear");
2179 gd->kernel_channelmixer_rgb_xyz = dt_opencl_create_kernel(program, "channelmixerrgb_XYZ");
2180 gd->kernel_channelmixer_rgb_rgb = dt_opencl_create_kernel(program, "channelmixerrgb_RGB");
2181}
2182
2183
2194#endif
2195
2196
2198 const float x_increment, const float y_increment)
2199{
2200 // update box nodes
2201 for(size_t k = 0; k < 4; k++)
2202 {
2203 if(g->active_node[k])
2204 {
2205 g->box[k].x += x_increment;
2206 g->box[k].y += y_increment;
2207 }
2208 }
2209
2210 // update the homography
2211 get_homography(g->ideal_box, g->box, g->homography);
2212 get_homography(g->box, g->ideal_box, g->inverse_homography);
2213}
2214
2215static inline void init_bounding_box(dt_iop_channelmixer_rgb_gui_data_t *g, const float width, const float height)
2216{
2217 if(g->checker && !g->checker_ready)
2218 {
2219 // top left
2220 g->box[0].x = g->box[0].y = 10.;
2221
2222 // top right
2223 g->box[1].x = (width - 10.);
2224 g->box[1].y = g->box[0].y;
2225
2226 // bottom right
2227 g->box[2].x = g->box[1].x;
2228 g->box[2].y = (width - 10.) * g->checker->ratio;
2229
2230 // bottom left
2231 g->box[3].x = g->box[0].x;
2232 g->box[3].y = g->box[2].y;
2233
2234 g->checker_ready = TRUE;
2235 }
2236
2237 g->center_box.x = 0.5f;
2238 g->center_box.y = 0.5f;
2239
2240 g->ideal_box[0].x = 0.f;
2241 g->ideal_box[0].y = 0.f;
2242 g->ideal_box[1].x = 1.f;
2243 g->ideal_box[1].y = 0.f;
2244 g->ideal_box[2].x = 1.f;
2245 g->ideal_box[2].y = 1.f;
2246 g->ideal_box[3].x = 0.f;
2247 g->ideal_box[3].y = 1.f;
2248
2249 update_bounding_box(g, 0.f, 0.f);
2250}
2251
2252
2253
2254int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
2255{
2256 if(!self->enabled) return 0;
2257
2259 if(IS_NULL_PTR(g) || !g->is_profiling_started) return 0;
2260 if(g->box[0].x == -1.0f || g->box[1].y == -1.0f) return 0;
2261
2262 dt_develop_t *dev = self->dev;
2263 const float wd = dev->roi.preview_width;
2264 const float ht = dev->roi.preview_height;
2265 if(wd == 0.f || ht == 0.f) return 0;
2266
2267 float pzxpy[2] = { (float)x, (float)y };
2270 const float pzx = pzxpy[0];
2271 const float pzy = pzxpy[1];
2272
2273 // if dragging and dropping, don't update active nodes,
2274 // just update cursor coordinates then redraw
2275 // this ensure smooth updates
2276 if(g->drag_drop)
2277 {
2279 g->click_end.x = pzx;
2280 g->click_end.y = pzy;
2281
2282 update_bounding_box(g, g->click_end.x - g->click_start.x, g->click_end.y - g->click_start.y);
2283
2284 g->click_start.x = pzx;
2285 g->click_start.y = pzy;
2287
2289 return 1;
2290 }
2291
2292 // Find out if we are close to a node
2294 g->is_cursor_close = FALSE;
2295
2296 for(size_t k = 0; k < 4; k++)
2297 {
2298 if(hypotf(pzx - g->box[k].x, pzy - g->box[k].y) < 15.f)
2299 {
2300 g->active_node[k] = TRUE;
2301 g->is_cursor_close = TRUE;
2302 }
2303 else
2304 g->active_node[k] = FALSE;
2305 }
2307
2308 // if cursor is close from a node, remove the system pointer arrow to prevent hiding the spot behind it
2309 if(g->is_cursor_close)
2310 {
2312 }
2313 else
2314 {
2315 // fall back to default cursor
2318 }
2319
2321
2322 return 1;
2323}
2324
2325int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
2326 uint32_t state)
2327{
2328 if(!self->enabled) return 0;
2329
2331 if(IS_NULL_PTR(g) || !g->is_profiling_started) return 0;
2332
2333 dt_develop_t *dev = self->dev;
2334 const float wd = dev->roi.preview_width;
2335 const float ht = dev->roi.preview_height;
2336 if(wd == 0.f || ht == 0.f) return 0;
2337
2338 // double click : reset the perspective correction
2339 if(type == GDK_DOUBLE_BUTTON_PRESS)
2340 {
2342 g->checker_ready = FALSE;
2343 g->profile_ready = FALSE;
2344 init_bounding_box(g, wd, ht);
2346
2348 return 1;
2349 }
2350
2351 // bounded box not inited, abort
2352 if(g->box[0].x == -1.0f || g->box[1].y == -1.0f) return 0;
2353
2354 // cursor is not on a node, abort
2355 if(!g->is_cursor_close) return 0;
2356
2357 float pzxpy[2] = { (float)x, (float)y };
2360 const float pzx = pzxpy[0];
2361 const float pzy = pzxpy[1];
2362
2364 g->drag_drop = TRUE;
2365 g->click_start.x = pzx;
2366 g->click_start.y = pzy;
2368
2370
2371 return 1;
2372}
2373
2374int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
2375{
2376 if(!self->enabled) return 0;
2377
2379 if(IS_NULL_PTR(g) || !g->is_profiling_started) return 0;
2380 if(g->box[0].x == -1.0f || g->box[1].y == -1.0f) return 0;
2381 if(!g->is_cursor_close || !g->drag_drop) return 0;
2382
2383 dt_develop_t *dev = self->dev;
2384 const float wd = dev->roi.preview_width;
2385 const float ht = dev->roi.preview_height;
2386 if(wd == 0.f || ht == 0.f) return 0;
2387
2388 float pzxpy[2] = { (float)x, (float)y };
2391 const float pzx = pzxpy[0];
2392 const float pzy = pzxpy[1];
2393
2395 g->drag_drop = FALSE;
2396 g->click_end.x = pzx;
2397 g->click_end.y = pzy;
2398 update_bounding_box(g, g->click_end.x - g->click_start.x, g->click_end.y - g->click_start.y);
2400
2402
2403 return 1;
2404}
2405
2406void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
2407 int32_t pointerx, int32_t pointery)
2408{
2410 if(IS_NULL_PTR(work_profile)) return;
2411
2413 if(IS_NULL_PTR(g) || !g->is_profiling_started) return;
2414
2415 // Rescale and shift Cairo drawing coordinates
2416 dt_develop_t *dev = self->dev;
2417 if(dt_dev_rescale_roi(dev, cr, width, height)) return;
2418 const float zoom_scale = dt_dev_get_overlay_scale(dev);
2419
2420 cairo_set_line_width(cr, 2.0 / zoom_scale);
2421 const double origin = 9. / zoom_scale;
2422 const double destination = 18. / zoom_scale;
2423
2424 for(size_t k = 0; k < 4; k++)
2425 {
2426 if(g->active_node[k])
2427 {
2428 // draw cross hair
2429 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2430
2431 cairo_move_to(cr, g->box[k].x - origin, g->box[k].y);
2432 cairo_line_to(cr, g->box[k].x - destination, g->box[k].y);
2433
2434 cairo_move_to(cr, g->box[k].x + origin, g->box[k].y);
2435 cairo_line_to(cr, g->box[k].x + destination, g->box[k].y);
2436
2437 cairo_move_to(cr, g->box[k].x, g->box[k].y - origin);
2438 cairo_line_to(cr, g->box[k].x, g->box[k].y - destination);
2439
2440 cairo_move_to(cr, g->box[k].x, g->box[k].y + origin);
2441 cairo_line_to(cr, g->box[k].x, g->box[k].y + destination);
2442
2443 cairo_stroke(cr);
2444 }
2445
2446 // draw outline circle
2447 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2448 cairo_arc(cr, g->box[k].x, g->box[k].y, 8. / zoom_scale, 0, 2. * M_PI);
2449 cairo_stroke(cr);
2450
2451 // draw black dot
2452 cairo_set_source_rgba(cr, 0., 0., 0., 1.);
2453 cairo_arc(cr, g->box[k].x, g->box[k].y, 1.5 / zoom_scale, 0, 2. * M_PI);
2454 cairo_fill(cr);
2455 }
2456
2457 // draw symmetry axes
2458 cairo_set_line_width(cr, 1.5 / zoom_scale);
2459 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2460 const point_t top_ideal = { 0.5f, 1.f };
2461 const point_t top = apply_homography(top_ideal, g->homography);
2462 const point_t bottom_ideal = { 0.5f, 0.f };
2463 const point_t bottom = apply_homography(bottom_ideal, g->homography);
2464 cairo_move_to(cr, top.x, top.y);
2465 cairo_line_to(cr, bottom.x, bottom.y);
2466 cairo_stroke(cr);
2467
2468 const point_t left_ideal = { 0.f, 0.5f };
2469 const point_t left = apply_homography(left_ideal, g->homography);
2470 const point_t right_ideal = { 1.f, 0.5f };
2471 const point_t right = apply_homography(right_ideal, g->homography);
2472 cairo_move_to(cr, left.x, left.y);
2473 cairo_line_to(cr, right.x, right.y);
2474 cairo_stroke(cr);
2475
2476 /* For debug : display center of the image and center of the ideal target
2477 point_t new_target_center = apply_homography(target_center, g->homography);
2478 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2479 cairo_arc(cr, new_target_center.x, new_target_center.y, 7., 0, 2. * M_PI);
2480 cairo_stroke(cr);
2481
2482 cairo_set_source_rgba(cr, 0., 1., 1., 1.);
2483 cairo_arc(cr, 0.5 * wd, 0.5 * ht, 7., 0, 2. * M_PI);
2484 cairo_stroke(cr);
2485 */
2486 if(!g->checker || !g->checker->finished || !g->checker->values)
2487 {
2488 // no color data available, display a message
2489
2490 const point_t target_center = { 0.5f, 0.5f };
2491 point_t new_target_center = apply_homography(target_center, g->homography);
2492
2493 const char *msg = _("Error: No color data available for the selected chart.");
2494 cairo_set_font_size(cr, 20.0 / zoom_scale);
2495 cairo_text_extents_t extents;
2496 cairo_text_extents(cr, msg, &extents);
2497
2498 // Draw a white rectangle behind the text
2499 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2500 cairo_rectangle(cr,
2501 new_target_center.x - extents.width / 2.0 - 5.0 / zoom_scale,
2502 new_target_center.y - extents.height / 2.0 - 5.0 / zoom_scale,
2503 extents.width + 10.0 / zoom_scale,
2504 extents.height + 10.0 / zoom_scale);
2505 cairo_fill(cr);
2506
2507 // Draw the text in red
2508 cairo_set_source_rgba(cr, 1., 0., 0., 1.);
2509 cairo_select_font_face(cr, "roboto", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
2510 cairo_move_to(cr, new_target_center.x - extents.width / 2.0, new_target_center.y + extents.height / 2.0);
2511 cairo_show_text(cr, msg);
2512 return;
2513 }
2514
2515 const float radius_x = g->checker->radius * hypotf(1.f, g->checker->ratio) * g->safety_margin;
2516 const float radius_y = radius_x / g->checker->ratio;
2517
2518 for(size_t k = 0; k < g->checker->patches; k++)
2519 {
2520 // center of the patch in the ideal reference
2521 const point_t center = { g->checker->values[k].x, g->checker->values[k].y };
2522
2523 // corners of the patch in the ideal reference
2524 const point_t corners[4] = { {center.x - radius_x, center.y - radius_y},
2525 {center.x + radius_x, center.y - radius_y},
2526 {center.x + radius_x, center.y + radius_y},
2527 {center.x - radius_x, center.y + radius_y} };
2528
2529 // apply patch coordinates transform depending on perspective
2530 const point_t new_center = apply_homography(center, g->homography);
2531 // apply_homography_scaling gives a scaling of areas. we need to scale the
2532 // radius of the center circle so take a square root.
2533 const float scaling = sqrtf(apply_homography_scaling(center, g->homography));
2534 point_t new_corners[4];
2535 for(size_t c = 0; c < 4; c++) new_corners[c] = apply_homography(corners[c], g->homography);
2536
2537 cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
2538 cairo_set_source_rgba(cr, 0., 0., 0., 1.);
2539 cairo_move_to(cr, new_corners[0].x, new_corners[0].y);
2540 cairo_line_to(cr, new_corners[1].x, new_corners[1].y);
2541 cairo_line_to(cr, new_corners[2].x, new_corners[2].y);
2542 cairo_line_to(cr, new_corners[3].x, new_corners[3].y);
2543 cairo_line_to(cr, new_corners[0].x, new_corners[0].y);
2544
2545 if(g->delta_E_in)
2546 {
2547 // draw delta E feedback
2548 if(g->delta_E_in[k] > 2.3f)
2549 {
2550 // one diagonal if delta E > 3
2551 cairo_move_to(cr, new_corners[0].x, new_corners[0].y);
2552 cairo_line_to(cr, new_corners[2].x, new_corners[2].y);
2553 }
2554 if(g->delta_E_in[k] > 4.6f)
2555 {
2556 // the other diagonal if delta E > 6
2557 cairo_move_to(cr, new_corners[1].x, new_corners[1].y);
2558 cairo_line_to(cr, new_corners[3].x, new_corners[3].y);
2559 }
2560 }
2561
2562 cairo_set_line_width(cr, 5.0 / zoom_scale);
2563 cairo_stroke_preserve(cr);
2564 cairo_set_line_width(cr, 2.0 / zoom_scale);
2565 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2566 cairo_stroke(cr);
2567
2568 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
2569
2571 dt_ioppr_lab_to_rgb_matrix(g->checker->values[k].Lab, RGB, work_profile->matrix_out_transposed, work_profile->lut_out,
2572 work_profile->unbounded_coeffs_out, work_profile->lutsize,
2573 work_profile->nonlinearlut);
2574
2575 cairo_set_source_rgba(cr, RGB[0], RGB[1], RGB[2], 1.);
2576 cairo_arc(cr, new_center.x, new_center.y, 0.25 * (radius_x + radius_y) * scaling, 0, 2. * M_PI);
2577 cairo_fill(cr);
2578 }
2579}
2580
2581void color_list_visibility(dt_iop_module_t *self, const int checker_cmbbx_index)
2582{
2584
2585 if(checker_cmbbx_index >= COLOR_CHECKER_USER_REF)
2586 if(dt_bauhaus_combobox_get_entry(GTK_WIDGET(g->checkers_color_list), 0) != NULL)
2587 {
2588 gtk_widget_show(GTK_WIDGET(g->checkers_color_list));
2589 gtk_widget_hide(GTK_WIDGET(g->checker_msg));
2590 }
2591 else
2592 {
2593 gtk_widget_hide(GTK_WIDGET(g->checkers_color_list));
2594 gtk_widget_show_now(GTK_WIDGET(g->checker_msg));
2595 }
2596
2597 else
2598 {
2599 gtk_widget_hide(GTK_WIDGET(g->checkers_color_list));
2600 gtk_widget_hide(GTK_WIDGET(g->checker_msg));
2601 }
2602}
2603
2610{
2612
2613 if(!g) return;
2614 if(!g->colorcheckers) return;
2615
2616 const int selected_checker = dt_conf_get_int("darkroom/modules/channelmixerrgb/colorchecker");
2617 // early return
2618 if(g->n_colorcheckers < COLOR_CHECKER_USER_REF || selected_checker < COLOR_CHECKER_USER_REF) return;
2619
2620 // find all CGATS files if not already done
2621 if(!g->n_color)
2622 g->n_color = dt_colorchecker_find_color(&(g->colorcheckers_all_color));
2623 // no CGATS files found, early return
2624 if(g->n_color == 0) return;
2625
2626 // update the gui
2627 dt_bauhaus_combobox_clear(g->checkers_color_list);
2628 g_list_free(g->colorcheckers_color); // don't free_full because data pointers belong to g->colorcheckers_all_color
2629 g->colorcheckers_color = NULL;
2630
2631 const dt_colorchecker_label_t *checker_label = (const dt_colorchecker_label_t*)g_list_nth_data(g->colorcheckers, selected_checker);
2632 const int checker_patch_nb = checker_label ? checker_label->patch_nb : 0;
2633
2634 if(checker_patch_nb > 0)
2635 {
2636 for(GList *l = g_list_first(g->colorcheckers_all_color); l; l = g_list_next(l))
2637 {
2638 dt_colorchecker_label_t *cht_label = (dt_colorchecker_label_t *)l->data;
2639 // Filter out colorcheckers with different patch number.
2640 // A separate GList is required to associate the color checkers with their corresponding combobox item IDs.
2641 if(cht_label->patch_nb == checker_patch_nb)
2642 {
2643 dt_bauhaus_combobox_add(g->checkers_color_list, cht_label->name);
2644 g->colorcheckers_color = g_list_append(g->colorcheckers_color, cht_label);
2645 }
2646 }
2647 }
2648 dt_control_queue_redraw_widget(g->checkers_color_list);
2649}
2650
2652{
2654 if(IS_NULL_PTR(g)) return;
2655
2656 dt_colorchecker_label_list_cleanup(&(g->colorcheckers));
2657
2658 g->n_colorcheckers = 0;
2659 int pos = dt_colorchecker_find(&(g->colorcheckers));
2660 g->n_colorcheckers = pos;
2661
2662 // update the gui
2663 dt_bauhaus_combobox_clear(g->checkers_list);
2664 gboolean has_builtin_checker = FALSE;
2665 gboolean user_checker_separator_added = FALSE;
2666 for(GList *l = g_list_first(g->colorcheckers); l; l = g_list_next(l))
2667 {
2668 const dt_colorchecker_label_t *checker_data = (const dt_colorchecker_label_t*)l->data;
2669
2670 if(checker_data->type < COLOR_CHECKER_USER_REF)
2671 {
2672 has_builtin_checker = TRUE;
2673 }
2674 else if(has_builtin_checker && !user_checker_separator_added)
2675 {
2676 dt_bauhaus_combobox_add_separator(g->checkers_list);
2677 user_checker_separator_added = TRUE;
2678 }
2679
2680 dt_bauhaus_combobox_add(g->checkers_list, checker_data->name);
2681 }
2682}
2683
2684static void optimize_changed_callback(GtkWidget *widget, gpointer user_data)
2685{
2686 if(darktable.gui->reset) return;
2687 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2689
2690 const int i = dt_bauhaus_combobox_get(widget);
2691 dt_conf_set_int("darkroom/modules/channelmixerrgb/optimization", i);
2692
2694 g->optimization = i;
2696}
2697
2698static void checker_color_changed_callback(GtkWidget *widget, gpointer user_data)
2699{
2700 if(darktable.gui->reset) return;
2701 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2703
2704 const int selected_cmbbx_index = dt_bauhaus_combobox_get(widget);
2705 dt_conf_set_int("darkroom/modules/channelmixerrgb/colorchecker_color", selected_cmbbx_index);
2706
2707 const int n_chkr = dt_bauhaus_combobox_get(g->checkers_list);
2708
2709 const dt_colorchecker_label_t *color_label = (selected_cmbbx_index >= 0 && g->colorcheckers_color) ?
2710 (const dt_colorchecker_label_t*)g_list_nth_data(g->colorcheckers_color, selected_cmbbx_index) : NULL;
2711
2712 const char *color_path = color_label ? color_label->path : NULL;
2713
2714 dt_colorchecker_cleanup(g->checker);
2715 g->checker = dt_get_color_checker(n_chkr, &(g->colorcheckers), color_path);
2716
2717 dt_develop_t *dev = self->dev;
2718 const float wd = dev->roi.preview_width;
2719 const float ht = dev->roi.preview_height;
2720 if(wd == 0.f || ht == 0.f) return;
2721
2723 g->profile_ready = FALSE;
2724 init_bounding_box(g, wd, ht);
2726
2728}
2729
2730void checker_changed_callback(GtkWidget *widget, gpointer user_data)
2731{
2732 if(darktable.gui->reset) return;
2733 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2735
2736 const int checker_cmbbx_index = dt_bauhaus_combobox_get(widget);
2737 dt_conf_set_int("darkroom/modules/channelmixerrgb/colorchecker", checker_cmbbx_index);
2738 ++darktable.gui->reset;
2740 color_list_visibility(self, checker_cmbbx_index);
2741 --darktable.gui->reset;
2742
2743 const int n_color = dt_bauhaus_combobox_get(g->checkers_color_list);
2744 const dt_colorchecker_label_t *color_label = (n_color >= 0 && g->colorcheckers_color) ? (const dt_colorchecker_label_t*)g_list_nth_data(g->colorcheckers_color, n_color) : NULL;
2745 const char *color_path = color_label ? color_label->path : NULL;
2746
2747 dt_colorchecker_cleanup(g->checker);
2748 g->checker = dt_get_color_checker(checker_cmbbx_index, &(g->colorcheckers), color_path);
2749
2750 dt_develop_t *dev = self->dev;
2751 const float wd = dev->roi.preview_width;
2752 const float ht = dev->roi.preview_height;
2753 if(wd == 0.f || ht == 0.f) return;
2754
2756 g->profile_ready = FALSE;
2757 init_bounding_box(g, wd, ht);
2759
2761}
2762
2763static void safety_changed_callback(GtkWidget *widget, gpointer user_data)
2764{
2765 if(darktable.gui->reset) return;
2766 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2768
2770 g->safety_margin = dt_bauhaus_slider_get(widget);
2772
2773 dt_conf_set_float("darkroom/modules/channelmixerrgb/safety", g->safety_margin);
2775}
2776
2777
2778static void start_profiling_callback(GtkWidget *togglebutton, dt_iop_module_t *self)
2779{
2780 if(darktable.gui->reset) return;
2782 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
2783
2784 dt_develop_t *dev = self->dev;
2785 const float wd = dev->roi.preview_width;
2786 const float ht = dev->roi.preview_height;
2787 if(wd == 0.f || ht == 0.f) return;
2788
2790 g->is_profiling_started = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->cs.toggle));
2791
2792 // init bounding box
2794 init_bounding_box(g, wd, ht);
2796
2798}
2799
2800static void run_profile_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2801{
2802 if(darktable.gui->reset) return;
2803 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2805
2807 g->run_profile = TRUE;
2811}
2812
2813static void run_validation_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2814{
2815 if(darktable.gui->reset) return;
2816 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2818
2820 g->run_validation = TRUE;
2824}
2825
2826static void commit_profile_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2827{
2828 if(darktable.gui->reset) return;
2829 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2832
2833 if(!g->profile_ready) return;
2834
2836
2837 p->x = g->xy[0];
2838 p->y = g->xy[1];
2839 p->illuminant = DT_ILLUMINANT_CUSTOM;
2840 check_if_close_to_daylight(p->x, p->y, &p->temperature, NULL, NULL);
2841
2842 p->red[0] = g->mix[0][0];
2843 p->red[1] = g->mix[0][1];
2844 p->red[2] = g->mix[0][2];
2845
2846 p->green[0] = g->mix[1][0];
2847 p->green[1] = g->mix[1][1];
2848 p->green[2] = g->mix[1][2];
2849
2850 p->blue[0] = g->mix[2][0];
2851 p->blue[1] = g->mix[2][1];
2852 p->blue[2] = g->mix[2][2];
2853
2855
2856 ++darktable.gui->reset;
2857 dt_bauhaus_combobox_set(g->illuminant, p->illuminant);
2858 dt_bauhaus_slider_set(g->temperature, p->temperature);
2859
2860 dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
2861 dt_aligned_pixel_t Lch = { 0 };
2862 dt_xyY_to_Lch(xyY, Lch);
2863 dt_bauhaus_slider_set(g->illum_x, Lch[2] / M_PI * 180.f);
2864 dt_bauhaus_slider_set(g->illum_y, Lch[1]);
2865
2866 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
2867 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
2868 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
2869
2870 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
2871 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
2872 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
2873
2874 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
2875 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
2876 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
2877
2878 --darktable.gui->reset;
2879
2880 gui_changed(self, NULL, NULL);
2881
2882 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=commit_profile\n");
2884}
2885
2886static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
2887{
2888 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2891
2892 if(IS_NULL_PTR(g)) return;
2893 if(p->illuminant != DT_ILLUMINANT_DETECT_EDGES && p->illuminant != DT_ILLUMINANT_DETECT_SURFACES)
2894 return;
2895
2897 p->x = g->XYZ[0];
2898 p->y = g->XYZ[1];
2900
2901 check_if_close_to_daylight(p->x, p->y, &p->temperature, &p->illuminant, &p->adaptation);
2902
2903 ++darktable.gui->reset;
2904
2905 dt_bauhaus_slider_set(g->temperature, p->temperature);
2906 dt_bauhaus_combobox_set(g->illuminant, p->illuminant);
2907 dt_bauhaus_combobox_set(g->adaptation, p->adaptation);
2908
2909 const dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
2911 dt_xyY_to_Lch(xyY, Lch);
2912 dt_bauhaus_slider_set(g->illum_x, Lch[2] / M_PI * 180.f);
2913 dt_bauhaus_slider_set(g->illum_y, Lch[1]);
2914
2915 update_illuminants(self);
2916 update_approx_cct(self);
2919
2920 --darktable.gui->reset;
2921
2922 gui_changed(self, NULL, NULL);
2923
2924 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=ui_pipe_finished\n");
2926}
2927
2928static void _preview_pipe_finished_callback(gpointer instance, gpointer user_data)
2929{
2930 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2932
2934 gtk_label_set_markup(GTK_LABEL(g->label_delta_E), g->delta_E_label_text);
2936}
2937
2940{
2944
2945 d->version = p->version;
2946
2947 float norm_R = 1.0f;
2948 if(p->normalize_R) norm_R = p->red[0] + p->red[1] + p->red[2];
2949
2950 float norm_G = 1.0f;
2951 if(p->normalize_G) norm_G = p->green[0] + p->green[1] + p->green[2];
2952
2953 float norm_B = 1.0f;
2954 if(p->normalize_B) norm_B = p->blue[0] + p->blue[1] + p->blue[2];
2955
2956 float norm_sat = 0.0f;
2957 if(p->normalize_sat) norm_sat = (p->saturation[0] + p->saturation[1] + p->saturation[2]) / 3.f;
2958
2959 float norm_light = 0.0f;
2960 if(p->normalize_light) norm_light = (p->lightness[0] + p->lightness[1] + p->lightness[2]) / 3.f;
2961
2962 float norm_grey = p->grey[0] + p->grey[1] + p->grey[2];
2963 d->apply_grey = (p->grey[0] != 0.f) || (p->grey[1] != 0.f) || (p->grey[2] != 0.f);
2964 if(!p->normalize_grey || norm_grey == 0.f) norm_grey = 1.f;
2965
2966 for(int i = 0; i < 3; i++)
2967 {
2968 d->MIX[0][i] = p->red[i] / norm_R;
2969 d->MIX[1][i] = p->green[i] / norm_G;
2970 d->MIX[2][i] = p->blue[i] / norm_B;
2971 d->saturation[i] = -p->saturation[i] + norm_sat;
2972 d->lightness[i] = p->lightness[i] - norm_light;
2973 d->grey[i] = p->grey[i] / norm_grey; // = NaN if (norm_grey == 0.f) but we don't care since (d->apply_grey == FALSE)
2974 }
2975
2976 if(p->version == CHANNELMIXERRGB_V_1)
2977 {
2978 // for the v1 saturation algo, the effect of R and B coeffs is reversed
2979 d->saturation[0] = -p->saturation[2] + norm_sat;
2980 d->saturation[2] = -p->saturation[0] + norm_sat;
2981 }
2982
2983 // just in case compiler feels clever and uses SSE 4x1 dot product
2984 d->saturation[CHANNEL_SIZE - 1] = 0.0f;
2985 d->lightness[CHANNEL_SIZE - 1] = 0.0f;
2986 d->grey[CHANNEL_SIZE - 1] = 0.0f;
2987
2988 d->adaptation = p->adaptation;
2989 d->clip = p->clip;
2990 d->gamut = (p->gamut == 0.f) ? p->gamut : 1.f / p->gamut;
2991
2992 // find x y coordinates of illuminant for CIE 1931 2° observer
2993 float x = p->x;
2994 float y = p->y;
2995 dt_aligned_pixel_t custom_wb;
2996 get_white_balance_coeff(self, custom_wb);
2997 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led);
2998
2999 // if illuminant is set as camera, x and y are set on-the-fly at commit time, so we need to set adaptation too
3000 if(p->illuminant == DT_ILLUMINANT_CAMERA)
3001 check_if_close_to_daylight(x, y, NULL, NULL, &(d->adaptation));
3002
3003 d->illuminant_type = p->illuminant;
3004
3005 // Convert illuminant from xyY to XYZ
3008
3009 // Convert illuminant from XYZ to Bradford modified LMS
3010 dt_store_simd_aligned(d->illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), d->adaptation));
3011 d->illuminant[3] = 0.f;
3012
3013 dt_iop_fmt_log(self, "commit: class=%s matrix_supported=%d illuminant=%d adaptation=%d custom_wb=[%.4f %.4f %.4f %.4f] xy=[%.4f %.4f]",
3016 p->illuminant, d->adaptation, custom_wb[0], custom_wb[1], custom_wb[2], custom_wb[3], x, y);
3017
3018 //fprintf(stdout, "illuminant: %i\n", p->illuminant);
3019 //fprintf(stdout, "x: %f, y: %f\n", x, y);
3020 //fprintf(stdout, "X: %f - Y: %f - Z: %f\n", XYZ[0], XYZ[1], XYZ[2]);
3021 //fprintf(stdout, "L: %f - M: %f - S: %f\n", d->illuminant[0], d->illuminant[1], d->illuminant[2]);
3022
3023 // blue compensation for Bradford transform = (test illuminant blue / reference illuminant blue)^0.0834
3024 // reference illuminant is hard-set D50 for darktable's pipeline
3025 // test illuminant is user params
3026 d->p = powf(0.818155f / d->illuminant[2], 0.0834f);
3027
3028 // Disable OpenCL path if we are in any kind of diagnose mode (only C path has diagnostics)
3029 if(self->dev->gui_attached && g)
3030 {
3031 if( (g->run_profile && pipe->type == DT_DEV_PIXELPIPE_PREVIEW) || // color checker extraction mode
3032 (g->run_validation && pipe->type == DT_DEV_PIXELPIPE_PREVIEW) || // delta E validation
3033 ( (d->illuminant_type == DT_ILLUMINANT_DETECT_EDGES ||
3034 d->illuminant_type == DT_ILLUMINANT_DETECT_SURFACES ) && // WB extraction mode
3035 pipe->type == DT_DEV_PIXELPIPE_FULL ) )
3036 {
3037 piece->process_cl_ready = 0;
3038 }
3039 }
3040}
3041
3042
3044{
3047
3048 if(p->adaptation == DT_ADAPTATION_RGB || p->adaptation == DT_ADAPTATION_LAST)
3049 {
3050 // user disabled CAT at all, hide everything and exit
3051 gtk_widget_set_visible(g->illuminant, FALSE);
3052 gtk_widget_set_visible(g->illum_color, FALSE);
3053 gtk_widget_set_visible(g->approx_cct, FALSE);
3054 gtk_widget_set_visible(g->color_picker, FALSE);
3055 gtk_widget_set_visible(g->temperature, FALSE);
3056 gtk_widget_set_visible(g->illum_fluo, FALSE);
3057 gtk_widget_set_visible(g->illum_led, FALSE);
3058 gtk_widget_set_visible(g->illum_x, FALSE);
3059 gtk_widget_set_visible(g->illum_y, FALSE);
3060 return;
3061 }
3062 else
3063 {
3064 // set everything visible again and carry on
3065 gtk_widget_set_visible(g->illuminant, TRUE);
3066 gtk_widget_set_visible(g->illum_color, TRUE);
3067 gtk_widget_set_visible(g->approx_cct, TRUE);
3068 gtk_widget_set_visible(g->color_picker, TRUE);
3069 gtk_widget_set_visible(g->temperature, TRUE);
3070 gtk_widget_set_visible(g->illum_fluo, TRUE);
3071 gtk_widget_set_visible(g->illum_led, TRUE);
3072 gtk_widget_set_visible(g->illum_x, TRUE);
3073 }
3074
3075 // Display only the relevant sliders
3076 switch(p->illuminant)
3077 {
3078 case DT_ILLUMINANT_PIPE:
3079 case DT_ILLUMINANT_A:
3080 case DT_ILLUMINANT_E:
3081 {
3082 gtk_widget_set_visible(g->adaptation, TRUE);
3083 gtk_widget_set_visible(g->temperature, FALSE);
3084 gtk_widget_set_visible(g->illum_fluo, FALSE);
3085 gtk_widget_set_visible(g->illum_led, FALSE);
3086 gtk_widget_set_visible(g->illum_x, FALSE);
3087 gtk_widget_set_visible(g->illum_y, FALSE);
3088 break;
3089 }
3090 case DT_ILLUMINANT_D:
3091 case DT_ILLUMINANT_BB:
3092 {
3093 gtk_widget_set_visible(g->adaptation, TRUE);
3094 gtk_widget_set_visible(g->temperature, TRUE);
3095 gtk_widget_set_visible(g->illum_fluo, FALSE);
3096 gtk_widget_set_visible(g->illum_led, FALSE);
3097 gtk_widget_set_visible(g->illum_x, FALSE);
3098 gtk_widget_set_visible(g->illum_y, FALSE);
3099 break;
3100 }
3101 case DT_ILLUMINANT_F:
3102 {
3103 gtk_widget_set_visible(g->adaptation, TRUE);
3104 gtk_widget_set_visible(g->temperature, FALSE);
3105 gtk_widget_set_visible(g->illum_fluo, TRUE);
3106 gtk_widget_set_visible(g->illum_led, FALSE);
3107 gtk_widget_set_visible(g->illum_x, FALSE);
3108 gtk_widget_set_visible(g->illum_y, FALSE);
3109 break;
3110 }
3111 case DT_ILLUMINANT_LED:
3112 {
3113 gtk_widget_set_visible(g->adaptation, TRUE);
3114 gtk_widget_set_visible(g->temperature, FALSE);
3115 gtk_widget_set_visible(g->illum_fluo, FALSE);
3116 gtk_widget_set_visible(g->illum_led, TRUE);
3117 gtk_widget_set_visible(g->illum_x, FALSE);
3118 gtk_widget_set_visible(g->illum_y, FALSE);
3119 break;
3120 }
3122 {
3123 gtk_widget_set_visible(g->adaptation, TRUE);
3124 gtk_widget_set_visible(g->temperature, FALSE);
3125 gtk_widget_set_visible(g->illum_fluo, FALSE);
3126 gtk_widget_set_visible(g->illum_led, FALSE);
3127 gtk_widget_set_visible(g->illum_x, TRUE);
3128 gtk_widget_set_visible(g->illum_y, TRUE);
3129 break;
3130 }
3132 {
3133 gtk_widget_set_visible(g->adaptation, TRUE);
3134 gtk_widget_set_visible(g->temperature, FALSE);
3135 gtk_widget_set_visible(g->illum_fluo, FALSE);
3136 gtk_widget_set_visible(g->illum_led, FALSE);
3137 gtk_widget_set_visible(g->illum_x, FALSE);
3138 gtk_widget_set_visible(g->illum_y, FALSE);
3139 break;
3140 }
3143 {
3144 gtk_widget_set_visible(g->adaptation, FALSE);
3145 gtk_widget_set_visible(g->temperature, FALSE);
3146 gtk_widget_set_visible(g->illum_fluo, FALSE);
3147 gtk_widget_set_visible(g->illum_led, FALSE);
3148 gtk_widget_set_visible(g->illum_x, FALSE);
3149 gtk_widget_set_visible(g->illum_y, FALSE);
3150 break;
3151 }
3152 case DT_ILLUMINANT_LAST:
3153 default:
3154 {
3155 break;
3156 }
3157 }
3158}
3159
3188{
3189 // update the fill background color of x, y sliders
3192
3193 if(IS_NULL_PTR(g) || IS_NULL_PTR(p) || IS_NULL_PTR(g->illum_x) || IS_NULL_PTR(g->illum_y)) return;
3194
3195 // Varies x in range around current y param
3196 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
3197 {
3198 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
3199 const float x = stop * ILLUM_X_MAX;
3200 dt_aligned_pixel_t RGB = { 0 };
3201
3202 dt_aligned_pixel_t Lch = { 100.f, 50.f, x / 180.f * M_PI };
3203 dt_aligned_pixel_t xyY = { 0 };
3204 dt_Lch_to_xyY(Lch, xyY);
3206 dt_bauhaus_slider_set_stop(g->illum_x, stop, RGB[0], RGB[1], RGB[2]);
3207
3208 const float y = stop * ILLUM_Y_MAX / 2.0f;
3209
3210 // Find current hue
3211 const dt_aligned_pixel_t xyY2 = { p->x, p->y, 1.f };
3212 dt_xyY_to_Lch(xyY2, Lch);
3213
3214 // Replace chroma by current step
3215 Lch[0] = 75.f;
3216 Lch[1] = y;
3217
3218 // Go back to xyY
3219 dt_Lch_to_xyY(Lch, xyY);
3221 dt_bauhaus_slider_set_stop(g->illum_y, stop, RGB[0], RGB[1], RGB[2]);
3222 }
3223
3224 gtk_widget_queue_draw(g->illum_x);
3225 gtk_widget_queue_draw(g->illum_y);
3226}
3227
3228static void paint_hue(dt_iop_module_t *self)
3229{
3230 // update the fill background color of LCh sliders
3232
3233 const float hue = dt_bauhaus_slider_get(g->hue_spot);
3234
3235 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
3236 {
3237 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
3238 dt_aligned_pixel_t RGB = { 0 }, Lab = { 0 }, XYZ = { 0 };
3239
3240 const dt_aligned_pixel_t Lch_hue = { 67.f, 96.f, (stop * HUE_MAX) / 360.f};
3241
3242 dt_LCH_2_Lab(Lch_hue, Lab);
3245
3246 dt_bauhaus_slider_set_stop(g->hue_spot, stop, RGB[0], RGB[1], RGB[2]);
3247
3248 const dt_aligned_pixel_t Lch_lightness = { stop * LIGHTNESS_MAX, 0.f, 0. };
3249
3250 dt_LCH_2_Lab(Lch_lightness, Lab);
3253
3254 dt_bauhaus_slider_set_stop(g->lightness_spot, stop, RGB[0], RGB[1], RGB[2]);
3255
3256 const dt_aligned_pixel_t Lch_chroma = { 50., stop * CHROMA_MAX, hue / 360.f };
3257
3258 dt_LCH_2_Lab(Lch_chroma, Lab);
3261
3262 dt_bauhaus_slider_set_stop(g->chroma_spot, stop, RGB[0], RGB[1], RGB[2]);
3263 }
3264
3265 gtk_widget_queue_draw(g->hue_spot);
3266 gtk_widget_queue_draw(g->lightness_spot);
3267 gtk_widget_queue_draw(g->chroma_spot);
3268 gtk_widget_queue_draw(g->target_spot);
3269}
3270
3271
3280{
3283 GtkWidget *const widgets[9]
3284 = { g->primaries_achromatic_hue, g->primaries_achromatic_purity, g->primaries_red_hue,
3285 g->primaries_red_purity, g->primaries_green_hue, g->primaries_green_purity,
3286 g->primaries_blue_hue, g->primaries_blue_purity, g->primaries_gain };
3290 const float rows[3][3] = { { p->red[0], p->red[1], p->red[2] },
3291 { p->green[0], p->green[1], p->green[2] },
3292 { p->blue[0], p->blue[1], p->blue[2] } };
3293 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
3294 float M[3][3] = { { 0.f } };
3295 float roundtrip[3][3] = { { 0.f } };
3296
3298 if(!dt_iop_channelmixer_shared_primaries_from_matrix(basis, M, &primaries)) return FALSE;
3299 if(!dt_iop_channelmixer_shared_primaries_to_matrix(basis, &primaries, roundtrip)) return FALSE;
3300
3301 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
3302 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
3303
3304 ++darktable.gui->reset;
3306 --darktable.gui->reset;
3307 return isfinite(roundtrip_error) && roundtrip_error <= DT_CHANNELMIXERRGB_SIMPLE_EPS;
3308}
3309
3311{
3315 const dt_iop_order_iccprofile_info_t *const display_profile
3317 GtkWidget *const widgets[9]
3318 = { g->primaries_achromatic_hue, g->primaries_achromatic_purity, g->primaries_red_hue,
3319 g->primaries_red_purity, g->primaries_green_hue, g->primaries_green_purity,
3320 g->primaries_blue_hue, g->primaries_blue_purity, g->primaries_gain };
3324
3326 dt_iop_channelmixer_shared_paint_primaries_sliders(p->adaptation, work_profile, display_profile, basis, &primaries,
3327 widgets);
3328}
3329
3331 const struct dt_iop_order_iccprofile_info_t *const work_profile,
3332 const struct dt_iop_order_iccprofile_info_t *const display_profile,
3334{
3335 dt_aligned_pixel_t work_rgb = { LMS[0], LMS[1], LMS[2], 0.f };
3336
3337 if(p->adaptation != DT_ADAPTATION_RGB)
3338 {
3339 convert_any_LMS_to_RGB(LMS, work_rgb, p->adaptation);
3340 }
3341 else
3342 {
3343 for(size_t c = 0; c < 3; c++) work_rgb[c] = LMS[c];
3344 }
3345
3346 dt_iop_channelmixer_shared_work_rgb_to_display(work_rgb, work_profile, display_profile, RGB);
3347}
3348
3350 const struct dt_iop_order_iccprofile_info_t *const work_profile,
3351 const struct dt_iop_order_iccprofile_info_t *const display_profile,
3352 GtkWidget *w, float stop, float c, float r, float g, float b)
3353{
3354 const dt_aligned_pixel_t LMS = { 0.5f * (c * r + 1 - r),
3355 0.5f * (c * g + 1 - g),
3356 0.5f * (c * b + 1 - b)};
3357 dt_aligned_pixel_t RGB_t = { 0.5f };
3358 _convert_GUI_colors(p, work_profile, display_profile, LMS, RGB_t);
3359 dt_bauhaus_slider_set_stop(w, stop, RGB_t[0], RGB_t[1], RGB_t[2]);
3360}
3361
3362static void _update_RGB_colors(dt_iop_module_t *self, float r, float g, float b, gboolean normalize, float *a,
3363 GtkWidget *w_r, GtkWidget *w_g, GtkWidget *w_b)
3364{
3365 // update the fill background color of x, y sliders
3367 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(self->dev->pipe);
3368 const struct dt_iop_order_iccprofile_info_t *const display_profile = dt_ioppr_get_pipe_output_profile_info(self->dev->pipe);
3369
3370 // scale params if needed
3371 dt_aligned_pixel_t RGB = { a[0], a[1], a[2] };
3372
3373 if(normalize)
3374 {
3375 const float sum = RGB[0] + RGB[1] + RGB[2];
3376 if(sum != 0.f) for(int c = 0; c < 3; c++) RGB[c] /= sum;
3377 }
3378
3379 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
3380 {
3381 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
3382 // Use the hard bounds of the sliders; drawing will take into account the possible soft rescaling
3383 const float range_x = COLOR_MAX - COLOR_MIN;
3384
3385 const float x = COLOR_MIN + stop * range_x;
3386
3387 _update_RGB_slider_stop(p, work_profile, display_profile, w_r, stop, x + RGB[1] + RGB[2], r, g, b);
3388 _update_RGB_slider_stop(p, work_profile, display_profile, w_g, stop, RGB[0] + x + RGB[2], r, g, b);
3389 _update_RGB_slider_stop(p, work_profile, display_profile, w_b, stop, RGB[0] + RGB[1] + x , r, g, b);
3390 }
3391
3392 gtk_widget_queue_draw(w_r);
3393 gtk_widget_queue_draw(w_b);
3394 gtk_widget_queue_draw(w_g);
3395}
3396
3405{
3408 GtkWidget *const widgets[6]
3409 = { g->simple_theta, g->simple_psi, g->simple_stretch_1, g->simple_stretch_2, g->simple_coupling_1,
3410 g->simple_coupling_2 };
3411 const float rows[3][3] = { { p->red[0], p->red[1], p->red[2] },
3412 { p->green[0], p->green[1], p->green[2] },
3413 { p->blue[0], p->blue[1], p->blue[2] } };
3414 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
3415 float M[3][3] = { { 0.f } };
3416 float roundtrip[3][3] = { { 0.f } };
3418
3419 if(!IS_NULL_PTR(error)) *error = INFINITY;
3422
3425
3426 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
3427 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
3428
3429 ++darktable.gui->reset;
3431 --darktable.gui->reset;
3432
3433 return isfinite(roundtrip_error) && roundtrip_error <= DT_CHANNELMIXERRGB_SIMPLE_EPS;
3434}
3435
3447{
3451 const dt_iop_order_iccprofile_info_t *const display_profile
3453 GtkWidget *const widgets[6]
3454 = { g->simple_theta, g->simple_psi, g->simple_stretch_1, g->simple_stretch_2, g->simple_coupling_1,
3455 g->simple_coupling_2 };
3457
3459 dt_iop_channelmixer_shared_paint_simple_sliders(p->adaptation, work_profile, display_profile, &simple, widgets);
3460}
3461
3462
3464{
3466 if(IS_NULL_PTR(g)) return;
3467
3468 if(!IS_NULL_PTR(g->illum_color))
3469 gtk_widget_queue_draw(g->illum_color);
3470
3471 update_xy_color(self);
3472}
3473
3474static gboolean illuminant_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3475{
3476 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3478
3479 // Init
3480 GtkAllocation allocation;
3481 gtk_widget_get_allocation(widget, &allocation);
3482 int width = allocation.width, height = allocation.height;
3483 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3484 cairo_t *cr = cairo_create(cst);
3485
3486 // Margins
3487 const float margin = 2. * DT_PIXEL_APPLY_DPI(1.5);
3489 height -= 2 * margin;
3490
3491 // Paint illuminant color - we need to recompute it in full in case camera RAW is chosen
3492 float x = p->x;
3493 float y = p->y;
3494 dt_aligned_pixel_t RGB = { 0 };
3495 dt_aligned_pixel_t custom_wb;
3496 get_white_balance_coeff(self, custom_wb);
3497 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb,
3498 &x, &y, p->temperature, p->illum_fluo, p->illum_led);
3500 cairo_set_source_rgb(cr, RGB[0], RGB[1], RGB[2]);
3501 cairo_rectangle(cr, INNER_PADDING, margin, width, height);
3502 cairo_fill(cr);
3503
3504 // Clean
3505 cairo_stroke(cr);
3506 cairo_destroy(cr);
3507 cairo_set_source_surface(crf, cst, 0, 0);
3508 cairo_paint(crf);
3509 cairo_surface_destroy(cst);
3510 return TRUE;
3511}
3512
3513static gboolean target_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3514{
3515 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3517
3518 // Init
3519 GtkAllocation allocation;
3520 gtk_widget_get_allocation(widget, &allocation);
3521 int width = allocation.width, height = allocation.height;
3522 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3523 cairo_t *cr = cairo_create(cst);
3524
3525 // Margins
3526 const float margin = 2. * DT_PIXEL_APPLY_DPI(1.5);
3528 height -= 2 * margin;
3529
3530 // Paint target color
3531 dt_aligned_pixel_t RGB = { 0 };
3532 dt_aligned_pixel_t Lch = { 0 };
3533 dt_aligned_pixel_t Lab = { 0 };
3534 dt_aligned_pixel_t XYZ = { 0 };
3535 Lch[0] = dt_bauhaus_slider_get(g->lightness_spot);
3536 Lch[1] = dt_bauhaus_slider_get(g->chroma_spot);
3537 Lch[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f;
3538 dt_LCH_2_Lab(Lch, Lab);
3541
3542 cairo_set_source_rgb(cr, RGB[0], RGB[1], RGB[2]);
3543 cairo_rectangle(cr, INNER_PADDING, margin, width, height);
3544 cairo_fill(cr);
3545
3546 // Clean
3547 cairo_stroke(cr);
3548 cairo_destroy(cr);
3549 cairo_set_source_surface(crf, cst, 0, 0);
3550 cairo_paint(crf);
3551 cairo_surface_destroy(cst);
3552 return TRUE;
3553}
3554
3555static gboolean origin_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3556{
3557 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3559
3560 // Init
3561 GtkAllocation allocation;
3562 gtk_widget_get_allocation(widget, &allocation);
3563 int width = allocation.width, height = allocation.height;
3564 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3565 cairo_t *cr = cairo_create(cst);
3566
3567 // Margins
3568 const float margin = 2. * DT_PIXEL_APPLY_DPI(1.5);
3570 height -= 2 * margin;
3571
3572 cairo_set_source_rgb(cr, g->spot_RGB[0], g->spot_RGB[1], g->spot_RGB[2]);
3573 cairo_rectangle(cr, INNER_PADDING, margin, width, height);
3574 cairo_fill(cr);
3575
3576 // Clean
3577 cairo_stroke(cr);
3578 cairo_destroy(cr);
3579 cairo_set_source_surface(crf, cst, 0, 0);
3580 cairo_paint(crf);
3581 cairo_surface_destroy(cst);
3582 return TRUE;
3583}
3584
3586{
3589
3590 float x = p->x;
3591 float y = p->y;
3592 dt_aligned_pixel_t custom_wb;
3593 get_white_balance_coeff(self, custom_wb);
3594 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led);
3595
3596 dt_illuminant_t test_illuminant;
3597 float t = 5000.f;
3598 check_if_close_to_daylight(x, y, &t, &test_illuminant, NULL);
3599
3600 gchar *str;
3601 if(t > 1667.f && t < 25000.f)
3602 {
3603 if(test_illuminant == DT_ILLUMINANT_D)
3604 {
3605 str = g_strdup_printf(_("CCT: %.0f K (daylight)"), t);
3606 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3607 _("approximated correlated color temperature.\n"
3608 "this illuminant can be accurately modeled by a daylight spectrum,\n"
3609 "so its temperature is relevant and meaningful with a D illuminant."));
3610 }
3611 else if(test_illuminant == DT_ILLUMINANT_BB)
3612 {
3613 str = g_strdup_printf(_("CCT: %.0f K (black body)"), t);
3614 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3615 _("approximated correlated color temperature.\n"
3616 "this illuminant can be accurately modeled by a black body spectrum,\n"
3617 "so its temperature is relevant and meaningful with a Planckian illuminant."));
3618 }
3619 else
3620 {
3621 str = g_strdup_printf(_("CCT: %.0f K (invalid)"), t);
3622 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3623 _("approximated correlated color temperature.\n"
3624 "this illuminant cannot be accurately modeled by a daylight or black body spectrum,\n"
3625 "so its temperature is not relevant and meaningful and you need to use a custom illuminant."));
3626 }
3627 }
3628 else
3629 {
3630 str = g_strdup_printf(_("CCT: undefined"));
3631 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3632 _("the approximated correlated color temperature\n"
3633 "cannot be computed at all so you need to use a custom illuminant."));
3634 }
3635 gtk_label_set_text(GTK_LABEL(g->approx_cct), str);
3636 dt_free(str);
3637}
3638
3639
3640static void illum_xy_callback(GtkWidget *slider, gpointer user_data)
3641{
3642 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3643 if(darktable.gui->reset) return;
3646
3647 dt_aligned_pixel_t Lch = { 0 };
3648 Lch[0] = 100.f;
3649 Lch[2] = dt_bauhaus_slider_get(g->illum_x) / 180. * M_PI;
3650 Lch[1] = dt_bauhaus_slider_get(g->illum_y);
3651
3652 dt_aligned_pixel_t xyY = { 0 };
3653 dt_Lch_to_xyY(Lch, xyY);
3654 p->x = xyY[0];
3655 p->y = xyY[1];
3656
3657 float t = xy_to_CCT(p->x, p->y);
3658 // xy_to_CCT is valid only above 3000 K
3659 if(t < 3000.f) t = CCT_reverse_lookup(p->x, p->y);
3660 p->temperature = t;
3661
3662 ++darktable.gui->reset;
3663 dt_bauhaus_slider_set(g->temperature, p->temperature);
3664 update_approx_cct(self);
3667 --darktable.gui->reset;
3668
3669 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=illum_xy_callback slider=%p\n",
3670 (void *)slider);
3672}
3673
3679
3681{
3682 self->dev->proxy.chroma_adaptation = NULL;
3683 dt_free_align(piece->data);
3684 piece->data = NULL;
3685}
3686
3688{
3690 g->is_profiling_started = FALSE;
3692 gui_changed(self, NULL, NULL);
3693}
3694
3695void gui_update(struct dt_iop_module_t *self)
3696{
3697 dt_iop_module_t *module = (dt_iop_module_t *)self;
3700 float simple_error = INFINITY;
3701 float primaries_error = INFINITY;
3702
3704
3705 /* Restoring GUI state from params must not re-enter widget callbacks.
3706 Channelmixer's spot/color widgets can otherwise rerun auto-picking while
3707 a history refresh is already in flight, which feeds TOP_CHANGED/full-pipe
3708 loops when the picker is active. */
3709 ++darktable.gui->reset;
3710
3711 // always reset the mode the correct
3713
3714 // get the saved params
3716
3717 gboolean use_mixing = TRUE;
3718 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/use_mixing"))
3719 use_mixing = dt_conf_get_bool("darkroom/modules/channelmixerrgb/use_mixing");
3720 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->use_mixing), use_mixing);
3721
3722 float lightness = 50.f;
3723 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/lightness"))
3724 lightness = dt_conf_get_float("darkroom/modules/channelmixerrgb/lightness");
3725 dt_bauhaus_slider_set(g->lightness_spot, lightness);
3726
3727 float hue = 0.f;
3728 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/hue"))
3729 hue = dt_conf_get_float("darkroom/modules/channelmixerrgb/hue");
3730 dt_bauhaus_slider_set(g->hue_spot, hue);
3731
3732 float chroma = 0.f;
3733 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/chroma"))
3734 chroma = dt_conf_get_float("darkroom/modules/channelmixerrgb/chroma");
3735 dt_bauhaus_slider_set(g->chroma_spot, chroma);
3736
3738
3739 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->clip), p->clip);
3740 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), p->normalize_R);
3741 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), p->normalize_G);
3742 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), p->normalize_B);
3743
3744 const gboolean simple_ok = _channelmixerrgb_sync_simple_from_params(self, &simple_error);
3745 const gboolean primaries_ok = _channelmixerrgb_sync_primaries_from_params(self, &primaries_error);
3746 const dt_iop_channelmixer_rgb_mixer_mode_t requested_mode
3751 = requested_mode == DT_CHANNELMIXERRGB_MIXER_SIMPLE && simple_ok
3753 : requested_mode == DT_CHANNELMIXERRGB_MIXER_PRIMARIES && primaries_ok
3756 dt_bauhaus_combobox_set(g->mixer_mode, mixer_mode);
3758
3759 if(p->version != CHANNELMIXERRGB_V_3)
3760 dt_bauhaus_combobox_set(g->saturation_version, p->version);
3761 else
3762 gtk_widget_hide(GTK_WIDGET(g->saturation_version));
3763
3764 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_sat), p->normalize_sat);
3765 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_light), p->normalize_light);
3766 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_grey), p->normalize_grey);
3767
3769
3772
3773 const int selected_checker = dt_conf_get_int("darkroom/modules/channelmixerrgb/colorchecker");
3774 dt_bauhaus_combobox_set(g->checkers_list, selected_checker);
3775
3776 const int selected_color = dt_conf_get_int("darkroom/modules/channelmixerrgb/colorchecker_color");
3777 dt_bauhaus_combobox_set(g->checkers_color_list, selected_color);
3778
3779 const dt_colorchecker_label_t *label = ((selected_color >= 0) && g->colorcheckers_color) ? (const dt_colorchecker_label_t*)g_list_nth_data(g->colorcheckers_color, selected_color) : NULL;
3780 const char *color_path = label ? label->path : NULL;
3781
3782 dt_colorchecker_cleanup(g->checker);
3783 g->checker = dt_get_color_checker(selected_checker, &(g->colorcheckers), color_path);
3784
3785 color_list_visibility(self, selected_checker);
3786
3787 const int j = dt_conf_get_int("darkroom/modules/channelmixerrgb/optimization");
3788 dt_bauhaus_combobox_set(g->optimize, j);
3789 g->optimization = j;
3790
3791 g->safety_margin = dt_conf_get_float("darkroom/modules/channelmixerrgb/safety");
3792 dt_bauhaus_slider_set(g->safety, g->safety_margin);
3793
3795
3796 // always disable profiling mode by default
3797 g->is_profiling_started = FALSE;
3798
3801
3802 g->spot_RGB[0] = 0.f;
3803 g->spot_RGB[1] = 0.f;
3804 g->spot_RGB[2] = 0.f;
3805 g->spot_RGB[3] = 0.f;
3806
3807 --darktable.gui->reset;
3808
3809 gui_changed(self, NULL, NULL);
3810}
3811
3813{
3814 dt_iop_default_init(module);
3815
3817 d->red[0] = d->green[1] = d->blue[2] = 1.0;
3818 d->normalize_R = TRUE;
3819 d->normalize_G = TRUE;
3820 d->normalize_B = TRUE;
3821}
3822
3824{
3826
3827 d->normalize_R = TRUE;
3828 d->normalize_G = TRUE;
3829 d->normalize_B = TRUE;
3830
3831 d->x = module->get_f("x")->Float.Default;
3832 d->y = module->get_f("y")->Float.Default;
3833 d->temperature = module->get_f("temperature")->Float.Default;
3834 d->illuminant = module->get_f("illuminant")->Enum.Default;
3835 d->adaptation = module->get_f("adaptation")->Enum.Default;
3836
3837 // Note : this is not an user param anymore, and is set to modern by default
3838 // except for old histories using temperature without having an history entry for it.
3839 // see develop/develop.c/_dev_auto_apply_presets()
3840 const gboolean is_modern =
3841 dt_conf_is_equal("plugins/darkroom/chromatic-adaptation", "modern");
3842
3843 // note that if there is already an instance of this module with an
3844 // adaptation set we default to RGB (none) in this instance.
3845 // try to register the CAT here
3846 declare_cat_on_pipe(module, is_modern);
3847 const dt_image_t *img = &module->dev->image_storage;
3848
3849 // check if we could register
3850 gboolean CAT_already_applied =
3851 (!IS_NULL_PTR(module->dev->proxy.chroma_adaptation)) // CAT exists
3852 && (module->dev->proxy.chroma_adaptation != module) // and it is not us
3853 && (!dt_image_is_monochrome(img));
3854
3855 module->default_enabled = FALSE;
3856
3857 dt_aligned_pixel_t custom_wb;
3858 if(!CAT_already_applied
3859 && is_modern
3860 && !get_white_balance_coeff(module, custom_wb)
3861 && !dt_image_is_monochrome(img))
3862 {
3863 // if workflow = modern and we find WB coeffs, take care of white balance here
3864 if(find_temperature_from_raw_coeffs(img, custom_wb, &(d->x), &(d->y)))
3865 d->illuminant = DT_ILLUMINANT_CAMERA;
3866
3867 check_if_close_to_daylight(d->x, d->y, &(d->temperature), &(d->illuminant), &(d->adaptation));
3868 module->workflow_enabled = TRUE;
3869 }
3870 else
3871 {
3872 // otherwise, simple channel mixer
3873 d->illuminant = DT_ILLUMINANT_PIPE;
3874 d->adaptation = DT_ADAPTATION_RGB;
3875 }
3876
3878 if(g)
3879 {
3880 const dt_aligned_pixel_t xyY = { d->x, d->y, 1.f };
3881 dt_aligned_pixel_t Lch = { 0 };
3882 dt_xyY_to_Lch(xyY, Lch);
3883
3884 dt_bauhaus_slider_set_default(g->illum_x, Lch[2] / M_PI * 180.f);
3885 dt_bauhaus_slider_set_default(g->illum_y, Lch[1]);
3886 dt_bauhaus_slider_set_default(g->temperature, d->temperature);
3887 dt_bauhaus_combobox_set_default(g->illuminant, d->illuminant);
3888 dt_bauhaus_combobox_set_default(g->adaptation, d->adaptation);
3889 if(g->delta_E_label_text)
3890 {
3891 dt_free(g->delta_E_label_text);
3892 }
3893
3895 {
3897 dt_bauhaus_combobox_add_full(g->illuminant, _("as shot in camera"), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT,
3898 GINT_TO_POINTER(DT_ILLUMINANT_CAMERA), NULL, TRUE);
3899 }
3900 else
3902
3903 gui_changed(module, NULL, NULL);
3904 }
3905}
3906
3907
3909{
3910 if(darktable.gui->reset) return;
3911
3913
3914 dt_aligned_pixel_t Lch_target = { 0.f };
3915
3917 Lch_target[0] = dt_bauhaus_slider_get(g->lightness_spot);
3918 Lch_target[1] = dt_bauhaus_slider_get(g->chroma_spot);
3919 Lch_target[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f;
3920 const gboolean use_mixing = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->use_mixing));
3922
3923 // Save the color on change
3924 dt_conf_set_float("darkroom/modules/channelmixerrgb/lightness", Lch_target[0]);
3925 dt_conf_set_float("darkroom/modules/channelmixerrgb/chroma", Lch_target[1]);
3926 dt_conf_set_float("darkroom/modules/channelmixerrgb/hue", Lch_target[2] * 360.f);
3927 dt_conf_set_bool("darkroom/modules/channelmixerrgb/use_mixing", use_mixing);
3928
3929 ++darktable.gui->reset;
3930 paint_hue(self);
3931 --darktable.gui->reset;
3932
3933 // Re-run auto illuminant only for the module that currently owns the active picker.
3934 const dt_spot_mode_t mode = dt_bauhaus_combobox_get(g->spot_mode);
3935 const gboolean picker_active = dt_iop_color_picker_is_active_module(self);
3936 if(mode == DT_SPOT_MODE_CORRECT && picker_active)
3937 _auto_set_illuminant(self, self->dev->pipe);
3938 // else : just record new values and do nothing
3939}
3940
3943{
3944 gtk_stack_set_visible_child_name(GTK_STACK(g->mixer_stack),
3945 mode == DT_CHANNELMIXERRGB_MIXER_SIMPLE ? "simple"
3946 : mode == DT_CHANNELMIXERRGB_MIXER_PRIMARIES ? "primaries"
3947 : "complete");
3948}
3949
3950static void _channelmixerrgb_mixer_mode_callback(GtkWidget *combo, gpointer user_data)
3951{
3952 if(darktable.gui->reset) return;
3953
3954 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3958
3960
3962 {
3964 return;
3965 }
3966
3968 {
3969 float error = INFINITY;
3971 {
3972 dt_control_log(_("simple mixer mode requires all three output rows to be normalized with non-zero sums."));
3973 ++darktable.gui->reset;
3975 --darktable.gui->reset;
3978 return;
3979 }
3980
3982 gui_changed(self, NULL, NULL);
3983 return;
3984 }
3985
3987 {
3988 const float rows[3][3] = { { p->red[0], p->red[1], p->red[2] },
3989 { p->green[0], p->green[1], p->green[2] },
3990 { p->blue[0], p->blue[1], p->blue[2] } };
3991 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
3992 float M[3][3] = { { 0.f } };
3993 float error = INFINITY;
3996 {
3997 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
3998 ++darktable.gui->reset;
4000 --darktable.gui->reset;
4003 return;
4004 }
4005
4006 gboolean changed = p->normalize_R || p->normalize_G || p->normalize_B;
4007 for(int col = 0; col < 3; col++)
4008 {
4009 changed = changed || p->red[col] != M[0][col] || p->green[col] != M[1][col] || p->blue[col] != M[2][col];
4010 p->red[col] = M[0][col];
4011 p->green[col] = M[1][col];
4012 p->blue[col] = M[2][col];
4013 }
4014
4015 p->normalize_R = FALSE;
4016 p->normalize_G = FALSE;
4017 p->normalize_B = FALSE;
4018
4019 ++darktable.gui->reset;
4020 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), FALSE);
4021 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), FALSE);
4022 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), FALSE);
4023 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
4024 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
4025 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
4026 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
4027 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
4028 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
4029 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
4030 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
4031 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
4032 --darktable.gui->reset;
4033
4035 gui_changed(self, NULL, NULL);
4036
4037 if(changed)
4038 {
4039 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] history commit source=mixer_mode_primaries\n");
4041 }
4042 }
4043}
4044
4045static void _channelmixerrgb_simple_slider_callback(GtkWidget *slider, gpointer user_data)
4046{
4047 if(darktable.gui->reset) return;
4048
4049 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4052 GtkWidget *const widgets[6]
4053 = { g->simple_theta, g->simple_psi, g->simple_stretch_1, g->simple_stretch_2, g->simple_coupling_1,
4054 g->simple_coupling_2 };
4056 float M[3][3] = { { 0.f } };
4057
4060
4061 for(int col = 0; col < 3; col++)
4062 {
4063 p->red[col] = M[0][col];
4064 p->green[col] = M[1][col];
4065 p->blue[col] = M[2][col];
4066 }
4067
4068 ++darktable.gui->reset;
4069 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), p->normalize_R);
4070 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), p->normalize_G);
4071 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), p->normalize_B);
4072
4073 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
4074 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
4075 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
4076 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
4077 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
4078 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
4079 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
4080 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
4081 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
4082 --darktable.gui->reset;
4083
4084 gui_changed(self, slider, NULL);
4085
4086 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] history commit source=simple_slider slider=%p\n", (void *)slider);
4088}
4089
4090static void _channelmixerrgb_primaries_slider_callback(GtkWidget *slider, gpointer user_data)
4091{
4092 if(darktable.gui->reset) return;
4093
4094 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4097 GtkWidget *const widgets[9]
4098 = { g->primaries_achromatic_hue, g->primaries_achromatic_purity, g->primaries_red_hue,
4099 g->primaries_red_purity, g->primaries_green_hue, g->primaries_green_purity,
4100 g->primaries_blue_hue, g->primaries_blue_purity, g->primaries_gain };
4104 float M[3][3] = { { 0.f } };
4105
4107 if(!dt_iop_channelmixer_shared_primaries_to_matrix(basis, &primaries, M))
4108 {
4109 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
4110 return;
4111 }
4112
4113 for(int col = 0; col < 3; col++)
4114 {
4115 p->red[col] = M[0][col];
4116 p->green[col] = M[1][col];
4117 p->blue[col] = M[2][col];
4118 }
4119 p->normalize_R = FALSE;
4120 p->normalize_G = FALSE;
4121 p->normalize_B = FALSE;
4122
4123 ++darktable.gui->reset;
4124 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), FALSE);
4125 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), FALSE);
4126 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), FALSE);
4127
4128 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
4129 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
4130 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
4131 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
4132 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
4133 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
4134 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
4135 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
4136 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
4137 --darktable.gui->reset;
4138
4139 gui_changed(self, slider, NULL);
4140
4141 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] history commit source=primaries_slider slider=%p\n", (void *)slider);
4143}
4144
4145
4146void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
4147{
4150 const gboolean simple_widget
4151 = w == g->simple_theta || w == g->simple_psi || w == g->simple_stretch_1 || w == g->simple_stretch_2
4152 || w == g->simple_coupling_1 || w == g->simple_coupling_2;
4153 const gboolean primaries_widget
4154 = w == g->primaries_achromatic_hue || w == g->primaries_achromatic_purity || w == g->primaries_red_hue
4155 || w == g->primaries_red_purity || w == g->primaries_green_hue || w == g->primaries_green_purity
4156 || w == g->primaries_blue_hue || w == g->primaries_blue_purity || w == g->primaries_gain;
4157 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
4158 const gboolean rows_are_normalized = dt_iop_channelmixer_shared_rows_are_normalized(normalize);
4159 const gboolean complete_widget
4160 = w == g->scale_red_R || w == g->scale_red_G || w == g->scale_red_B || w == g->scale_green_R
4161 || w == g->scale_green_G || w == g->scale_green_B || w == g->scale_blue_R || w == g->scale_blue_G
4162 || w == g->scale_blue_B || w == g->normalize_R || w == g->normalize_G || w == g->normalize_B;
4163 // Some Bauhaus setters emit during gui_init(), so synchronize the illuminant group only after
4164 // every widget sharing the same xyY/Lch state has been created.
4165 const gboolean illuminant_widgets_ready
4166 = !IS_NULL_PTR(g->illuminant) && !IS_NULL_PTR(g->illum_fluo) && !IS_NULL_PTR(g->illum_led)
4167 && !IS_NULL_PTR(g->temperature) && !IS_NULL_PTR(g->illum_color) && !IS_NULL_PTR(g->illum_x)
4168 && !IS_NULL_PTR(g->illum_y);
4169
4170 if(!IS_NULL_PTR(w) && w == g->illuminant)
4171 {
4172 if(!IS_NULL_PTR(previous))
4173 {
4174 dt_illuminant_t *prev_illuminant = (dt_illuminant_t *)previous;
4175 if(*prev_illuminant == DT_ILLUMINANT_CAMERA)
4176 {
4177 // If illuminant was previously set with "as set in camera",
4178 // when changing it, we need to ensure the temperature and chromaticity
4179 // are inited with the correct values taken from camera EXIF.
4180 // Otherwise, if using a preset defining illuminant = "as set in camera",
4181 // temperature and chromaticity are inited with the preset content when illuminant is changed.
4182 dt_aligned_pixel_t custom_wb;
4183 get_white_balance_coeff(self, custom_wb);
4184 find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(p->x), &(p->y));
4185 check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation));
4186 }
4187 }
4188 if(p->illuminant == DT_ILLUMINANT_CAMERA)
4189 {
4190 // Get camera WB and update illuminant
4191 dt_aligned_pixel_t custom_wb;
4192 get_white_balance_coeff(self, custom_wb);
4193 const int found = find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(p->x), &(p->y));
4194 check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation));
4195
4196 if(found)
4197 dt_control_log(_("white balance successfully extracted from raw image"));
4198 }
4199 else if(p->illuminant == DT_ILLUMINANT_DETECT_EDGES
4200 || p->illuminant == DT_ILLUMINANT_DETECT_SURFACES)
4201 {
4202 // We need to recompute only the full preview
4203 dt_control_log(_("auto-detection of white balance started..."));
4204 }
4205 }
4206
4207 if(!IS_NULL_PTR(w) && (w == g->illuminant || w == g->illum_fluo || w == g->illum_led || w == g->temperature))
4208 {
4209 // Convert and synchronize all the possible ways to define an illuminant to allow swapping modes
4210
4211 if(p->illuminant != DT_ILLUMINANT_CUSTOM && p->illuminant != DT_ILLUMINANT_CAMERA)
4212 {
4213 // We are in any mode defining (x, y) indirectly from an interface, so commit (x, y) explicitly
4214 illuminant_to_xy(p->illuminant, NULL, NULL, &(p->x), &(p->y), p->temperature, p->illum_fluo, p->illum_led);
4215 }
4216
4217 if(p->illuminant != DT_ILLUMINANT_D && p->illuminant != DT_ILLUMINANT_BB && p->illuminant != DT_ILLUMINANT_CAMERA)
4218 {
4219 // We are in any mode not defining explicitly a temperature, so find the the closest CCT and commit it
4220 check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, NULL);
4221 }
4222 }
4223
4224 ++darktable.gui->reset;
4225
4226 if(IS_NULL_PTR(w) || w == g->hue_spot || w == g->chroma_spot || w == g->lightness_spot || w == g->spot_settings)
4227 {
4228 paint_hue(self);
4229 }
4230
4231 if((IS_NULL_PTR(w) || w == g->illuminant || w == g->illum_fluo || w == g->illum_led || w == g->temperature)
4232 && illuminant_widgets_ready)
4233 {
4234 update_illuminants(self);
4235 update_approx_cct(self);
4237
4238 // force-update all the illuminant sliders in case something above changed them
4239 // notice the hue/chroma of the illuminant has to be computed on-the-fly anyway
4240 dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
4242 dt_xyY_to_Lch(xyY, Lch);
4243
4244 // If the chroma is zero then there is not a meaningful hue angle. In this case
4245 // leave the hue slider where it was, so that if chroma is set to zero and then
4246 // set to a nonzero value, the hue setting will remain unchanged.
4247 if(Lch[1] > 0)
4248 dt_bauhaus_slider_set(g->illum_x, Lch[2] / M_PI * 180.f);
4249 dt_bauhaus_slider_set(g->illum_y, Lch[1]);
4250
4251 // Redraw the temperature background color taking new soft bounds into account
4252 dt_bauhaus_slider_set(g->temperature, p->temperature);
4254 }
4255
4256 if(w == g->adaptation)
4257 update_illuminants(self);
4258
4259 if(IS_NULL_PTR(w) || w == g->adaptation || primaries_widget || w == g->scale_red_R || w == g->scale_red_G || w == g->scale_red_B || w == g->normalize_R)
4260 _update_RGB_colors(self, 1, 0, 0, p->normalize_R, p->red, g->scale_red_R, g->scale_red_G, g->scale_red_B);
4261 if(IS_NULL_PTR(w) || w == g->adaptation || primaries_widget || w == g->scale_green_R || w == g->scale_green_G || w == g->scale_green_B || w == g->normalize_G)
4262 _update_RGB_colors(self, 0, 1, 0, p->normalize_G, p->green, g->scale_green_R, g->scale_green_G, g->scale_green_B);
4263 if(IS_NULL_PTR(w) || w == g->adaptation || primaries_widget || w == g->scale_blue_R || w == g->scale_blue_G || w == g->scale_blue_B || w == g->normalize_B)
4264 _update_RGB_colors(self, 0, 0, 1, p->normalize_B, p->blue, g->scale_blue_R, g->scale_blue_G, g->scale_blue_B);
4265
4266 if(rows_are_normalized && !simple_widget && (IS_NULL_PTR(w) || complete_widget))
4267 {
4268 float error = INFINITY;
4270 {
4272 {
4273 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] simple mixer rejected error=%g normalized=%d%d%d\n",
4274 error, p->normalize_R, p->normalize_G, p->normalize_B);
4275 dt_control_log(_("simple mixer mode requires all three output rows to be normalized with non-zero sums."));
4279 }
4280 else
4281 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] simple mixer roundtrip error=%g\n", error);
4282 }
4283 }
4284
4285 if(!primaries_widget && (IS_NULL_PTR(w) || complete_widget || simple_widget || w == g->adaptation))
4286 {
4287 float error = INFINITY;
4289 {
4291 {
4292 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] primaries mixer roundtrip error=%g\n", error);
4296 }
4297 }
4298 }
4299
4300 if(IS_NULL_PTR(w) || w == g->adaptation || complete_widget || simple_widget || primaries_widget)
4302
4303 if(IS_NULL_PTR(w) || w == g->adaptation || complete_widget || simple_widget || primaries_widget)
4305
4306 // if grey channel is used and norm = 0 and normalization = ON, we are going to have a division by zero
4307 // in commit_param, we avoid dividing by zero automatically, but user needs a notification
4308 if((p->grey[0] != 0.f) || (p->grey[1] != 0.f) || (p->grey[2] != 0.f))
4309 if((p->grey[0] + p->grey[1] + p->grey[2] == 0.f) && p->normalize_grey)
4310 dt_control_log(_("color calibration: the sum of the gray channel parameters is zero, normalization will be disabled."));
4311
4312 // If "as shot in camera" illuminant is used, CAT space is forced automatically
4313 // therefore, make the control insensitive
4314 gtk_widget_set_sensitive(g->adaptation, p->illuminant != DT_ILLUMINANT_CAMERA);
4315 gtk_widget_queue_draw(g->adaptation);
4316
4318
4319 --darktable.gui->reset;
4320}
4321
4323{
4326 const dt_iop_channelmixer_rgb_params_t previous = *p;
4327
4328 // capture gui color picked event.
4329 if(self->picked_color_max[0] < self->picked_color_min[0])
4330 {
4331 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] rejected invalid min/max min=%g max=%g\n",
4332 self->picked_color_min[0], self->picked_color_max[0]);
4333 return;
4334 }
4335 const float *RGB = self->picked_color;
4336 if(!isfinite(RGB[0]) || !isfinite(RGB[1]) || !isfinite(RGB[2]))
4337 {
4338 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] rejected nonfinite RGB=(%g,%g,%g)\n",
4339 RGB[0], RGB[1], RGB[2]);
4340 return;
4341 }
4342 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] RGB=(%g,%g,%g) pipe=%p\n",
4343 RGB[0], RGB[1], RGB[2], (void *)pipe);
4344
4345 // Get the module-stage profile matching the sampled buffer.
4346 const dt_iop_order_iccprofile_info_t *const current_profile
4348 if(IS_NULL_PTR(current_profile))
4349 {
4350 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] missing current profile\n");
4351 return;
4352 }
4353
4354 // Convert the sampled linear RGB code values to XYZ in the module profile.
4355 // The picker already sampled the live module buffer, so keep the conversion explicit here.
4356 dt_aligned_pixel_t XYZ = { 0.f };
4357 dot_product(RGB, current_profile->matrix_in, XYZ);
4358 if(!isfinite(XYZ[0]) || !isfinite(XYZ[1]) || !isfinite(XYZ[2])) return;
4359 dt_XYZ_to_sRGB(XYZ, g->spot_RGB);
4360
4361 // Convert to Lch for GUI feedback (input)
4365 dt_Lab_2_LCH(Lab, Lch);
4366
4367 // Write report in GUI
4368 ++darktable.gui->reset;
4369 gtk_label_set_text(GTK_LABEL(g->Lch_origin),
4370 g_strdup_printf(_("L: \t%.1f %%\nh: \t%.1f \302\260\nc: \t%.1f"),
4371 Lch[0], Lch[2] * 360.f, Lch[1] ));
4372 gtk_widget_queue_draw(g->origin_spot);
4373 --darktable.gui->reset;
4374
4375 const dt_spot_mode_t mode = dt_bauhaus_combobox_get(g->spot_mode);
4376 const gboolean use_mixing = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->use_mixing));
4377
4378 // build the channel mixing matrix - keep in synch with commit_params()
4379 dt_colormatrix_t MIX = { { 0.f } };
4380
4381 float norm_R = 1.0f;
4382 if(p->normalize_R) norm_R = p->red[0] + p->red[1] + p->red[2];
4383
4384 float norm_G = 1.0f;
4385 if(p->normalize_G) norm_G = p->green[0] + p->green[1] + p->green[2];
4386
4387 float norm_B = 1.0f;
4388 if(p->normalize_B) norm_B = p->blue[0] + p->blue[1] + p->blue[2];
4389
4390 for(int i = 0; i < 3; i++)
4391 {
4392 MIX[0][i] = p->red[i] / norm_R;
4393 MIX[1][i] = p->green[i] / norm_G;
4394 MIX[2][i] = p->blue[i] / norm_B;
4395 }
4396
4397 if(mode == DT_SPOT_MODE_MEASURE)
4398 {
4399 // Keep the following in sync with commit_params()
4400
4401 // find x y coordinates of illuminant for CIE 1931 2° observer
4402 float x = p->x;
4403 float y = p->y;
4404 dt_adaptation_t adaptation = p->adaptation;
4405 dt_aligned_pixel_t custom_wb;
4406 get_white_balance_coeff(self, custom_wb);
4407 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led);
4408
4409 // if illuminant is set as camera, x and y are set on-the-fly at commit time, so we need to set adaptation too
4410 if(p->illuminant == DT_ILLUMINANT_CAMERA) check_if_close_to_daylight(x, y, NULL, NULL, &adaptation);
4411
4412 // Convert illuminant from xyY to XYZ
4413 dt_aligned_pixel_t XYZ_illuminant = { 0.f };
4414 illuminant_xy_to_XYZ(x, y, XYZ_illuminant);
4415
4416 // Convert illuminant from XYZ to Bradford modified LMS
4417 dt_aligned_pixel_t LMS_illuminant = { 0.f };
4418 dt_store_simd_aligned(LMS_illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_illuminant), adaptation));
4419
4420 // For the non-linear Bradford
4421 const float pp = powf(0.818155f / LMS_illuminant[2], 0.0834f);
4422
4423 //fprintf(stdout, "illuminant: %i\n", p->illuminant);
4424 //fprintf(stdout, "x: %f, y: %f\n", x, y);
4425 //fprintf(stdout, "X: %f - Y: %f - Z: %f\n", XYZ_illuminant[0], XYZ_illuminant[1], XYZ_illuminant[2]);
4426 //fprintf(stdout, "L: %f - M: %f - S: %f\n", LMS_illuminant[0], LMS_illuminant[1], LMS_illuminant[2]);
4427
4428 // Finally, chroma-adapt the pixel
4429 dt_aligned_pixel_t XYZ_output = { 0.f };
4430 dt_store_simd_aligned(XYZ_output, chroma_adapt_pixel(dt_load_simd_aligned(XYZ),
4431 dt_load_simd_aligned(LMS_illuminant),
4432 adaptation, pp));
4433
4434 // Optionaly, apply the channel mixing
4435 if(use_mixing)
4436 {
4437 dt_aligned_pixel_t LMS_output = { 0.f };
4438 dt_store_simd_aligned(LMS_output, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_output), adaptation));
4439 dt_aligned_pixel_t temp = { 0.f };
4440 dot_product(LMS_output, MIX, temp);
4441 dt_store_simd_aligned(XYZ_output, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), adaptation));
4442 }
4443
4444 // Convert to Lab and Lch for GUI feedback
4445 dt_aligned_pixel_t Lab_output = { 0.f };
4446 dt_aligned_pixel_t Lch_output = { 0.f };
4447 dt_XYZ_to_Lab(XYZ_output, Lab_output);
4448 dt_Lab_2_LCH(Lab_output, Lch_output);
4449
4450 // Return the values in sliders
4451 ++darktable.gui->reset;
4452 dt_bauhaus_slider_set(g->lightness_spot, Lch_output[0]);
4453 dt_bauhaus_slider_set(g->chroma_spot, Lch_output[1]);
4454 dt_bauhaus_slider_set(g->hue_spot, Lch_output[2] * 360.f);
4455 paint_hue(self);
4456 --darktable.gui->reset;
4457
4458 dt_conf_set_float("darkroom/modules/channelmixerrgb/lightness", Lch_output[0]);
4459 dt_conf_set_float("darkroom/modules/channelmixerrgb/chroma", Lch_output[1]);
4460 dt_conf_set_float("darkroom/modules/channelmixerrgb/hue", Lch_output[2] * 360.f);
4461 dt_conf_set_bool("darkroom/modules/channelmixerrgb/use_mixing", use_mixing);
4462 }
4463 else if(mode == DT_SPOT_MODE_CORRECT)
4464 {
4465 // Get the target color in LMS space
4466 dt_aligned_pixel_t Lch_target = { 0.f };
4467 dt_aligned_pixel_t Lab_target = { 0.f };
4468 dt_aligned_pixel_t XYZ_target = { 0.f };
4469 dt_aligned_pixel_t LMS_target = { 0.f };
4470
4472 Lch_target[0] = dt_bauhaus_slider_get(g->lightness_spot);
4473 Lch_target[1] = dt_bauhaus_slider_get(g->chroma_spot);
4474 Lch_target[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f;
4476
4477 dt_LCH_2_Lab(Lch_target, Lab_target);
4478 dt_Lab_to_XYZ(Lab_target, XYZ_target);
4479 const float Y_target = XYZ_target[1];
4480 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_target;
4481 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4482
4483 // optionaly, apply the inverse mixing on the target
4484 if(use_mixing)
4485 {
4486 // Repack the MIX matrix to 3x3 to support the pseudoinverse function
4487 // I'm just too lazy to rewrite the pseudo-inverse for 3x4 padded input
4488 float MIX_3x3[9];
4489 pack_3xSSE_to_3x3(MIX, MIX_3x3);
4490
4491 /* DEBUG
4492 fprintf(stdout, "Repacked channel mixer matrix :\n");
4493 fprintf(stdout, "%f \t%f \t%f\n", MIX_3x3[0][0], MIX_3x3[0][1], MIX_3x3[0][2]);
4494 fprintf(stdout, "%f \t%f \t%f\n", MIX_3x3[1][0], MIX_3x3[1][1], MIX_3x3[1][2]);
4495 fprintf(stdout, "%f \t%f \t%f\n", MIX_3x3[2][0], MIX_3x3[2][1], MIX_3x3[2][2]);
4496 */
4497
4498 // Invert the matrix
4499 float MIX_INV_3x3[9];
4500 matrice_pseudoinverse((float (*)[3])MIX_3x3, (float (*)[3])MIX_INV_3x3, 3);
4501
4502 // Transpose and repack the inverse to SSE matrix because the inversion transposes too
4503 dt_colormatrix_t MIX_INV;
4504 transpose_3x3_to_3xSSE(MIX_INV_3x3, MIX_INV);
4505
4506 /* DEBUG
4507 fprintf(stdout, "Repacked inverted channel mixer matrix :\n");
4508 fprintf(stdout, "%f \t%f \t%f\n", MIX_INV[0][0], MIX_INV[0][1], MIX_INV[0][2]);
4509 fprintf(stdout, "%f \t%f \t%f\n", MIX_INV[1][0], MIX_INV[1][1], MIX_INV[1][2]);
4510 fprintf(stdout, "%f \t%f \t%f\n", MIX_INV[2][0], MIX_INV[2][1], MIX_INV[2][2]);
4511 */
4512
4513 // Undo the channel mixing on the reference color
4514 // So we get the expected target color after the CAT
4515 dt_aligned_pixel_t temp;
4516 dot_product(LMS_target, MIX_INV, temp);
4517
4518 //fprintf(stdout, "LMS before channel mixer inversion : \t%f \t%f \t%f\n", LMS_target[0], LMS_target[1], LMS_target[2]);
4519 //fprintf(stdout, "LMS after channel mixer inversion : \t%f \t%f \t%f\n", temp[0], temp[1], temp[2]);
4520
4521 // convert back to XYZ to normalize luminance again
4522 // in case the matrix is not normalized
4523 dt_store_simd_aligned(XYZ_target, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), p->adaptation));
4524 const float Y_mix = XYZ_target[1];
4525 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_mix;
4526 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4527
4528 //fprintf(stdout, "LMS target after everything : %f \t%f \t%f\n", LMS_target[0], LMS_target[1], LMS_target[2]);
4529
4530 // So now we got the target color after CAT and before mixing
4531 // in LMS space
4532 }
4533
4534 // Get the input color in LMS space
4535 dt_aligned_pixel_t LMS = { 0.f };
4536 const float Y = XYZ[1];
4537 for(int c = 0; c < 3; c++) XYZ[c] /= Y;
4538 dt_store_simd_aligned(LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), p->adaptation));
4539
4540 // Find the illuminant
4542 convert_D50_to_LMS(p->adaptation, D50);
4543
4544 dt_aligned_pixel_t illuminant_LMS = { 0.f };
4545 dt_aligned_pixel_t illuminant_XYZ = { 0.f };
4546
4547 // We solve the equation : target color / input color = D50 / illuminant, for illuminant
4548 for(int c = 0; c < 3; c++) illuminant_LMS[c] = D50[c] * LMS[c] / LMS_target[c];
4549 dt_store_simd_aligned(illuminant_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(illuminant_LMS), p->adaptation));
4550
4551 // Convert to xyY
4552 const float sum = fmaxf(illuminant_XYZ[0] + illuminant_XYZ[1] + illuminant_XYZ[2], NORM_MIN);
4553 illuminant_XYZ[0] /= sum; // x
4554 illuminant_XYZ[2] = illuminant_XYZ[1]; // Y
4555 illuminant_XYZ[1] /= sum; // y
4556
4557 p->x = illuminant_XYZ[0];
4558 p->y = illuminant_XYZ[1];
4559
4560 // Force illuminant to custom, the daylight/black body approximations are
4561 // not accurate enough for color matching
4562 p->illuminant = DT_ILLUMINANT_CUSTOM;
4563
4564 ++darktable.gui->reset;
4565
4566 check_if_close_to_daylight(p->x, p->y, &p->temperature, NULL, NULL);
4567
4568 dt_bauhaus_slider_set(g->temperature, p->temperature);
4569 dt_bauhaus_combobox_set(g->illuminant, p->illuminant);
4570 dt_bauhaus_combobox_set(g->adaptation, p->adaptation);
4571
4572 const dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
4573 dt_aligned_pixel_t Lch_illuminant = { 0 };
4574 dt_xyY_to_Lch(xyY, Lch_illuminant);
4575 dt_bauhaus_slider_set(g->illum_x, Lch_illuminant[2] / M_PI * 180.f);
4576 dt_bauhaus_slider_set(g->illum_y, Lch_illuminant[1]);
4577
4578 update_illuminants(self);
4579 update_approx_cct(self);
4581 paint_hue(self);
4583 gtk_widget_queue_draw(g->origin_spot);
4584
4585 --darktable.gui->reset;
4586
4587 if(memcmp(&previous, p, sizeof(previous)) != 0)
4588 {
4589 /* Auto-illuminant is driven by live picker motion. Writing history synchronously for every
4590 sampled move can outpace the preview worker and keep it permanently in TOP_CHANGED.
4591 Queue the standard throttled history update instead so the picker remains live while
4592 the worker converges on the latest sampled state. */
4593 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=auto_set_illuminant\n");
4595 }
4596 }
4597}
4598
4599
4601{
4602 if(darktable.gui->reset) return;
4603 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] apply picker=%p pipe=%p\n", (void *)picker, (void *)pipe);
4604 _auto_set_illuminant(self, pipe);
4605}
4606
4607
4608void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
4609 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
4610{
4611 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4613 if(piece->dsc_in.channels != 4) return;
4614
4615 const dt_iop_order_iccprofile_info_t *const current_profile
4617 if(IS_NULL_PTR(current_profile)) return;
4618
4619 dt_colormatrix_t MIX = { { 0.f } };
4620
4621 float norm_R = 1.0f;
4622 if(p->normalize_R) norm_R = p->red[0] + p->red[1] + p->red[2];
4623
4624 float norm_G = 1.0f;
4625 if(p->normalize_G) norm_G = p->green[0] + p->green[1] + p->green[2];
4626
4627 float norm_B = 1.0f;
4628 if(p->normalize_B) norm_B = p->blue[0] + p->blue[1] + p->blue[2];
4629
4630 for(int c = 0; c < 3; c++)
4631 {
4632 MIX[0][c] = p->red[c] / norm_R;
4633 MIX[1][c] = p->green[c] / norm_G;
4634 MIX[2][c] = p->blue[c] / norm_B;
4635 }
4636
4637 float lightness = 50.f;
4638 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/lightness"))
4639 lightness = dt_conf_get_float("darkroom/modules/channelmixerrgb/lightness");
4640
4641 float hue = 0.f;
4642 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/hue"))
4643 hue = dt_conf_get_float("darkroom/modules/channelmixerrgb/hue");
4644
4645 float chroma = 0.f;
4646 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/chroma"))
4647 chroma = dt_conf_get_float("darkroom/modules/channelmixerrgb/chroma");
4648
4649 dt_aligned_pixel_t Lch_target = { lightness, chroma, hue / 360.f, 0.f };
4650 dt_aligned_pixel_t Lab_target = { 0.f };
4651 dt_aligned_pixel_t XYZ_target = { 0.f };
4652 dt_aligned_pixel_t LMS_target = { 0.f };
4653 dt_LCH_2_Lab(Lch_target, Lab_target);
4654 dt_Lab_to_XYZ(Lab_target, XYZ_target);
4655
4656 // Normalize for unit luminance (illuminant)
4657 const float Y_target = XYZ_target[1];
4658 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_target;
4659 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4660
4661 float MIX_3x3[9];
4662 pack_3xSSE_to_3x3(MIX, MIX_3x3);
4663
4664 float MIX_INV_3x3[9];
4665 matrice_pseudoinverse((float (*)[3])MIX_3x3, (float (*)[3])MIX_INV_3x3, 3);
4666
4667 dt_colormatrix_t MIX_INV;
4668 transpose_3x3_to_3xSSE(MIX_INV_3x3, MIX_INV);
4669
4670 dt_aligned_pixel_t temp = { 0.f };
4671 dot_product(LMS_target, MIX_INV, temp);
4672
4673 dt_store_simd_aligned(XYZ_target, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), p->adaptation));
4674 const float Y_mix = XYZ_target[1];
4675 if(Y_mix <= NORM_MIN) return;
4676 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_mix;
4677 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4678
4679 const float *const restrict in = (float *)i;
4680 float average_L = 0.f;
4681 float average_M = 0.f;
4682 float average_S = 0.f;
4683 size_t valid_pixels = 0;
4684
4685 // Compute average LMS in the image
4686 __OMP_PARALLEL_FOR__(reduction(+:average_L, average_M, average_S, valid_pixels))
4687 for(size_t k = 0; k < roi_out->width * roi_out->height * 4; k += 4)
4688 {
4689 // Convert each input pixel to the module CAT space first, then average the chromaticity there.
4690 dt_aligned_pixel_t XYZ = { 0.f };
4691 dt_aligned_pixel_t LMS = { 0.f };
4692 dt_ioppr_rgb_matrix_to_xyz(in + k, XYZ, current_profile->matrix_in_transposed, current_profile->lut_in,
4693 current_profile->unbounded_coeffs_in, current_profile->lutsize,
4694 current_profile->nonlinearlut);
4695
4696 dt_store_simd_aligned(LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), p->adaptation));
4697 if(!isfinite(LMS[0]) || !isfinite(LMS[1]) || !isfinite(LMS[2])) continue;
4698
4699 average_L += LMS[0];
4700 average_M += LMS[1];
4701 average_S += LMS[2];
4702 valid_pixels++;
4703 }
4704
4705 if(valid_pixels == 0) return;
4706
4707 const float norm = 1.f / (float)valid_pixels;
4708 dt_aligned_pixel_t average_LMS = { average_L * norm, average_M * norm, average_S * norm, 0.f };
4709
4710 // Normalize for unit luminance
4711 dt_aligned_pixel_t average_XYZ = { 0.f };
4712 dt_store_simd_aligned(average_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(average_LMS), p->adaptation));
4713 const float Y_average_lms = average_XYZ[1];
4714 for(int c = 0; c < 3; c++) average_XYZ[c] /= Y_average_lms;
4715 dt_store_simd_aligned(average_LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(average_XYZ), p->adaptation));
4716
4717 dt_aligned_pixel_t D50 = { 0.f };
4718 dt_aligned_pixel_t illuminant_LMS = { 0.f };
4719 dt_aligned_pixel_t illuminant_XYZ = { 0.f };
4720 convert_D50_to_LMS(p->adaptation, D50);
4721
4722 for(int c = 0; c < 3; c++)
4723 {
4724 const float target = copysignf(fmaxf(fabsf(LMS_target[c]), NORM_MIN), LMS_target[c]);
4725 illuminant_LMS[c] = D50[c] * average_LMS[c] / target;
4726 }
4727
4728 dt_store_simd_aligned(illuminant_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(illuminant_LMS), p->adaptation));
4729
4730 const float sum = fmaxf(illuminant_XYZ[0] + illuminant_XYZ[1] + illuminant_XYZ[2], NORM_MIN);
4731 p->x = illuminant_XYZ[0] / sum;
4732 p->y = illuminant_XYZ[1] / sum;
4733 p->illuminant = DT_ILLUMINANT_CUSTOM;
4734 check_if_close_to_daylight(p->x, p->y, &p->temperature, NULL, NULL);
4735}
4736
4737
4738void gui_init(struct dt_iop_module_t *self)
4739{
4741
4742 // Init the color checker UI
4743 for(size_t k = 0; k < 4; k++)
4744 {
4745 g->box[k].x = g->box[k].y = -1.;
4746 g->active_node[k] = FALSE;
4747 }
4748 g->is_cursor_close = FALSE;
4749 g->drag_drop = FALSE;
4750 g->is_profiling_started = FALSE;
4751 g->run_profile = FALSE;
4752 g->run_validation = FALSE;
4753 g->profile_ready = FALSE;
4754 g->checker_ready = FALSE;
4755 g->delta_E_in = NULL;
4756 g->delta_E_label_text = NULL;
4757 g->colorcheckers = NULL;
4758
4759 g->XYZ[0] = NAN;
4760
4762 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
4764 G_CALLBACK(_preview_pipe_finished_callback), self);
4765
4766 // Init GTK notebook
4767 g->notebook = dt_ui_notebook_new();
4768
4769 // Page CAT
4770 self->widget = dt_ui_notebook_page(g->notebook, N_("CAT"), _("chromatic adaptation transform"));
4771
4772 g->adaptation = dt_bauhaus_combobox_from_params(self, N_("adaptation"));
4773 gtk_widget_set_tooltip_text(GTK_WIDGET(g->adaptation),
4774 _("choose the method to adapt the illuminant\n"
4775 "and the colorspace in which the module works: \n"
4776 "- Linear Bradford (1985) is consistent with ICC v4 toolchain.\n"
4777 "- CAT16 (2016) is more robust and accurate.\n"
4778 "- Non-linear Bradford (1985) is the original Bradford,\n"
4779 " it can produce better results than the linear version, but is unreliable.\n"
4780 "- XYZ is a simple scaling in XYZ space. It is not recommended in general.\n"
4781 "- none disables any adaptation and uses pipeline working RGB."));
4782
4783 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4784
4785 g->approx_cct = dt_ui_label_new("CCT:");
4786 gtk_box_pack_start(GTK_BOX(hbox), g->approx_cct, FALSE, FALSE, 0);
4787
4788 g->illum_color = GTK_WIDGET(gtk_drawing_area_new());
4789 gtk_widget_set_size_request(g->illum_color, 2 * DT_PIXEL_APPLY_DPI(darktable.bauhaus->quad_width),
4791 gtk_widget_set_tooltip_text(GTK_WIDGET(g->illum_color),
4792 _("this is the color of the scene illuminant before chromatic adaptation\n"
4793 "this color will be turned into pure white by the adaptation."));
4794
4795 g_signal_connect(G_OBJECT(g->illum_color), "draw", G_CALLBACK(illuminant_color_draw), self);
4796 gtk_box_pack_start(GTK_BOX(hbox), g->illum_color, TRUE, TRUE, 0);
4797
4798 g->color_picker = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, hbox);
4799 gtk_widget_set_tooltip_text(g->color_picker, _("set white balance to detected from area"));
4800
4801 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), FALSE, FALSE, 0);
4802
4803 g->illuminant = dt_bauhaus_combobox_from_params(self, N_("illuminant"));
4804
4805 g->illum_fluo = dt_bauhaus_combobox_from_params(self, "illum_fluo");
4806
4807 g->illum_led = dt_bauhaus_combobox_from_params(self, "illum_led");
4808
4809 g->temperature = dt_bauhaus_slider_from_params(self, N_("temperature"));
4810
4812 dt_bauhaus_widget_set_label(g->illum_x, N_("hue"));
4813 dt_bauhaus_slider_set_format(g->illum_x, "\302\260");
4814 g_signal_connect(G_OBJECT(g->illum_x), "value-changed", G_CALLBACK(illum_xy_callback), self);
4815 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->illum_x), FALSE, FALSE, 0);
4816
4817 g->illum_y = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0., 100., 0, 0, 1);
4818 dt_bauhaus_widget_set_label(g->illum_y, N_("chroma"));
4819 dt_bauhaus_slider_set_format(g->illum_y, "%");
4821 g_signal_connect(G_OBJECT(g->illum_y), "value-changed", G_CALLBACK(illum_xy_callback), self);
4822 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->illum_y), FALSE, FALSE, 0);
4823
4824 dt_bauhaus_slider_set_soft_range(g->temperature, 3000., 7000.);
4825 dt_bauhaus_slider_set_digits(g->temperature, 0);
4826 dt_bauhaus_slider_set_format(g->temperature, " K");
4827
4828 g->gamut = dt_bauhaus_slider_from_params(self, "gamut");
4829 dt_bauhaus_slider_set_soft_max(g->gamut, 4.f);
4830
4831 g->clip = dt_bauhaus_toggle_from_params(self, "clip");
4832
4833 // Add the color mapping collapsible panel
4834
4836 (&g->csspot,
4837 "plugins/darkroom/channelmixerrgb/expand_picker_mapping",
4838 _("spot color mapping"),
4839 GTK_BOX(self->widget), GTK_PACK_END);
4840
4841 gtk_widget_set_tooltip_text(g->csspot.expander, _("use a color checker target to autoset CAT and channels"));
4842
4843 DT_BAUHAUS_COMBOBOX_NEW_FULL(darktable.bauhaus, g->spot_mode, DT_GUI_MODULE(self), N_("spot mode"),
4844 _("\"correction\" automatically adjust the illuminant\n"
4845 "such that the input color is mapped to the target.\n"
4846 "\"measure\" simply shows how an input color is mapped by the CAT\n"
4847 "and can be used to sample a target."),
4848 0, NULL, self,
4849 N_("correction"),
4850 N_("measure"));
4851 gtk_box_pack_start(GTK_BOX(g->csspot.container), GTK_WIDGET(g->spot_mode), TRUE, TRUE, 0);
4852 g_signal_connect(G_OBJECT(g->spot_mode), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4853
4854 gchar *label = N_("take channel mixing into account");
4855 g->use_mixing = gtk_check_button_new_with_label(_(label));
4856 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->use_mixing))), PANGO_ELLIPSIZE_END);
4857 gtk_widget_set_tooltip_text(g->use_mixing,
4858 _("compute the target by taking the channel mixing into account.\n"
4859 "if disabled, only the CAT is considered."));
4860 gtk_box_pack_start(GTK_BOX(g->csspot.container), GTK_WIDGET(g->use_mixing), TRUE, TRUE, 0);
4861 g_signal_connect(G_OBJECT(g->use_mixing), "toggled", G_CALLBACK(_spot_settings_changed_callback), self);
4862
4863 GtkWidget *hhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4864 GtkWidget *vvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4865
4866 gtk_box_pack_start(GTK_BOX(vvbox), dt_ui_section_label_new(_("input")), FALSE, FALSE, 0);
4867
4868 g->origin_spot = GTK_WIDGET(gtk_drawing_area_new());
4869 gtk_widget_set_size_request(g->origin_spot, 2 * DT_PIXEL_APPLY_DPI(darktable.bauhaus->quad_width),
4871 gtk_widget_set_tooltip_text(GTK_WIDGET(g->origin_spot),
4872 _("the input color that should be mapped to the target"));
4873
4874 g_signal_connect(G_OBJECT(g->origin_spot), "draw", G_CALLBACK(origin_color_draw), self);
4875 gtk_box_pack_start(GTK_BOX(vvbox), g->origin_spot, TRUE, TRUE, 0);
4876
4877 g->Lch_origin = gtk_label_new(_("L: \tN/A\nh: \tN/A\nc: \tN/A"));
4878 gtk_widget_set_tooltip_text(GTK_WIDGET(g->Lch_origin),
4879 _("these LCh coordinates are computed from CIE Lab 1976 coordinates"));
4880 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->Lch_origin), FALSE, FALSE, 0);
4881
4882 gtk_box_pack_start(GTK_BOX(hhbox), GTK_WIDGET(vvbox), FALSE, FALSE, DT_BAUHAUS_SPACE);
4883
4884 vvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4885
4886 gtk_box_pack_start(GTK_BOX(vvbox), dt_ui_section_label_new(_("target")), TRUE, TRUE, 0);
4887
4888 g->target_spot = GTK_WIDGET(gtk_drawing_area_new());
4889 gtk_widget_set_size_request(g->target_spot, 2 * DT_PIXEL_APPLY_DPI(darktable.bauhaus->quad_width),
4891 gtk_widget_set_tooltip_text(GTK_WIDGET(g->target_spot),
4892 _("the desired target color after mapping"));
4893
4894 g_signal_connect(G_OBJECT(g->target_spot), "draw", G_CALLBACK(target_color_draw), self);
4895 gtk_box_pack_start(GTK_BOX(vvbox), g->target_spot, TRUE, TRUE, 0);
4896
4897 g->lightness_spot = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0., LIGHTNESS_MAX, 0, 0, 1);
4898 dt_bauhaus_widget_set_label(g->lightness_spot, N_("lightness"));
4899 dt_bauhaus_slider_set_format(g->lightness_spot, "%");
4900 dt_bauhaus_slider_set_default(g->lightness_spot, 50.f);
4901 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->lightness_spot), TRUE, TRUE, 0);
4902 g_signal_connect(G_OBJECT(g->lightness_spot), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4903
4905 dt_bauhaus_widget_set_label(g->hue_spot, N_("hue"));
4906 dt_bauhaus_slider_set_format(g->hue_spot, "\302\260");
4907 dt_bauhaus_slider_set_default(g->hue_spot, 0.f);
4908 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->hue_spot), TRUE, TRUE, 0);
4909 g_signal_connect(G_OBJECT(g->hue_spot), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4910
4911 g->chroma_spot = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0., CHROMA_MAX, 0, 0, 1);
4912 dt_bauhaus_widget_set_label(g->chroma_spot, N_("chroma"));
4913 dt_bauhaus_slider_set_default(g->chroma_spot, 0.f);
4914 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->chroma_spot), TRUE, TRUE, 0);
4915 g_signal_connect(G_OBJECT(g->chroma_spot), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4916
4917 gtk_box_pack_start(GTK_BOX(hhbox), GTK_WIDGET(vvbox), TRUE, TRUE, DT_BAUHAUS_SPACE);
4918
4919 gtk_box_pack_start(GTK_BOX(g->csspot.container), GTK_WIDGET(hhbox), FALSE, FALSE, 0);
4920
4921 GtkWidget *mixer_page = dt_ui_notebook_page(g->notebook, N_("Mixer"), _("channel mixing"));
4923 dt_bauhaus_widget_set_label(g->mixer_mode, N_("mode"));
4924 dt_bauhaus_combobox_add(g->mixer_mode, _("Complete"));
4925 dt_bauhaus_combobox_add(g->mixer_mode, _("Simple"));
4926 dt_bauhaus_combobox_add(g->mixer_mode, _("Primaries"));
4927 gtk_widget_set_tooltip_text(g->mixer_mode,
4928 _("complete exposes the original nine mixer coefficients.\n"
4929 "simple rebuilds the normalized mixer as an exact chroma-plane rotation,\n"
4930 "two signed stretches and two neutral couplings.\n"
4931 "primaries rebuilds the mixer as a generalized primaries, white tint and gain model."));
4932 gtk_box_pack_start(GTK_BOX(mixer_page), GTK_WIDGET(g->mixer_mode), FALSE, FALSE, 0);
4933 g_signal_connect(G_OBJECT(g->mixer_mode), "value-changed", G_CALLBACK(_channelmixerrgb_mixer_mode_callback), self);
4934
4935 g->mixer_stack = gtk_stack_new();
4936 gtk_stack_set_transition_type(GTK_STACK(g->mixer_stack), GTK_STACK_TRANSITION_TYPE_NONE);
4937 gtk_box_pack_start(GTK_BOX(mixer_page), GTK_WIDGET(g->mixer_stack), FALSE, FALSE, 0);
4938
4939 GtkWidget *mixer_complete = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4940 gtk_stack_add_named(GTK_STACK(g->mixer_stack), mixer_complete, "complete");
4941
4942 GtkWidget *first, *second, *third;
4943#define MIXER_ROW(var, short, section, swap) \
4944 gtk_box_pack_start(GTK_BOX(mixer_complete), dt_ui_section_label_new(section), FALSE, FALSE, 0); \
4945 self->widget = mixer_complete; \
4946 first = dt_bauhaus_slider_from_params(self, swap ? #var "[2]" : #var "[0]");\
4947 dt_bauhaus_slider_set_digits(first, 3); \
4948 dt_bauhaus_widget_set_label(first, N_("input R")); \
4949 second = dt_bauhaus_slider_from_params(self, #var "[1]"); \
4950 dt_bauhaus_slider_set_digits(second, 3); \
4951 dt_bauhaus_widget_set_label(second, N_("input G")); \
4952 third = dt_bauhaus_slider_from_params(self, swap ? #var "[0]" : #var "[2]");\
4953 dt_bauhaus_slider_set_digits(third, 3); \
4954 dt_bauhaus_widget_set_label(third, N_("input B")); \
4955 g->scale_##var##_R = swap ? third : first; \
4956 g->scale_##var##_G = second; \
4957 g->scale_##var##_B = swap ? first : third; \
4958 g->normalize_##short = dt_bauhaus_toggle_from_params(self, "normalize_" #short);
4959
4960 MIXER_ROW(red, R, _("output red"), FALSE)
4961 MIXER_ROW(green, G, _("output green"), FALSE)
4962 MIXER_ROW(blue, B, _("output blue"), FALSE)
4963
4964 GtkWidget *mixer_simple = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4965 gtk_stack_add_named(GTK_STACK(g->mixer_stack), mixer_simple, "simple");
4966
4967 g->simple_theta = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
4968 dt_bauhaus_widget_set_label(g->simple_theta, N_("global hue rotation"));
4969 dt_bauhaus_slider_set_factor(g->simple_theta, 180.f);
4970 dt_bauhaus_slider_set_format(g->simple_theta, "\302\260");
4971 gtk_widget_set_tooltip_text(g->simple_theta, _("global rotation of the normalized chroma plane."));
4972 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_theta), FALSE, FALSE, 0);
4973 g_signal_connect(G_OBJECT(g->simple_theta), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
4974
4975 gtk_box_pack_start(GTK_BOX(mixer_simple), dt_ui_section_label_new(_("chroma")), FALSE, FALSE, 0);
4976
4977 g->simple_psi = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
4978 dt_bauhaus_widget_set_label(g->simple_psi, N_("chroma (u,v) axes orientation"));
4979 dt_bauhaus_slider_set_factor(g->simple_psi, 90.f);
4980 dt_bauhaus_slider_set_format(g->simple_psi, "\302\260");
4981 gtk_widget_set_tooltip_text(g->simple_psi, _("orientation of the principal stretch axes in the chroma plane."));
4982 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_psi), FALSE, FALSE, 0);
4983 g_signal_connect(G_OBJECT(g->simple_psi), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
4984
4985 g->simple_stretch_1 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
4986 dt_bauhaus_widget_set_label(g->simple_stretch_1, N_("u stretch"));
4987 gtk_widget_set_tooltip_text(g->simple_stretch_1, _("stretch along the first principal chroma axis. 0 neutralizes chroma, 1 keeps identity, -1 reverses chroma and +/-1.5 add contrast."));
4988 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_stretch_1), FALSE, FALSE, 0);
4989 g_signal_connect(G_OBJECT(g->simple_stretch_1), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
4990
4991 g->simple_stretch_2 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
4992 dt_bauhaus_widget_set_label(g->simple_stretch_2, N_("v stretch"));
4993 gtk_widget_set_tooltip_text(g->simple_stretch_2, _("stretch along the second principal chroma axis. 0 neutralizes chroma, 1 keeps identity, -1 reverses chroma and +/-1.5 add contrast."));
4994 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_stretch_2), FALSE, FALSE, 0);
4995 g_signal_connect(G_OBJECT(g->simple_stretch_2), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
4996
4997 gtk_box_pack_start(GTK_BOX(mixer_simple), dt_ui_section_label_new(_("achromatic coupling")), FALSE, FALSE, 0);
4998
4999 g->simple_coupling_2 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5000 dt_bauhaus_widget_set_label(g->simple_coupling_2, N_("achromatic coupling hue"));
5001 dt_bauhaus_slider_set_factor(g->simple_coupling_2, 180.f);
5002 dt_bauhaus_slider_set_format(g->simple_coupling_2, "\302\260");
5003 gtk_widget_set_tooltip_text(g->simple_coupling_2, _("chroma direction, in the fixed chroma basis, that is coupled into the achromatic axis."));
5004 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_coupling_2), FALSE, FALSE, 0);
5005 g_signal_connect(G_OBJECT(g->simple_coupling_2), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5006
5007 g->simple_coupling_1 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 1.f, 0, 0, 3);
5008 dt_bauhaus_widget_set_label(g->simple_coupling_1, N_("achromatic coupling amount"));
5009 gtk_widget_set_tooltip_text(g->simple_coupling_1, _("strength of the chroma-to-achromatic coupling in the fixed chroma basis."));
5010 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_coupling_1), FALSE, FALSE, 0);
5011 g_signal_connect(G_OBJECT(g->simple_coupling_1), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5012
5013 GtkWidget *mixer_primaries = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5014 gtk_stack_add_named(GTK_STACK(g->mixer_stack), mixer_primaries, "primaries");
5015
5016 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("achromatic axis")), FALSE, FALSE, 0);
5017
5018 g->primaries_achromatic_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -2.f, 2.f, 0, 0, 3);
5019 dt_bauhaus_widget_set_label(g->primaries_achromatic_hue, N_("white hue"));
5020 dt_bauhaus_slider_set_factor(g->primaries_achromatic_hue, 90.f);
5021 dt_bauhaus_slider_set_format(g->primaries_achromatic_hue, "\302\260");
5022 gtk_widget_set_tooltip_text(g->primaries_achromatic_hue,
5023 _("rotate the custom white vector around the D50 white of the current mixer basis."));
5024 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_achromatic_hue), FALSE, FALSE, 0);
5025 g_signal_connect(G_OBJECT(g->primaries_achromatic_hue), "value-changed",
5027
5028 g->primaries_achromatic_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 0, 3);
5029 dt_bauhaus_widget_set_label(g->primaries_achromatic_purity, N_("white purity"));
5030 gtk_widget_set_tooltip_text(g->primaries_achromatic_purity,
5031 _("distance of the custom white vector from the D50 white within the current mixer basis."));
5032 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_achromatic_purity), FALSE, FALSE, 0);
5033 g_signal_connect(G_OBJECT(g->primaries_achromatic_purity), "value-changed",
5035
5036 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("red primary")), FALSE, FALSE, 0);
5037
5038 g->primaries_red_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5039 dt_bauhaus_widget_set_label(g->primaries_red_hue, N_("red hue"));
5040 dt_bauhaus_slider_set_factor(g->primaries_red_hue, 90.f);
5041 dt_bauhaus_slider_set_format(g->primaries_red_hue, "\302\260");
5042 gtk_widget_set_tooltip_text(g->primaries_red_hue,
5043 _("rotate the first basis vector around the D50 white of the current mixer basis."));
5044 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_red_hue), FALSE, FALSE, 0);
5045 g_signal_connect(G_OBJECT(g->primaries_red_hue), "value-changed",
5047
5048 g->primaries_red_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
5049 dt_bauhaus_widget_set_label(g->primaries_red_purity, N_("red purity"));
5050 gtk_widget_set_tooltip_text(g->primaries_red_purity,
5051 _("radial scaling of the first basis vector inside the affine primaries footprint."));
5052 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_red_purity), FALSE, FALSE, 0);
5053 g_signal_connect(G_OBJECT(g->primaries_red_purity), "value-changed",
5055
5056 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("green primary")), FALSE, FALSE, 0);
5057
5058 g->primaries_green_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5059 dt_bauhaus_widget_set_label(g->primaries_green_hue, N_("green hue"));
5060 dt_bauhaus_slider_set_factor(g->primaries_green_hue, 90.f);
5061 dt_bauhaus_slider_set_format(g->primaries_green_hue, "\302\260");
5062 gtk_widget_set_tooltip_text(g->primaries_green_hue,
5063 _("rotate the second basis vector around the D50 white of the current mixer basis."));
5064 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_green_hue), FALSE, FALSE, 0);
5065 g_signal_connect(G_OBJECT(g->primaries_green_hue), "value-changed",
5067
5068 g->primaries_green_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
5069 dt_bauhaus_widget_set_label(g->primaries_green_purity, N_("green purity"));
5070 gtk_widget_set_tooltip_text(g->primaries_green_purity,
5071 _("radial scaling of the second basis vector inside the affine primaries footprint."));
5072 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_green_purity), FALSE, FALSE, 0);
5073 g_signal_connect(G_OBJECT(g->primaries_green_purity), "value-changed",
5075
5076 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("blue primary")), FALSE, FALSE, 0);
5077
5078 g->primaries_blue_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5079 dt_bauhaus_widget_set_label(g->primaries_blue_hue, N_("blue hue"));
5080 dt_bauhaus_slider_set_factor(g->primaries_blue_hue, 90.f);
5081 dt_bauhaus_slider_set_format(g->primaries_blue_hue, "\302\260");
5082 gtk_widget_set_tooltip_text(g->primaries_blue_hue,
5083 _("rotate the third basis vector around the D50 white of the current mixer basis."));
5084 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_blue_hue), FALSE, FALSE, 0);
5085 g_signal_connect(G_OBJECT(g->primaries_blue_hue), "value-changed",
5087
5088 g->primaries_blue_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
5089 dt_bauhaus_widget_set_label(g->primaries_blue_purity, N_("blue purity"));
5090 gtk_widget_set_tooltip_text(g->primaries_blue_purity,
5091 _("radial scaling of the third basis vector inside the affine primaries footprint."));
5092 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_blue_purity), FALSE, FALSE, 0);
5093 g_signal_connect(G_OBJECT(g->primaries_blue_purity), "value-changed",
5095
5096 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("gain correction")), FALSE, FALSE, 0);
5097
5098 g->primaries_gain = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -8.f, 8.f, 0, 1.f, 3);
5099 dt_bauhaus_widget_set_label(g->primaries_gain, N_("gain"));
5100 gtk_widget_set_tooltip_text(g->primaries_gain,
5101 _("global gain multiplying the custom white vector after the affine primaries transform."));
5102 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_gain), FALSE, FALSE, 0);
5103 g_signal_connect(G_OBJECT(g->primaries_gain), "value-changed",
5105
5106 GtkWidget *outputs_page = dt_ui_notebook_page(g->notebook, N_("Outputs"),
5107 _("output colorfulness, brightness and B&W mixing"));
5108 self->widget = outputs_page;
5109
5110#define OUTPUT_SECTION(var, short, section, swap) \
5111 gtk_box_pack_start(GTK_BOX(outputs_page), dt_ui_section_label_new(section), FALSE, FALSE, 0); \
5112 \
5113 first = dt_bauhaus_slider_from_params(self, swap ? #var "[2]" : #var "[0]");\
5114 dt_bauhaus_slider_set_digits(first, 3); \
5115 dt_bauhaus_widget_set_label(first, N_("input R")); \
5116 \
5117 second = dt_bauhaus_slider_from_params(self, #var "[1]"); \
5118 dt_bauhaus_slider_set_digits(second, 3); \
5119 dt_bauhaus_widget_set_label(second, N_("input G")); \
5120 \
5121 third = dt_bauhaus_slider_from_params(self, swap ? #var "[0]" : #var "[2]");\
5122 dt_bauhaus_slider_set_digits(third, 3); \
5123 dt_bauhaus_widget_set_label(third, N_("input B")); \
5124 \
5125 g->scale_##var##_R = swap ? third : first; \
5126 g->scale_##var##_G = second; \
5127 g->scale_##var##_B = swap ? first : third; \
5128 \
5129 g->normalize_##short = dt_bauhaus_toggle_from_params(self, "normalize_" #short);
5130
5131 OUTPUT_SECTION(saturation, sat, _("colorfulness"), FALSE)
5132 g->saturation_version = dt_bauhaus_combobox_from_params(self, "version");
5133 OUTPUT_SECTION(lightness, light, _("brightness"), FALSE)
5134 OUTPUT_SECTION(grey, grey, _("B&W"), FALSE)
5135
5136 // start building top level widget
5137 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5138
5139 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
5140 const int saved_page = dt_conf_get_int("plugins/darkroom/channelmixerrgb/gui_page");
5141 const int active_page = saved_page > 2 ? 2 : CLAMP(saved_page, 0, 2);
5142 gtk_widget_show(gtk_notebook_get_nth_page(g->notebook, active_page));
5143 gtk_notebook_set_current_page(g->notebook, active_page);
5144
5145 // Add the color checker collapsible panel
5146
5148 (&g->cs,
5149 "plugins/darkroom/channelmixerrgb/expand_values",
5150 _("calibrate with a color checker"),
5151 GTK_BOX(self->widget), GTK_PACK_END);
5152
5153 gtk_widget_set_tooltip_text(g->cs.toggle,
5154 _("use a color checker target to autoset CAT and channels"));
5155 g_signal_connect(G_OBJECT(g->cs.toggle), "toggled", G_CALLBACK(start_profiling_callback), self);
5156
5157 GtkWidget *collapsible = GTK_WIDGET(g->cs.container);
5158
5159 gchar *tip_files_loc = NULL;
5160 {
5161 char confdir[PATH_MAX] = { 0 };
5162 dt_loc_get_user_config_dir(confdir, sizeof(confdir));
5163
5164 gchar *user_CGATS_dir = g_build_filename(confdir, "color", "checker", NULL);
5165 tip_files_loc = g_strdup_printf(_("'%s'"), user_CGATS_dir);
5166 dt_free(user_CGATS_dir);
5167 }
5168
5169 g->checkers_list = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
5170 dt_bauhaus_widget_set_label(g->checkers_list, N_("Chart"));
5171 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->checkers_list), TRUE, TRUE, 0);
5172 dt_bauhaus_combobox_set(g->checkers_list, 0);
5173 gchar *tooltip = g_strdup_printf(_("Choose the vendor and the type of your chart.\n"
5174 ".cht files in %s."), tip_files_loc);
5175 gtk_widget_set_tooltip_text(g->checkers_list, tooltip);
5177 g_signal_connect(G_OBJECT(g->checkers_list), "value-changed", G_CALLBACK(checker_changed_callback), (gpointer)self);
5178
5179 g->checkers_color_list = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
5180 dt_bauhaus_widget_set_label(g->checkers_color_list, N_("Chart color"));
5181 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->checkers_color_list), TRUE, TRUE, 0);
5182
5183 dt_bauhaus_combobox_set(g->checkers_color_list, 0);
5184 tooltip = g_strdup_printf(_("Choose the definition of your chart.\n"
5185 "CGATS.17 files in %s."), tip_files_loc);
5186 gtk_widget_set_tooltip_text(g->checkers_color_list, tooltip);
5188 g_signal_connect(G_OBJECT(g->checkers_color_list), "value-changed", G_CALLBACK(checker_color_changed_callback), (gpointer)self);
5189
5190 tooltip = g_markup_printf_escaped(_("<i>No CGATS.17 color file matches the selected chart.\n"
5191 "Add a compatible CGATS.17 file to <b>%s</b>.</i>"), tip_files_loc);
5192 g->checker_msg = gtk_label_new(NULL);
5193 gtk_label_set_markup(GTK_LABEL(g->checker_msg), tooltip);
5195 gtk_label_set_line_wrap(GTK_LABEL(g->checker_msg), TRUE);
5196 gtk_label_set_line_wrap_mode(GTK_LABEL(g->checker_msg), PANGO_WRAP_WORD);
5197 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->checker_msg), FALSE, TRUE, 0);
5198 gtk_widget_set_visible(g->checker_msg, FALSE);
5199 dt_free(tip_files_loc);
5200
5201
5202
5203 DT_BAUHAUS_COMBOBOX_NEW_FULL(darktable.bauhaus, g->optimize, DT_GUI_MODULE(self), N_("optimize for"),
5204 _("choose the colors that will be optimized with higher priority.\n"
5205 "neutral colors gives the lowest average delta E but a high maximum delta E\n"
5206 "saturated colors gives the lowest maximum delta E but a high average delta E\n"
5207 "none is a trade-off between both\n"
5208 "the others are special behaviours to protect some hues"),
5210 N_("none"),
5211 N_("neutral colors"),
5212 N_("saturated colors"),
5213 N_("skin and soil colors"),
5214 N_("foliage colors"),
5215 N_("sky and water colors"),
5216 N_("average delta E"),
5217 N_("maximum delta E"));
5218 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->optimize), TRUE, TRUE, 0);
5219
5221 dt_bauhaus_widget_set_label(g->safety, N_("patch scale"));
5222 gtk_widget_set_tooltip_text(g->safety, _("reduce the radius of the patches to select the more or less central part.\n"
5223 "useful when the perspective correction is sloppy or\n"
5224 "the patches frame cast a shadows on the edges of the patch." ));
5225 g_signal_connect(G_OBJECT(g->safety), "value-changed", G_CALLBACK(safety_changed_callback), self);
5226 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->safety), TRUE, TRUE, 0);
5227
5228 g->label_delta_E = dt_ui_label_new("");
5229 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->label_delta_E), TRUE, TRUE, 0);
5230 gtk_widget_set_tooltip_text(g->label_delta_E, _("the delta E is using the CIE 2000 formula"));
5231
5232 GtkWidget *toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
5233
5234 g->button_commit = dtgtk_button_new(dtgtk_cairo_paint_check_mark, 0, NULL);
5235 gtk_box_pack_end(GTK_BOX(toolbar), GTK_WIDGET(g->button_commit), FALSE, FALSE, 0);
5236 gtk_widget_set_tooltip_text(g->button_commit, _("accept the computed profile and set it in the module"));
5237 g_signal_connect(G_OBJECT(g->button_commit), "button-press-event", G_CALLBACK(commit_profile_callback), (gpointer)self);
5238
5239 g->button_profile = dtgtk_button_new(dtgtk_cairo_paint_refresh, 0, NULL);
5240 g_signal_connect(G_OBJECT(g->button_profile), "button-press-event", G_CALLBACK(run_profile_callback), (gpointer)self);
5241 gtk_widget_set_tooltip_text(g->button_profile, _("recompute the profile"));
5242 gtk_box_pack_end(GTK_BOX(toolbar), GTK_WIDGET(g->button_profile), FALSE, FALSE, 0);
5243
5244 g->button_validate = dtgtk_button_new(dtgtk_cairo_paint_softproof, 0, NULL);
5245 g_signal_connect(G_OBJECT(g->button_validate), "button-press-event", G_CALLBACK(run_validation_callback), (gpointer)self);
5246 gtk_widget_set_tooltip_text(g->button_validate, _("check the output delta E"));
5247 gtk_box_pack_end(GTK_BOX(toolbar), GTK_WIDGET(g->button_validate), FALSE, FALSE, 0);
5248
5249 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
5250}
5251
5253{
5256 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
5258
5260 dt_conf_set_int("plugins/darkroom/channelmixerrgb/gui_page", gtk_notebook_get_current_page (g->notebook));
5261
5262 if(g->delta_E_in)
5263 {
5264 dt_free_align(g->delta_E_in);
5265 g->delta_E_in = NULL;
5266 }
5267
5268 dt_free(g->delta_E_label_text);
5269
5270 dt_colorchecker_label_list_cleanup(&(g->colorcheckers));
5271 dt_colorchecker_label_list_cleanup(&(g->colorcheckers_all_color));
5272 g_list_free(g->colorcheckers_color); // data pointers are already freed by the cleanup function for colorcheckers_all_color
5273
5274 dt_colorchecker_cleanup(g->checker);
5275
5277}
5278
5279// clang-format off
5280// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
5281// vim: shiftwidth=2 expandtab tabstop=2 cindent
5282// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
5283// 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
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
void dt_bauhaus_slider_set_hard_max(GtkWidget *widget, float val)
Definition bauhaus.c:1583
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:2189
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1640
const char * dt_bauhaus_combobox_get_entry(GtkWidget *widget, int pos)
Definition bauhaus.c:2199
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
GtkWidget * dt_bauhaus_slider_new_with_range_and_feedback(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits, int feedback)
Definition bauhaus.c:1786
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1624
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:2155
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1551
void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(free_func)(void *data), gboolean sensitive)
Definition bauhaus.c:2038
void dt_bauhaus_combobox_add_separator(GtkWidget *widget)
Definition bauhaus.c:2050
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
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:2101
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3611
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
Definition bauhaus.h:125
#define DT_BAUHAUS_SPACE
Definition bauhaus.h:291
#define INTERNAL_PADDING
Definition bauhaus.h:76
#define DT_BAUHAUS_COMBOBOX_NEW_FULL(bauhaus, widget, action, label, tip, pos, callback, data,...)
Definition bauhaus.h:392
#define DT_BAUHAUS_SLIDER_MAX_STOPS
Definition bauhaus.h:71
#define INNER_PADDING
Definition bauhaus.h:79
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DEVELOP_BLEND_CS_RGB_SCENE
Definition blend.h:60
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:347
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:134
static void checker_color_changed_callback(GtkWidget *widget, gpointer user_data)
#define DT_CHANNELMIXERRGB_SIMPLE_MODE_CONF
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)
#define COLOR_MAX
#define CHANNEL_SIZE
static __DT_CLONE_TARGETS__ void loop_switch(const float *const restrict in, float *const restrict out, const size_t width, const size_t height, const size_t ch, const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t MIX, const dt_aligned_pixel_t illuminant, const dt_aligned_pixel_t saturation, const dt_aligned_pixel_t lightness, const dt_aligned_pixel_t grey, const float p, const float gamut, const int clip, const int apply_grey, const dt_adaptation_t kind, const dt_iop_channelmixer_rgb_version_t version)
const char ** description(struct dt_iop_module_t *self)
int default_group()
static void _spot_settings_changed_callback(GtkWidget *slider, dt_iop_module_t *self)
static void optimize_changed_callback(GtkWidget *widget, gpointer user_data)
void gui_reset(dt_iop_module_t *self)
static void update_xy_color(dt_iop_module_t *self)
void _auto_set_illuminant(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe)
#define HUE_MAX
dt_iop_channelmixer_rgb_mixer_mode_t
@ DT_CHANNELMIXERRGB_MIXER_SIMPLE
@ DT_CHANNELMIXERRGB_MIXER_PRIMARIES
@ DT_CHANNELMIXERRGB_MIXER_COMPLETE
void reload_defaults(dt_iop_module_t *module)
#define OFF
#define GET_WEIGHT
static void update_bounding_box(dt_iop_channelmixer_rgb_gui_data_t *g, const float x_increment, const float y_increment)
static void update_illuminant_color(struct dt_iop_module_t *self)
static gboolean _channelmixerrgb_sync_primaries_from_params(dt_iop_module_t *self, float *error)
Synchronize the primaries-mode GUI from the effective mixer matrix.
static gboolean _is_another_module_cat_on_pipe(struct dt_iop_module_t *self)
static void _channelmixerrgb_set_mixer_mode(dt_iop_channelmixer_rgb_gui_data_t *g, dt_iop_channelmixer_rgb_mixer_mode_t mode)
static gboolean target_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
static gboolean origin_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
dt_iop_channelmixer_shared_primaries_params_t dt_iop_channelmixer_rgb_primaries_params_t
static void _channelmixerrgb_mixer_mode_callback(GtkWidget *combo, gpointer user_data)
const char * aliases()
void update_colorchecker_list(dt_iop_module_t *self)
dt_iop_channelmixer_shared_primaries_basis_t dt_iop_channelmixer_rgb_primaries_basis_t
dt_spot_mode_t
@ DT_SPOT_MODE_MEASURE
@ DT_SPOT_MODE_CORRECT
@ DT_SPOT_MODE_LAST
static __DT_CLONE_TARGETS__ int auto_detect_WB(const float *const restrict in, dt_illuminant_t illuminant, const size_t width, const size_t height, const size_t ch, const dt_colormatrix_t RGB_to_XYZ, dt_aligned_pixel_t xyz)
static __DT_CLONE_TARGETS__ int _extract_patches(const float *const restrict in, const dt_iop_roi_t *const roi_in, dt_iop_channelmixer_rgb_gui_data_t *g, const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_CAM, float *const restrict patches, const gboolean normalize_exposure, extraction_result_t *result)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void start_profiling_callback(GtkWidget *togglebutton, dt_iop_module_t *self)
static void _update_RGB_slider_stop(dt_iop_channelmixer_rgb_params_t *p, const struct dt_iop_order_iccprofile_info_t *const work_profile, const struct dt_iop_order_iccprofile_info_t *const display_profile, GtkWidget *w, float stop, float c, float r, float g, float b)
const char * name()
static void _update_RGB_colors(dt_iop_module_t *self, float r, float g, float b, gboolean normalize, float *a, GtkWidget *w_r, GtkWidget *w_g, GtkWidget *w_b)
static void _channelmixerrgb_update_simple_colors(dt_iop_module_t *self)
Paint the simple mixer sliders from the exact chroma-basis model.
dt_iop_channelmixer_shared_simple_params_t dt_iop_channelmixer_rgb_simple_params_t
void gui_update(struct dt_iop_module_t *self)
__DT_CLONE_TARGETS__ int extract_color_checker(const float *const restrict in, float *const restrict out, const dt_iop_roi_t *const roi_in, dt_iop_channelmixer_rgb_gui_data_t *g, const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t XYZ_to_CAM, const dt_adaptation_t kind)
#define RAD_TO_DEG(x)
#define LIGHTNESS_MAX
int validate_color_checker(const float *const restrict in, const dt_iop_roi_t *const roi_in, dt_iop_channelmixer_rgb_gui_data_t *g, const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t XYZ_to_CAM)
void gui_init(struct dt_iop_module_t *self)
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
#define MIXER_ROW(var, short, section, swap)
static __DT_CLONE_TARGETS__ int get_white_balance_coeff(struct dt_iop_module_t *self, dt_aligned_pixel_t custom_wb)
static void run_validation_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static gboolean illuminant_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
#define DEG_TO_RAD(x)
static void update_illuminants(struct dt_iop_module_t *self)
static void _channelmixerrgb_primaries_slider_callback(GtkWidget *slider, gpointer user_data)
static void _channelmixerrgb_simple_slider_callback(GtkWidget *slider, gpointer user_data)
void color_list_visibility(dt_iop_module_t *self, const int checker_cmbbx_index)
void cleanup_global(dt_iop_module_so_t *module)
#define TEMP_MAX
static void run_profile_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
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()
void checker_changed_callback(GtkWidget *widget, gpointer user_data)
#define SHF(ii, jj, c)
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
void gui_cleanup(struct dt_iop_module_t *self)
void update_colorchecker_color_list(dt_iop_module_t *self)
This function filters the list of all .cht files to only those that match the currently selected colo...
static void init_bounding_box(dt_iop_channelmixer_rgb_gui_data_t *g, const float width, const float height)
void init_presets(dt_iop_module_so_t *self)
#define CHROMA_MAX
static void _preview_pipe_finished_callback(gpointer instance, gpointer user_data)
static __DT_CLONE_TARGETS__ void declare_cat_on_pipe(struct dt_iop_module_t *self, gboolean preset)
static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
#define DT_CHANNELMIXERRGB_SIMPLE_EPS
static void illum_xy_callback(GtkWidget *slider, gpointer user_data)
static void update_approx_cct(struct dt_iop_module_t *self)
static void compute_patches_delta_E(const float *const restrict patches, const dt_color_checker_t *const checker, float *const restrict delta_E, float *const restrict avg_delta_E, float *const restrict max_delta_E)
#define COLOR_MIN
#define INVERSE_SQRT_3
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
#define OUTPUT_SECTION(var, short, section, swap)
#define ILLUM_Y_MAX
#define TEMP_MIN
void init_global(dt_iop_module_so_t *module)
dt_iop_channelmixer_shared_simple_probe_t dt_iop_channelmixer_rgb_simple_probe_t
void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
dt_iop_channelmixer_rgb_version_t
@ CHANNELMIXERRGB_V_1
@ CHANNELMIXERRGB_V_2
@ CHANNELMIXERRGB_V_3
dt_solving_strategy_t
@ DT_SOLVE_OPTIMIZE_SKIN
@ DT_SOLVE_OPTIMIZE_SKY
@ DT_SOLVE_OPTIMIZE_MAX_DELTA_E
@ DT_SOLVE_OPTIMIZE_LOW_SAT
@ DT_SOLVE_OPTIMIZE_AVG_DELTA_E
@ DT_SOLVE_OPTIMIZE_NONE
@ DT_SOLVE_OPTIMIZE_HIGH_SAT
@ DT_SOLVE_OPTIMIZE_FOLIAGE
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)
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
static void commit_profile_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static void safety_changed_callback(GtkWidget *widget, gpointer user_data)
static void paint_hue(dt_iop_module_t *self)
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void _convert_GUI_colors(dt_iop_channelmixer_rgb_params_t *p, const struct dt_iop_order_iccprofile_info_t *const work_profile, const struct dt_iop_order_iccprofile_info_t *const display_profile, const dt_aligned_pixel_t LMS, dt_aligned_pixel_t RGB)
#define ILLUM_X_MAX
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 void _channelmixerrgb_update_primaries_colors(dt_iop_module_t *self)
static gboolean _channelmixerrgb_sync_simple_from_params(dt_iop_module_t *self, float *error)
Rebuild the simple mixer sliders from the current normalized 3x3 matrix.
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid)
void dt_iop_channelmixer_shared_paint_primaries_sliders(const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, const dt_iop_channelmixer_shared_primaries_basis_t basis, const dt_iop_channelmixer_shared_primaries_params_t *const primaries, GtkWidget *const widgets[9])
void dt_iop_channelmixer_shared_simple_from_sliders(GtkWidget *const widgets[6], dt_iop_channelmixer_shared_simple_params_t *simple)
void dt_iop_channelmixer_shared_paint_temperature_slider(GtkWidget *const widget, const float temperature_min, const float temperature_max)
void dt_iop_channelmixer_shared_primaries_to_sliders(const dt_iop_channelmixer_shared_primaries_params_t *const primaries, GtkWidget *const widgets[9])
void dt_iop_channelmixer_shared_simple_to_matrix(const dt_iop_channelmixer_shared_simple_params_t *const simple, float M[3][3])
void dt_iop_channelmixer_shared_simple_to_sliders(const dt_iop_channelmixer_shared_simple_params_t *const simple, GtkWidget *const widgets[6])
dt_iop_channelmixer_shared_primaries_basis_t dt_iop_channelmixer_shared_primaries_basis_from_adaptation(const dt_adaptation_t adaptation)
void dt_iop_channelmixer_shared_work_rgb_to_display(const dt_aligned_pixel_t work_rgb, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, dt_aligned_pixel_t display_rgb)
void dt_iop_channelmixer_shared_paint_simple_sliders(const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, const dt_iop_channelmixer_shared_simple_params_t *const simple, GtkWidget *const widgets[6])
gboolean dt_iop_channelmixer_shared_primaries_from_matrix(const dt_iop_channelmixer_shared_primaries_basis_t basis, const float M[3][3], dt_iop_channelmixer_shared_primaries_params_t *primaries)
void dt_iop_channelmixer_shared_simple_from_matrix(const float M[3][3], dt_iop_channelmixer_shared_simple_params_t *simple)
gboolean dt_iop_channelmixer_shared_rows_are_normalized(const gboolean normalize[3])
float dt_iop_channelmixer_shared_roundtrip_error(const float M[3][3], const float roundtrip[3][3])
gboolean dt_iop_channelmixer_shared_get_matrix(const float rows[3][3], const gboolean normalize[3], const gboolean force_normalize, float M[3][3])
gboolean dt_iop_channelmixer_shared_primaries_to_matrix(const dt_iop_channelmixer_shared_primaries_basis_t basis, const dt_iop_channelmixer_shared_primaries_params_t *primaries, float M[3][3])
void dt_iop_channelmixer_shared_primaries_from_sliders(GtkWidget *const widgets[9], dt_iop_channelmixer_shared_primaries_params_t *primaries)
dt_iop_channelmixer_shared_simple_probe_t
dt_iop_channelmixer_shared_primaries_basis_t
static const dt_aligned_pixel_simd_t const dt_adaptation_t adaptation
static const dt_adaptation_t kind
static void convert_D50_to_LMS(const dt_adaptation_t adaptation, dt_aligned_pixel_t D50)
@ DT_ADAPTATION_LAST
@ DT_ADAPTATION_FULL_BRADFORD
@ DT_ADAPTATION_XYZ
@ DT_ADAPTATION_CAT16
@ DT_ADAPTATION_RGB
@ DT_ADAPTATION_LINEAR_BRADFORD
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static const float scaling
static const dt_aligned_pixel_simd_t illuminant
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
gboolean dt_iop_color_picker_is_active_module(const dt_iop_module_t *module)
Tell whether one module currently owns the active darkroom picker.
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
@ COLOR_CHECKER_USER_REF
void dt_colorchecker_label_list_cleanup(GList **colorcheckers)
static dt_color_checker_t * dt_get_color_checker(const dt_color_checker_targets target_type, GList **colorchecker_label, const char *color_filename)
int dt_colorchecker_find(GList **colorcheckers_label)
Find all builtin and .cht colorcheckers.
void dt_colorchecker_cleanup(dt_color_checker_t *checker)
int dt_colorchecker_find_color(GList **color_label)
Find all builtin and CGATS colorcheckers.
static void CAT16_adapt_D50(float4 *lms_in, const float4 origin_illuminant, const float D, const int full)
Definition colorspace.h:722
static float4 convert_XYZ_to_bradford_LMS(const float4 XYZ)
Definition colorspace.h:657
static float4 convert_XYZ_to_CAT16_LMS(const float4 XYZ)
Definition colorspace.h:677
static void bradford_adapt_D50(float4 *lms_in, const float4 origin_illuminant, const float p, const int full)
Definition colorspace.h:697
static void XYZ_adapt_D50(float4 *lms_in, const float4 origin_illuminant)
Definition colorspace.h:737
static float4 dt_XYZ_to_xyY(const float4 XYZ)
Definition colorspace.h:635
static float4 convert_CAT16_LMS_to_XYZ(const float4 LMS)
Definition colorspace.h:687
static float4 convert_bradford_LMS_to_XYZ(const float4 LMS)
Definition colorspace.h:667
#define B(y, x)
__DT_CLONE_TARGETS__ int dt_colorspaces_conversion_matrices_rgb(const float adobe_XYZ_to_CAM[4][3], double out_RGB_to_CAM[4][3], double out_CAM_to_RGB[3][4], const float *embedded_matrix, double mul[4])
#define A(y, x)
dt_aligned_pixel_t LMS
static dt_aligned_pixel_t xyY
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
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
const float top
dt_XYZ_to_Lab(XYZ, Lab)
static dt_aligned_pixel_t RGB
static dt_aligned_pixel_t Lch
const float delta
static const dt_colormatrix_t M
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
gboolean dt_image_is_matrix_correction_supported(const dt_image_t *img)
dt_image_pipe_class_t dt_image_pipe_class(const dt_image_t *img)
const char * dt_image_pipe_class_name(const dt_image_pipe_class_t klass)
gboolean dt_image_is_monochrome(const dt_image_t *img)
int type
point_t apply_homography(point_t p, const float *h)
Definition common.c:68
float apply_homography_scaling(point_t p, const float *h)
Definition common.c:79
int get_homography(const point_t *source, const point_t *target, float *h)
Definition common.c:27
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
int dt_conf_key_exists(const char *key)
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
gboolean dt_conf_is_equal(const char *name, const char *value)
void dt_control_log(const char *msg,...)
Definition control.c:761
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:906
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:398
#define dt_control_set_cursor_visible(visible)
Definition control.h:148
darktable_t darktable
Definition darktable.c:181
void * dt_alloc_align(size_t size)
Definition darktable.c:446
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define DT_ALIGNED_PIXEL
Definition darktable.h:389
#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
@ DT_DEBUG_DEV
Definition darktable.h:717
#define for_each_channel(_var,...)
Definition darktable.h:662
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:494
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __OMP_DECLARE_SIMD__(...)
Definition darktable.h:263
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define PATH_MAX
Definition darktable.h:1062
#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
#define dt_dev_pixelpipe_resync_history_preview(dev)
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1712
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1144
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:1824
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1003
void dtgtk_cairo_paint_refresh(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_check_mark(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_softproof(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
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 pseudo_solve_gaussian(double *const restrict A, double *const restrict y, const size_t m, const size_t n, const int checks)
void dt_gui_hide_collapsible_section(dt_gui_collapsible_section_t *cs)
Definition gtk.c:3095
void dt_gui_new_collapsible_section(dt_gui_collapsible_section_t *cs, const char *confname, const char *label, GtkBox *parent, GtkPackType pack)
Create a collapsible section and pack it into the parent box.
Definition gtk.c:3102
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2259
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2254
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
Definition gtk.c:3080
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:451
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
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)
void dt_gui_throttle_queue(gpointer source, dt_gui_throttle_callback_t callback, gpointer user_data)
static int illuminant_to_xy(const dt_illuminant_t illuminant, const dt_image_t *img, const dt_aligned_pixel_t custom_wb, float *x_out, float *y_out, const float t, const dt_illuminant_fluo_t fluo, const dt_illuminant_led_t iled)
dt_illuminant_t
Definition illuminants.h:35
@ DT_ILLUMINANT_A
Definition illuminants.h:37
@ DT_ILLUMINANT_PIPE
Definition illuminants.h:36
@ DT_ILLUMINANT_CAMERA
Definition illuminants.h:46
@ DT_ILLUMINANT_BB
Definition illuminants.h:42
@ DT_ILLUMINANT_F
Definition illuminants.h:40
@ DT_ILLUMINANT_CUSTOM
Definition illuminants.h:43
@ DT_ILLUMINANT_LED
Definition illuminants.h:41
@ DT_ILLUMINANT_DETECT_EDGES
Definition illuminants.h:45
@ DT_ILLUMINANT_E
Definition illuminants.h:39
@ DT_ILLUMINANT_LAST
Definition illuminants.h:47
@ DT_ILLUMINANT_DETECT_SURFACES
Definition illuminants.h:44
@ DT_ILLUMINANT_D
Definition illuminants.h:38
static void illuminant_xy_to_RGB(const float x, const float y, dt_aligned_pixel_t RGB)
static void illuminant_xy_to_XYZ(const float x, const float y, dt_aligned_pixel_t XYZ)
static float xy_to_CCT(const float x, const float y)
dt_illuminant_led_t
Definition illuminants.h:70
@ DT_ILLUMINANT_LED_B5
Definition illuminants.h:75
@ DT_ILLUMINANT_LED_LAST
Definition illuminants.h:80
static float CCT_reverse_lookup(const float x, const float y)
static int find_temperature_from_raw_coeffs(const dt_image_t *img, const dt_aligned_pixel_t custom_wb, float *chroma_x, float *chroma_y)
static void matrice_pseudoinverse(float(*in)[3], float(*out)[3], int size)
dt_illuminant_fluo_t
Definition illuminants.h:52
@ DT_ILLUMINANT_FLUO_F3
Definition illuminants.h:55
@ DT_ILLUMINANT_FLUO_LAST
Definition illuminants.h:65
static void xy_to_uv(const float xy[2], float uv[2])
const char * tooltip
Definition image.h:251
@ DT_IMAGE_4BAYER
Definition image.h:127
static __DT_CLONE_TARGETS__ void dt_simd_memcpy(const float *const __restrict__ in, float *const __restrict__ out, const size_t num_elem)
Definition imagebuf.h:68
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:87
void dt_iop_throttled_history_update(gpointer data)
Definition imageop.c:3135
gboolean dt_iop_is_first_instance(GList *modules, dt_iop_module_t *module)
Definition imageop.c:3117
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:316
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
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
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2915
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:196
#define dt_omploop_sfence()
Definition imageop.h:702
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
#define dt_iop_fmt_log(module, fmt,...)
Debug helper to trace a module's input-format-driven decisions on the -d pipe channel (DT_DEBUG_PIPE)...
Definition imageop.h:453
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ IOP_GROUP_COLOR
Definition imageop.h:139
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
static float kernel(const float *x, const float *y)
struct dt_iop_tonecurve_params_t preset
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_input_profile_info(const struct dt_dev_pixelpipe_t *pipe)
static const float x
const int t
const float v
static float mix(const float a, const float b, const float t)
Definition liquify.c:705
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define R
#define DT_FMA(x, y, z)
Definition math.h:62
#define NORM_MIN
Definition math.h:35
#define M_PI
Definition math.h:45
static void transpose_3x3_to_3xSSE(const float input[9], dt_colormatrix_t output)
Definition matrices.h:91
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:33
static void transpose_3xSSE(const dt_colormatrix_t input, dt_colormatrix_t output)
Definition matrices.h:68
static void pack_3xSSE_to_3x4(const dt_colormatrix_t input, float output[12])
Definition matrices.h:149
static void repack_double3x3_to_3xSSE(const double input[9], dt_colormatrix_t output)
Definition matrices.h:113
static void pack_3xSSE_to_3x3(const dt_colormatrix_t input, float output[9])
Definition matrices.h:135
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
@ DT_DEV_PIXELPIPE_PREVIEW
Definition pixelpipe.h:40
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:174
@ DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED
This signal is raised when pipe is finished and the gui is attached no param, no returned value.
Definition signal.h:179
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
float quad_width
Definition bauhaus.h:273
dt_aligned_pixel_t Lab
dt_color_checker_patch * values
dt_color_checker_targets type
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
dt_dev_pixelpipe_type_t type
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:162
dt_image_t image_storage
Definition develop.h:259
GList * iop
Definition develop.h:279
int32_t preview_height
Definition develop.h:213
dt_aligned_pixel_t wb_coeffs
Definition develop.h:442
struct dt_develop_t::@17 roi
struct dt_develop_t::@20 proxy
int32_t preview_width
Definition develop.h:213
struct dt_iop_module_t * chroma_adaptation
Definition develop.h:438
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
int32_t reset
Definition gtk.h:172
int32_t flags
Definition image.h:319
float d65_color_matrix[9]
Definition image.h:339
float adobe_XYZ_to_CAM[4][3]
Definition image.h:362
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
dt_iop_channelmixer_rgb_version_t version
float DT_ALIGNED_PIXEL lightness[4]
float DT_ALIGNED_PIXEL grey[4]
float DT_ALIGNED_PIXEL saturation[4]
dt_gui_collapsible_section_t csspot
dt_gui_collapsible_section_t cs
dt_iop_channelmixer_rgb_version_t version
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
GtkDarktableToggleButton * off
Definition imageop.h:339
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
gboolean enabled
Definition imageop.h:298
dt_aligned_pixel_t picked_color_min
Definition imageop.h:272
dt_aligned_pixel_t picked_color_max
Definition imageop.h:272
dt_aligned_pixel_t picked_color
Definition imageop.h:272
dt_iop_params_t * params
Definition imageop.h:307
dt_colormatrix_t matrix_out_transposed
Definition iop_profile.h:66
dt_colormatrix_t matrix_in_transposed
Definition iop_profile.h:65
Region of interest passed through the pixelpipe.
Definition imageop.h:72
float y
Definition colorchart.h:33
float x
Definition colorchart.h:33