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
556// Core: compute the CAT adaptation ratio between the camera matrix's bogus-D65 and the given white
557// balance multipliers `wb_coeffs`. The coeff SOURCE differs by caller thread, and that is the whole
558// point of splitting this out: the pipeline must read temperature's coeffs from the per-piece buffer
559// descriptor (piece->dsc_in.temperature.coeffs, propagated in pipe order), NEVER from dev->proxy
560// (a GUI/main-thread inter-module channel). The GUI wrapper get_white_balance_coeff() sources proxy.
562static int _custom_wb_from_coeffs(struct dt_iop_module_t *self, const dt_aligned_pixel_t wb_coeffs,
563 dt_aligned_pixel_t custom_wb)
564{
565 // Init output with a no-op
566 for(size_t k = 0; k < 4; k++) custom_wb[k] = 1.f;
567
569 {
570 dt_iop_fmt_log(self, "get_white_balance_coeff: class=%s matrix_supported=0 -> custom_wb=no-op (CAT uses identity)",
572 return 1;
573 }
574
575 // First, get the D65-ish coeffs from the input matrix
576 // keep this in synch with calculate_bogus_daylight_wb from temperature.c !
577 // predicts the bogus D65 that temperature.c will compute for the camera input matrix
578 double bwb[4];
579
581 NULL, NULL,
583 {
584 // normalize green:
585 const double green = bwb[1];
586 bwb[0] /= green;
587 bwb[2] /= green;
588 bwb[1] = 1.0;
589 // bwb[3] is the SECOND green. For non-4-colour sensors the matrix's 4th coefficient is bogus
590 // (often huge or inf, see the temperature.c "usually NAN for RGB" note): both green sites
591 // share the first green, so force it to the green reference instead of propagating garbage.
592 bwb[3] = (self->dev->image_storage.flags & DT_IMAGE_4BAYER) ? bwb[3] / green : 1.0;
593 }
594 else
595 {
596 return 1;
597 }
598
599 // Second, if the temperature module is not using these, for example because they are wrong
600 // and user made a correct preset, find the WB adaptation ratio
601 if(wb_coeffs[0] != 0.f)
602 {
603 for(size_t k = 0; k < 4; k++)
604 {
605 // Guard the second-green ratio: wb_coeffs[3] may be 0 (non-RGBG sensor), which would
606 // yield inf. Mirror the first-green ratio in that case so custom_wb stays finite.
607 const float denom = wb_coeffs[k];
608 custom_wb[k] = (k == 3 && !isnormal(denom)) ? custom_wb[1] : bwb[k] / denom;
609 }
610 }
611
612 return 0;
613}
614
615// GUI-thread wrapper: source temperature's WB from the dev proxy (the GUI inter-module channel).
616// Pipeline code must NOT call this -- use _custom_wb_from_coeffs(self, piece->dsc_in.temperature.coeffs, ...).
619{
620 return _custom_wb_from_coeffs(self, self->dev->proxy.wb_coeffs, custom_wb);
621}
622
623
624static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
625gamut_mapping(const dt_aligned_pixel_simd_t input, const float compression, const int clip)
626{
627 // Get the sum XYZ
628 const float sum = input[0] + input[1] + input[2];
629 const float Y = input[1];
630
631 if(sum > 0.f && Y > 0.f)
632 {
633 // Convert to xyY
634 float x = input[0] / sum;
635 float y = input[1] / sum;
636
637 // Convert to uvY
638 const float uv_denominator = -2.f * x + 12.f * y + 3.f;
639 float u = 4.f * x / uv_denominator;
640 float v = 9.f * y / uv_denominator;
641
642 // Get the chromaticity difference with white point uv
643 const float D50[2] DT_ALIGNED_PIXEL = { 0.20915914598542354f, 0.488075320769787f };
644 const float delta[2] DT_ALIGNED_PIXEL = { D50[0] - u, D50[1] - v };
645 const float Delta = Y * (sqf(delta[0]) + sqf(delta[1]));
646
647 // Compress chromaticity (move toward white point)
648 const float correction = (compression == 0.0f) ? 0.f : powf(Delta, compression);
649 // Ensure the correction does not bring our uyY vector the other side of D50
650 // that would switch to the opposite color, so we clip at D50
651 const float tmp_u = DT_FMA(correction, delta[0], u);
652 const float tmp_v = DT_FMA(correction, delta[1], v);
653 u = (u > D50[0]) ? fmaxf(tmp_u, D50[0]) : fminf(tmp_u, D50[0]);
654 v = (v > D50[1]) ? fmaxf(tmp_v, D50[1]) : fminf(tmp_v, D50[1]);
655
656 // Convert back to xyY
657 const float xy_denominator = 6.f * u - 16.f * v + 12.f;
658 x = 9.f * u / xy_denominator;
659 y = 4.f * v / xy_denominator;
660
661 // Clip upon request
662 if(clip)
663 {
664 x = fmaxf(x, 0.0f);
665 y = fmaxf(y, 0.0f);
666 }
667
668 // Check sanity of y
669 // since we later divide by y, it can't be zero
670 y = fmaxf(y, NORM_MIN);
671
672 // Check sanity of x and y :
673 // since Z = Y (1 - x - y) / y, if x + y >= 1, Z will be negative
674 const float scale = x + y;
675 const int sanitize = (scale >= 1.f);
676 if(sanitize)
677 {
678 x /= scale;
679 y /= scale;
680 }
681
682 return (dt_aligned_pixel_simd_t){ Y * x / y, Y, Y * (1.f - x - y) / y, 0.f };
683 }
684 else
685 {
686 // sum of channels == 0, and/or Y == 0 so we have black
687 return (dt_aligned_pixel_simd_t){ 0.f };
688 }
689}
690__OMP_DECLARE_SIMD__(aligned(input, saturation, lightness, output:16) uniform(version))
691static inline __attribute__((always_inline)) void
692luma_chroma(const dt_aligned_pixel_t input, const dt_aligned_pixel_t saturation,
693 const dt_aligned_pixel_t lightness, dt_aligned_pixel_t output,
695{
696 // Compute euclidean norm
697 float norm = euclidean_norm(input);
698 const float avg = fmaxf((input[0] + input[1] + input[2]) / 3.0f, NORM_MIN);
699
700 if(norm > 0.f && avg > 0.f)
701 {
702 // Compute flat lightness adjustment
703 const float mix = scalar_product(input, lightness);
704
705 // Compensate the norm to get color ratios (R, G, B) = (1, 1, 1) for grey (colorless) pixels.
706 if(version == CHANNELMIXERRGB_V_3) norm *= INVERSE_SQRT_3;
707
708 // Ratios
709 for(size_t c = 0; c < 3; c++) output[c] = input[c] / norm;
710
711 // Compute ratios and a flat colorfulness adjustment for the whole pixel
712 float coeff_ratio = 0.f;
713
714 if(version == CHANNELMIXERRGB_V_1)
715 {
716 for(size_t c = 0; c < 3; c++)
717 coeff_ratio += sqf(1.0f - output[c]) * saturation[c];
718 }
719 else
720 coeff_ratio = scalar_product(output, saturation) / 3.f;
721
722 // Adjust the RGB ratios with the pixel correction
723 for(size_t c = 0; c < 3; c++)
724 {
725 // if the ratio was already invalid (negative), we accept the result to be invalid too
726 // otherwise bright saturated blues end up solid black
727 const float min_ratio = (output[c] < 0.0f) ? output[c] : 0.0f;
728 const float output_inverse = 1.0f - output[c];
729 output[c] = fmaxf(DT_FMA(output_inverse, coeff_ratio, output[c]),
730 min_ratio); // output_inverse * coeff_ratio + output
731 }
732
733 // The above interpolation between original pixel ratios and (1, 1, 1) might change the norm of the
734 // ratios. Compensate for that.
735 if(version == CHANNELMIXERRGB_V_3) norm /= euclidean_norm(output) * INVERSE_SQRT_3;
736
737 // Apply colorfulness adjustment channel-wise and repack with lightness to get LMS back
738 norm *= fmaxf(1.f + mix / avg, 0.f);
739 for(size_t c = 0; c < 3; c++) output[c] *= norm;
740 }
741 else
742 {
743 // we have black, 0 stays 0, no luminance = no color
744 for(size_t c = 0; c < 3; c++) output[c] = input[c];
745 }
746}
747
749static inline void loop_switch(const float *const restrict in, float *const restrict out,
750 const size_t width, const size_t height, const size_t ch,
751 const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t RGB_to_XYZ,
753 const dt_aligned_pixel_t saturation, const dt_aligned_pixel_t lightness,
754 const dt_aligned_pixel_t grey, const float p, const float gamut,
755 const int clip, const int apply_grey, const dt_adaptation_t kind,
757{
758 dt_colormatrix_t RGB_to_XYZ_t;
759 dt_colormatrix_t XYZ_to_RGB_t;
760 dt_colormatrix_t MIX_t;
761 transpose_3xSSE(RGB_to_XYZ, RGB_to_XYZ_t);
762 transpose_3xSSE(XYZ_to_RGB, XYZ_to_RGB_t);
763 transpose_3xSSE(MIX, MIX_t);
764 const dt_aligned_pixel_simd_t rgb_to_xyz0 = dt_colormatrix_row_to_simd(RGB_to_XYZ_t, 0);
765 const dt_aligned_pixel_simd_t rgb_to_xyz1 = dt_colormatrix_row_to_simd(RGB_to_XYZ_t, 1);
766 const dt_aligned_pixel_simd_t rgb_to_xyz2 = dt_colormatrix_row_to_simd(RGB_to_XYZ_t, 2);
767 const dt_aligned_pixel_simd_t xyz_to_rgb0 = dt_colormatrix_row_to_simd(XYZ_to_RGB_t, 0);
768 const dt_aligned_pixel_simd_t xyz_to_rgb1 = dt_colormatrix_row_to_simd(XYZ_to_RGB_t, 1);
769 const dt_aligned_pixel_simd_t xyz_to_rgb2 = dt_colormatrix_row_to_simd(XYZ_to_RGB_t, 2);
770 const dt_aligned_pixel_simd_t mix0 = dt_colormatrix_row_to_simd(MIX_t, 0);
771 const dt_aligned_pixel_simd_t mix1 = dt_colormatrix_row_to_simd(MIX_t, 1);
772 const dt_aligned_pixel_simd_t mix2 = dt_colormatrix_row_to_simd(MIX_t, 2);
773 const dt_aligned_pixel_simd_t illuminant_v = dt_load_simd_aligned(illuminant);
775 for(size_t k = 0; k < height * width * 4; k += 4)
776 {
777 const dt_aligned_pixel_simd_t in_v = dt_load_simd_aligned(in + k);
778 dt_aligned_pixel_simd_t temp_one_v = { 0.f };
779 dt_aligned_pixel_simd_t temp_two_v = clip ? dt_simd_max_zero(in_v) : in_v;
780
781 /* WE START IN PIPELINE RGB */
782
783 switch(kind)
784 {
786 {
787 // Convert from RGB to XYZ
788 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
789 const float Y = temp_one_v[1];
790
791 // Convert to LMS
792 temp_two_v = _downscale_vector_simd(convert_XYZ_to_bradford_LMS(temp_one_v), Y);
793 // Do white balance
794 temp_one_v = _upscale_vector_simd(bradford_adapt_D50(temp_two_v, illuminant_v, p, TRUE), Y);
795 // Compute the 3D mix - this is a rotation + homothety of the vector base
796 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, mix0, mix1, mix2);
797 temp_one_v = convert_bradford_LMS_to_XYZ(temp_two_v);
798
799 break;
800 }
802 {
803 // Convert from RGB to XYZ
804 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
805 const float Y = temp_one_v[1];
806
807 // Convert to LMS
808 temp_two_v = _downscale_vector_simd(convert_XYZ_to_bradford_LMS(temp_one_v), Y);
809 // Do white balance
810 temp_one_v = _upscale_vector_simd(bradford_adapt_D50(temp_two_v, illuminant_v, p, FALSE), Y);
811 // Compute the 3D mix - this is a rotation + homothety of the vector base
812 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, mix0, mix1, mix2);
813 temp_one_v = convert_bradford_LMS_to_XYZ(temp_two_v);
814
815 break;
816 }
818 {
819 // Convert from RGB to XYZ
820 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
821 const float Y = temp_one_v[1];
822
823 // Convert to LMS
824 temp_two_v = _downscale_vector_simd(convert_XYZ_to_CAT16_LMS(temp_one_v), Y);
825 // Do white balance
826 temp_one_v = _upscale_vector_simd(CAT16_adapt_D50(temp_two_v, illuminant_v, 1.0f, TRUE), Y);
827 // Compute the 3D mix - this is a rotation + homothety of the vector base
828 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, mix0, mix1, mix2);
829 temp_one_v = convert_CAT16_LMS_to_XYZ(temp_two_v);
830
831 break;
832 }
834 {
835 // Convert from RGB to XYZ
836 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
837 const float Y = temp_one_v[1];
838
839 // Do white balance in XYZ
840 temp_two_v = _upscale_vector_simd(XYZ_adapt_D50(_downscale_vector_simd(temp_one_v, Y), illuminant_v), Y);
841 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, mix0, mix1, mix2);
842
843 break;
844 }
847 default:
848 {
849 // No white balance.
850
851 // Compute the 3D mix in RGB - this is a rotation + homothety of the vector base
852 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, mix0, mix1, mix2);
853 temp_one_v = dt_mat3x4_mul_vec4(temp_one_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
854 break;
855 }
856 }
857
858 /* FROM HERE WE ARE MANDATORILY IN XYZ - DATA IS IN temp_one */
859
860 // Gamut mapping happens in XYZ space no matter what
861 temp_two_v = gamut_mapping(temp_one_v, gamut, clip);
862
863 // convert to LMS, XYZ or pipeline RGB
864 switch(kind)
865 {
870 {
871 temp_one_v = convert_any_XYZ_to_LMS(temp_two_v, kind);
872 break;
873 }
876 default:
877 {
878 // Convert from XYZ to RGB
879 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, xyz_to_rgb0, xyz_to_rgb1, xyz_to_rgb2);
880 break;
881 }
882 }
883
884 /* FROM HERE WE ARE IN LMS, XYZ OR PIPELINE RGB depending on user param - DATA IS IN temp_one */
885
886 // Clip in LMS
887 if(clip) temp_one_v = dt_simd_max_zero(temp_one_v);
888
889 // Apply lightness / saturation adjustment
890 dt_aligned_pixel_t luma_input;
891 dt_aligned_pixel_t luma_output;
892 dt_store_simd_aligned(luma_input, temp_one_v);
893 luma_chroma(luma_input, saturation, lightness, luma_output, version);
894 temp_two_v = dt_load_simd_aligned(luma_output);
895
896 // Clip in LMS
897 if(clip) temp_two_v = dt_simd_max_zero(temp_two_v);
898
899 // Save
900 if(apply_grey)
901 {
902 // Turn LMS, XYZ or pipeline RGB into monochrome
903 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);
904 dt_store_simd_nontemporal(out + k, (dt_aligned_pixel_simd_t){ grey_mix, grey_mix, grey_mix, in_v[3] });
905 }
906 else
907 {
908 // Convert back to XYZ
909 switch(kind)
910 {
915 {
916 temp_one_v = convert_any_LMS_to_XYZ(temp_two_v, kind);
917 break;
918 }
921 default:
922 {
923 // Convert from RBG to XYZ
924 temp_one_v = dt_mat3x4_mul_vec4(temp_two_v, rgb_to_xyz0, rgb_to_xyz1, rgb_to_xyz2);
925 break;
926 }
927 }
928
929 /* FROM HERE WE ARE MANDATORILY IN XYZ - DATA IS IN temp_one */
930
931 // Clip in XYZ
932 if(clip) temp_one_v = dt_simd_max_zero(temp_one_v);
933
934 // Convert back to RGB
935 temp_two_v = dt_mat3x4_mul_vec4(temp_one_v, xyz_to_rgb0, xyz_to_rgb1, xyz_to_rgb2);
936 if(clip) temp_two_v = dt_simd_max_zero(temp_two_v);
937
938 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] });
939 }
940 }
941 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
942}
943
944// util to shift pixel index without headache
945#define SHF(ii, jj, c) ((i + ii) * width + j + jj) * ch + c
946#define OFF 4
947
949static inline int auto_detect_WB(const float *const restrict in, dt_illuminant_t illuminant,
950 const size_t width, const size_t height, const size_t ch,
951 const dt_colormatrix_t RGB_to_XYZ, dt_aligned_pixel_t xyz)
952{
966 float *const restrict temp = dt_pixelpipe_cache_alloc_align_float_cache(width * height * ch, 0);
967 if(IS_NULL_PTR(temp)) return 1;
968
969 // Convert RGB to xy
970 __OMP_PARALLEL_FOR__(collapse(2) )
971 for(size_t i = 0; i < height; i++)
972 for(size_t j = 0; j < width; j++)
973 {
974 const size_t index = (i * width + j) * ch;
977
978 // Clip negatives
979 for_each_channel(c,aligned(in))
980 RGB[c] = fmaxf(in[index + c], 0.0f);
981
982 // Convert to XYZ
983 dot_product(RGB, RGB_to_XYZ, XYZ);
984
985 // Convert to xyY
986 const float sum = fmaxf(XYZ[0] + XYZ[1] + XYZ[2], NORM_MIN);
987 XYZ[0] /= sum; // x
988 XYZ[2] = XYZ[1]; // Y
989 XYZ[1] /= sum; // y
990
991 // Shift the chromaticity plane so the D50 point (target) becomes the origin
992 const float D50[2] = { 0.34567f, 0.35850f };
993 const float norm = dt_fast_hypotf(D50[0], D50[1]);
994
995 temp[index ] = (XYZ[0] - D50[0]) / norm;
996 temp[index + 1] = (XYZ[1] - D50[1]) / norm;
997 temp[index + 2] = XYZ[2];
998 }
999
1000 float elements = 0.f;
1001 dt_aligned_pixel_t xyY = { 0.f };
1002
1004 {
1005 __OMP_PARALLEL_FOR__(reduction(+:xyY, elements))
1006 for(size_t i = 2 * OFF; i < height - 4 * OFF; i += OFF)
1007 for(size_t j = 2 * OFF; j < width - 4 * OFF; j += OFF)
1008 {
1009 float DT_ALIGNED_PIXEL central_average[2];
1010
1011 for(size_t c = 0; c < 2; c++)
1012 {
1013 // B-spline local average / blur
1014 central_average[c] = ( temp[SHF(-OFF, -OFF, c)] + 2.f * temp[SHF(-OFF, 0, c)] + temp[SHF(-OFF, +OFF, c)] +
1015 2.f * temp[SHF( 0, -OFF, c)] + 4.f * temp[SHF( 0, 0, c)] + 2.f * temp[SHF( 0, +OFF, c)] +
1016 temp[SHF(+OFF, -OFF, c)] + 2.f * temp[SHF(+OFF, 0, c)] + temp[SHF(+OFF, +OFF, c)]) / 16.0f;
1017 central_average[c] = fmaxf(central_average[c], 0.0f);
1018 }
1019
1020 dt_aligned_pixel_t var = { 0.f };
1021
1022 // compute patch-wise variance
1023 // If variance = 0, we are on a flat surface and want to discard that patch.
1024 for(size_t c = 0; c < 2; c++)
1025 {
1026 var[c] = (
1027 sqf(temp[SHF(-OFF, -OFF, c)] - central_average[c]) +
1028 sqf(temp[SHF(-OFF, 0, c)] - central_average[c]) +
1029 sqf(temp[SHF(-OFF, +OFF, c)] - central_average[c]) +
1030 sqf(temp[SHF(0, -OFF, c)] - central_average[c]) +
1031 sqf(temp[SHF(0, 0, c)] - central_average[c]) +
1032 sqf(temp[SHF(0, +OFF, c)] - central_average[c]) +
1033 sqf(temp[SHF(+OFF, -OFF, c)] - central_average[c]) +
1034 sqf(temp[SHF(+OFF, 0, c)] - central_average[c]) +
1035 sqf(temp[SHF(+OFF, +OFF, c)] - central_average[c])
1036 ) / 9.0f;
1037 }
1038
1039 // Compute the patch-wise chroma covariance.
1040 // If covariance = 0, chroma channels are not correlated and we either have noise or chromatic aberrations.
1041 // Both ways, we want to discard that patch from the chroma average.
1042 var[2] = (
1043 (temp[SHF(-OFF, -OFF, 0)] - central_average[0]) * (temp[SHF(-OFF, -OFF, 1)] - central_average[1]) +
1044 (temp[SHF(-OFF, 0, 0)] - central_average[0]) * (temp[SHF(-OFF, 0, 1)] - central_average[1]) +
1045 (temp[SHF(-OFF, +OFF, 0)] - central_average[0]) * (temp[SHF(-OFF, +OFF, 1)] - central_average[1]) +
1046 (temp[SHF( 0, -OFF, 0)] - central_average[0]) * (temp[SHF( 0, -OFF, 1)] - central_average[1]) +
1047 (temp[SHF( 0, 0, 0)] - central_average[0]) * (temp[SHF( 0, 0, 1)] - central_average[1]) +
1048 (temp[SHF( 0, +OFF, 0)] - central_average[0]) * (temp[SHF( 0, +OFF, 1)] - central_average[1]) +
1049 (temp[SHF(+OFF, -OFF, 0)] - central_average[0]) * (temp[SHF(+OFF, -OFF, 1)] - central_average[1]) +
1050 (temp[SHF(+OFF, 0, 0)] - central_average[0]) * (temp[SHF(+OFF, 0, 1)] - central_average[1]) +
1051 (temp[SHF(+OFF, +OFF, 0)] - central_average[0]) * (temp[SHF(+OFF, +OFF, 1)] - central_average[1])
1052 ) / 9.0f;
1053
1054 // Compute the Minkowski p-norm for regularization
1055 const float p = 8.f;
1056 const float p_norm
1057 = powf(powf(fabsf(central_average[0]), p) + powf(fabsf(central_average[1]), p), 1.f / p) + NORM_MIN;
1058 const float weight = var[0] * var[1] * var[2];
1059
1060 for(size_t c = 0; c < 2; c++) xyY[c] += central_average[c] * weight / p_norm;
1061 elements += weight / p_norm;
1062 }
1063 }
1065 {
1066 __OMP_PARALLEL_FOR__(reduction(+:xyY, elements))
1067 for(size_t i = 2 * OFF; i < height - 4 * OFF; i += OFF)
1068 for(size_t j = 2 * OFF; j < width - 4 * OFF; j += OFF)
1069 {
1070 float DT_ALIGNED_PIXEL dd[2];
1071 float DT_ALIGNED_PIXEL central_average[2];
1072
1073 for(size_t c = 0; c < 2; c++)
1074 {
1075 // B-spline local average / blur
1076 central_average[c] = ( temp[SHF(-OFF, -OFF, c)] + 2.f * temp[SHF(-OFF, 0, c)] + temp[SHF(-OFF, +OFF, c)] +
1077 2.f * temp[SHF( 0, -OFF, c)] + 4.f * temp[SHF( 0, 0, c)] + 2.f * temp[SHF( 0, +OFF, c)] +
1078 temp[SHF(+OFF, -OFF, c)] + 2.f * temp[SHF(+OFF, 0, c)] + temp[SHF(+OFF, +OFF, c)]) / 16.0f;
1079
1080 // image - blur = laplacian = edges
1081 dd[c] = temp[SHF(0, 0, c)] - central_average[c];
1082 }
1083
1084 // Compute the Minkowski p-norm for regularization
1085 const float p = 8.f;
1086 const float p_norm = powf(powf(fabsf(dd[0]), p) + powf(fabsf(dd[1]), p), 1.f / p) + NORM_MIN;
1087
1088 for(size_t c = 0; c < 2; c++) xyY[c] -= dd[c] / p_norm;
1089 elements += 1.f;
1090 }
1091 }
1092
1093 const float D50[2] = { 0.34567f, 0.35850 };
1094 const float norm_D50 = dt_fast_hypotf(D50[0], D50[1]);
1095
1096 for(size_t c = 0; c < 2; c++)
1097 xyz[c] = norm_D50 * (xyY[c] / elements) + D50[c];
1098
1100 return 0;
1101}
1102
1104static void declare_cat_on_pipe(struct dt_iop_module_t *self, gboolean preset)
1105{
1106 // Advertise to the pipeline that we are doing chromatic adaptation here
1107 // preset = TRUE allows to capture the CAT a priori at init time
1109
1110 if((self->enabled && !(p->adaptation == DT_ADAPTATION_RGB || p->illuminant == DT_ILLUMINANT_PIPE)) || preset)
1111 {
1112 // We do CAT here so we need to register this instance as CAT-handler.
1114 {
1115 // We are the first to try to register, let's go !
1116 self->dev->proxy.chroma_adaptation = self;
1117 }
1118 else if(self->dev->proxy.chroma_adaptation == self)
1119 {
1120 }
1121 else
1122 {
1123 // Another instance already registered.
1124 // If we are lower in the pipe than it, register in its place.
1125 if(dt_iop_is_first_instance(self->dev->iop, self))
1126 self->dev->proxy.chroma_adaptation = self;
1127 }
1128 }
1129 else
1130 {
1132 {
1133 // We do NOT do CAT here.
1134 // Deregister this instance as CAT-handler if it previously registered
1135 if(self->dev->proxy.chroma_adaptation == self)
1136 self->dev->proxy.chroma_adaptation = NULL;
1137 }
1138 }
1139}
1140
1141static inline gboolean _is_another_module_cat_on_pipe(struct dt_iop_module_t *self)
1142{
1144 if(IS_NULL_PTR(g)) return FALSE;
1145 return self->dev->proxy.chroma_adaptation && self->dev->proxy.chroma_adaptation != self;
1146}
1147
1148
1149static void update_illuminants(struct dt_iop_module_t *self);
1150static void update_approx_cct(struct dt_iop_module_t *self);
1151static void update_illuminant_color(struct dt_iop_module_t *self);
1152
1153
1154static inline __attribute__((always_inline)) void check_if_close_to_daylight(const float x, const float y, float *temperature,
1156{
1157 /* Check if a chromaticity x, y is close to daylight within 2.5 % error margin.
1158 * If so, we enable the daylight GUI for better ergonomics
1159 * Otherwise, we default to direct x, y control for better accuracy
1160 *
1161 * Note : The use of CCT is discouraged if dE > 5 % in CIE 1960 Yuv space
1162 * reference : https://onlinelibrary.wiley.com/doi/abs/10.1002/9780470175637.ch3
1163 */
1164
1165 // Get the correlated color temperature (CCT)
1166 float t = xy_to_CCT(x, y);
1167
1168 // xy_to_CCT is valid only in 3000 - 25000 K. We need another model below
1169 if(t < 3000.f && t > 1667.f)
1170 t = CCT_reverse_lookup(x, y);
1171
1172 if(!IS_NULL_PTR(temperature))
1173 *temperature = t;
1174
1175 // Convert to CIE 1960 Yuv space
1176 float xy_ref[2] = { x, y };
1177 float uv_ref[2];
1178 xy_to_uv(xy_ref, uv_ref);
1179
1180 float xy_test[2] = { 0.f };
1181 float uv_test[2];
1182
1183 // Compute the test chromaticity from the daylight model
1184 illuminant_to_xy(DT_ILLUMINANT_D, NULL, NULL, &xy_test[0], &xy_test[1], t, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST);
1185 xy_to_uv(xy_test, uv_test);
1186
1187 // Compute the error between the reference illuminant and the test illuminant derivated from the CCT with daylight model
1188 const float delta_daylight = dt_fast_hypotf((uv_test[0] - uv_ref[0]), (uv_test[1] - uv_ref[1]));
1189
1190 // Compute the test chromaticity from the blackbody model
1191 illuminant_to_xy(DT_ILLUMINANT_BB, NULL, NULL, &xy_test[0], &xy_test[1], t, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST);
1192 xy_to_uv(xy_test, uv_test);
1193
1194 // Compute the error between the reference illuminant and the test illuminant derivated from the CCT with black body model
1195 const float delta_bb = dt_fast_hypotf((uv_test[0] - uv_ref[0]), (uv_test[1] - uv_ref[1]));
1196
1197 // Check the error between original and test chromaticity
1198 if(delta_bb < 0.005f || delta_daylight < 0.005f)
1199 {
1200 if(illuminant)
1201 {
1202 if(delta_bb < delta_daylight)
1204 else
1206 }
1207 }
1208 else
1209 {
1210 // error is too big to use a CCT-based model, we fall back to a custom/freestyle chroma selection for the illuminant
1212 }
1213
1214 // CAT16 is more accurate no matter the illuminant
1216}
1217
1218#define DEG_TO_RAD(x) (x * M_PI / 180.f)
1219#define RAD_TO_DEG(x) (x * 180.f / M_PI)
1220
1221static inline void compute_patches_delta_E(const float *const restrict patches,
1222 const dt_color_checker_t *const checker,
1223 float *const restrict delta_E, float *const restrict avg_delta_E, float *const restrict max_delta_E)
1224{
1225 // Compute the delta E
1226
1227 float dE = 0.f;
1228 float max_dE = 0.f;
1229
1230 for(size_t k = 0; k < checker->patches; k++)
1231 {
1232 // Convert to Lab
1233 dt_aligned_pixel_t Lab_test;
1234 dt_aligned_pixel_t XYZ_test;
1235
1236 // If exposure was normalized, denormalized it before
1237 for(size_t c = 0; c < 4; c++) XYZ_test[c] = patches[k * 4 + c];
1238 dt_XYZ_to_Lab(XYZ_test, Lab_test);
1239
1240 const float *const restrict Lab_ref = checker->values[k].Lab;
1241
1242 // Compute delta E 2000 to make your computer heat
1243 // ref: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
1244 // note : it will only be luck if I didn't mess-up the computation somewhere
1245 const float DL = Lab_ref[0] - Lab_test[0];
1246 const float L_avg = (Lab_ref[0] + Lab_test[0]) / 2.f;
1247 const float C_ref = dt_fast_hypotf(Lab_ref[1], Lab_ref[2]);
1248 const float C_test = dt_fast_hypotf(Lab_test[1], Lab_test[2]);
1249 const float C_avg = (C_ref + C_test) / 2.f;
1250 float C_avg_7 = C_avg * C_avg; // C_avg²
1251 C_avg_7 *= C_avg_7; // C_avg⁴
1252 C_avg_7 *= C_avg_7; // C_avg⁸
1253 C_avg_7 /= C_avg; // C_avg⁷
1254 const float C_avg_7_ratio_sqrt = sqrtf(C_avg_7 / (C_avg_7 + 6103515625.f)); // 25⁷ = 6103515625
1255 const float a_ref_prime = Lab_ref[1] * (1.f + 0.5f * (1.f - C_avg_7_ratio_sqrt));
1256 const float a_test_prime = Lab_test[1] * (1.f + 0.5f * (1.f - C_avg_7_ratio_sqrt));
1257 const float C_ref_prime = dt_fast_hypotf(a_ref_prime, Lab_ref[2]);
1258 const float C_test_prime = dt_fast_hypotf(a_test_prime, Lab_test[2]);
1259 const float DC_prime = C_ref_prime - C_test_prime;
1260 const float C_avg_prime = (C_ref_prime + C_test_prime) / 2.f;
1261 float h_ref_prime = atan2f(Lab_ref[2], a_ref_prime);
1262 float h_test_prime = atan2f(Lab_test[2], a_test_prime);
1263
1264 // Comply with recommendations, h = 0° where C = 0 by convention
1265 if(C_ref_prime == 0.f) h_ref_prime = 0.f;
1266 if(C_test_prime == 0.f) h_test_prime = 0.f;
1267
1268 // Get the hue angles from [-pi ; pi] back to [0 ; 2 pi],
1269 // again, to comply with specifications
1270 if(h_ref_prime < 0.f) h_ref_prime = 2.f * M_PI - h_ref_prime;
1271 if(h_test_prime < 0.f) h_test_prime = 2.f * M_PI - h_test_prime;
1272
1273 // Convert to degrees, again to comply with specs
1274 h_ref_prime = RAD_TO_DEG(h_ref_prime);
1275 h_test_prime = RAD_TO_DEG(h_test_prime);
1276
1277 float Dh_prime = h_test_prime - h_ref_prime;
1278 float Dh_prime_abs = fabsf(Dh_prime);
1279 if(C_test_prime == 0.f || C_ref_prime == 0.f)
1280 Dh_prime = 0.f;
1281 else if(Dh_prime_abs <= 180.f)
1282 ;
1283 else if(Dh_prime_abs > 180.f && (h_test_prime <= h_ref_prime))
1284 Dh_prime += 360.f;
1285 else if(Dh_prime_abs > 180.f && (h_test_prime > h_ref_prime))
1286 Dh_prime -= 360.f;
1287
1288 // update abs(Dh_prime) for later
1289 Dh_prime_abs = fabsf(Dh_prime);
1290
1291 const float DH_prime = 2.f * sqrtf(C_test_prime * C_ref_prime) * sinf(DEG_TO_RAD(Dh_prime) / 2.f);
1292 float H_avg_prime = h_ref_prime + h_test_prime;
1293 if(C_test_prime == 0.f || C_ref_prime == 0.f)
1294 ;
1295 else if(Dh_prime_abs <= 180.f)
1296 H_avg_prime /= 2.f;
1297 else if(Dh_prime_abs > 180.f && (H_avg_prime < 360.f))
1298 H_avg_prime = (H_avg_prime + 360.f) / 2.f;
1299 else if(Dh_prime_abs > 180.f && (H_avg_prime >= 360.f))
1300 H_avg_prime = (H_avg_prime - 360.f) / 2.f;
1301
1302 const float T = 1.f
1303 - 0.17f * cosf(DEG_TO_RAD(H_avg_prime) - DEG_TO_RAD(30.f))
1304 + 0.24f * cosf(2.f * DEG_TO_RAD(H_avg_prime))
1305 + 0.32f * cosf(3.f * DEG_TO_RAD(H_avg_prime) + DEG_TO_RAD(6.f))
1306 - 0.20f * cosf(4.f * DEG_TO_RAD(H_avg_prime) - DEG_TO_RAD(63.f));
1307
1308 const float S_L = 1.f + (0.015f * sqf(L_avg - 50.f)) / sqrtf(20.f + sqf(L_avg - 50.f));
1309 const float S_C = 1.f + 0.045f * C_avg_prime;
1310 const float S_H = 1.f + 0.015f * C_avg_prime * T;
1311 const float R_T = -2.f * C_avg_7_ratio_sqrt
1312 * sinf(DEG_TO_RAD(60.f) * expf(-sqf((H_avg_prime - 275.f) / 25.f)));
1313
1314 // roll the drum, here goes the Delta E, finally...
1315 const float DE = sqrtf(sqf(DL / S_L) + sqf(DC_prime / S_C) + sqf(DH_prime / S_H)
1316 + R_T * (DC_prime / S_C) * (DH_prime / S_H));
1317
1318 // Delta E 1976 for reference :
1319 //float DE = sqrtf(sqf(Lab_test[0] - Lab_ref[0]) + sqf(Lab_test[1] - Lab_ref[1]) + sqf(Lab_test[2] - Lab_ref[2]));
1320
1321 //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]);
1322 //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]);
1323 //fprintf(stdout, "patch %s : dE mes \t= \t%.3f \n", checker->values[k].name, DE);
1324
1325 delta_E[k] = DE;
1326 dE += DE / (float)checker->patches;
1327 if(DE > max_dE) max_dE = DE;
1328 }
1329
1330 *avg_delta_E = dE;
1331 *max_delta_E = max_dE;
1332}
1333
1334#define GET_WEIGHT \
1335 float hue = atan2f(reference[2], reference[1]); \
1336 const float chroma = hypotf(reference[2], reference[1]); \
1337 float delta_hue = hue - ref_hue; \
1338 if(chroma == 0.f) \
1339 delta_hue = 0.f; \
1340 else if(fabsf(delta_hue) <= M_PI) \
1341 ; \
1342 else if(fabsf(delta_hue) > M_PI && (hue <= ref_hue)) \
1343 delta_hue += 2.f * M_PI; \
1344 else if(fabsf(delta_hue) > M_PI && (hue > ref_hue)) \
1345 delta_hue -= 2.f * M_PI; \
1346 w = sqrtf(expf(-sqf(delta_hue) / 2.f));
1347
1348
1349typedef struct {
1350 float black;
1353
1355static int _extract_patches(const float *const restrict in, const dt_iop_roi_t *const roi_in,
1357 const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_CAM,
1358 float *const restrict patches,
1359 const gboolean normalize_exposure,
1360 extraction_result_t *result)
1361{
1362 const size_t width = roi_in->width;
1363 const size_t height = roi_in->height;
1364 const float radius_x = g->checker->radius * hypotf(1.f, g->checker->ratio) * g->safety_margin;
1365 const float radius_y = radius_x / g->checker->ratio;
1366
1367 if(IS_NULL_PTR(g->delta_E_in))
1368 {
1369 g->delta_E_in = dt_alloc_align_float(g->checker->patches);
1370 if(IS_NULL_PTR(g->delta_E_in)) return 1;
1371 }
1372
1373 /* Get the average color over each patch */
1374 for(size_t k = 0; k < g->checker->patches; k++)
1375 {
1376 // center of the patch in the ideal reference
1377 const point_t center = { g->checker->values[k].x, g->checker->values[k].y };
1378
1379 // corners of the patch in the ideal reference
1380 const point_t corners[4] = { {center.x - radius_x, center.y - radius_y},
1381 {center.x + radius_x, center.y - radius_y},
1382 {center.x + radius_x, center.y + radius_y},
1383 {center.x - radius_x, center.y + radius_y} };
1384
1385 // apply patch coordinates transform depending on perspective
1386 point_t new_corners[4];
1387 // find the bounding box of the patch at the same time
1388 size_t x_min = width - 1;
1389 size_t x_max = 0;
1390 size_t y_min = height - 1;
1391 size_t y_max = 0;
1392 for(size_t c = 0; c < 4; c++) {
1393 new_corners[c] = apply_homography(corners[c], g->homography);
1394 x_min = fminf(new_corners[c].x, x_min);
1395 x_max = fmaxf(new_corners[c].x, x_max);
1396 y_min = fminf(new_corners[c].y, y_min);
1397 y_max = fmaxf(new_corners[c].y, y_max);
1398 }
1399
1400 x_min = CLAMP((size_t)floorf(x_min), 0, width - 1);
1401 x_max = CLAMP((size_t)ceilf(x_max), 0, width - 1);
1402 y_min = CLAMP((size_t)floorf(y_min), 0, height - 1);
1403 y_max = CLAMP((size_t)ceilf(y_max), 0, height - 1);
1404
1405 // Get the average color on the patch
1406 patches[k * 4] = patches[k * 4 + 1] = patches[k * 4 + 2] = patches[k * 4 + 3] = 0.f;
1407 size_t num_elem = 0;
1408
1409 // Loop through the rectangular bounding box
1410 for(size_t j = y_min; j < y_max; j++)
1411 for(size_t i = x_min; i < x_max; i++)
1412 {
1413 // Check if this pixel lies inside the sampling area and sample if it does
1414 point_t current_point = { i + 0.5f, j + 0.5f };
1415 current_point = apply_homography(current_point, g->inverse_homography);
1416 current_point.x -= center.x;
1417 current_point.y -= center.y;
1418
1419 if(current_point.x < radius_x && current_point.x > -radius_x &&
1420 current_point.y < radius_y && current_point.y > -radius_y)
1421 {
1422 for(size_t c = 0; c < 3; c++)
1423 {
1424 patches[k * 4 + c] += in[(j * width + i) * 4 + c];
1425
1426 // Debug : inpaint a black square in the preview to ensure the coordanites of
1427 // overlay drawings and actual pixel processing match
1428 // out[(j * width + i) * 4 + c] = 0.f;
1429 }
1430 num_elem++;
1431 }
1432 }
1433
1434 for(size_t c = 0; c < 3; c++) patches[k * 4 + c] /= (float)num_elem;
1435
1436 // Convert to XYZ
1437 dt_aligned_pixel_t XYZ = { 0 };
1438 dot_product(patches + k * 4, RGB_to_XYZ, XYZ);
1439 for(size_t c = 0; c < 3; c++) patches[k * 4 + c] = XYZ[c];
1440 }
1441
1442 // find reference white patch
1443 dt_aligned_pixel_t XYZ_white_ref;
1444 dt_Lab_to_XYZ(g->checker->values[g->checker->white].Lab, XYZ_white_ref);
1445 const float white_ref_norm = euclidean_norm(XYZ_white_ref);
1446
1447 // find test white patch
1448 dt_aligned_pixel_t XYZ_white_test;
1449 for(size_t c = 0; c < 3; c++) XYZ_white_test[c] = patches[g->checker->white * 4 + c];
1450 const float white_test_norm = euclidean_norm(XYZ_white_test);
1451
1452 /* match global exposure */
1453 // white exposure depends on camera settings and raw white point,
1454 // we want our profile to be independent from that
1455 float exposure = white_ref_norm / white_test_norm;
1456
1457 /* Exposure compensation */
1458 // Ensure the relative luminance of the test patch (compared to white patch)
1459 // is the same as the relative luminance of the reference patch.
1460 // This compensate for lighting fall-off and unevenness
1461 if(normalize_exposure)
1462 {
1463 for(size_t k = 0; k < g->checker->patches; k++)
1464 {
1465 float *const sample = patches + k * 4;
1466
1467 dt_aligned_pixel_t XYZ_ref;
1468 dt_Lab_to_XYZ(g->checker->values[k].Lab, XYZ_ref);
1469
1470 const float sample_norm = euclidean_norm(sample);
1471 const float ref_norm = euclidean_norm(XYZ_ref);
1472
1473 const float relative_luminance_test = sample_norm / white_test_norm;
1474 const float relative_luminance_ref = ref_norm / white_ref_norm;
1475
1476 const float luma_correction = relative_luminance_ref / relative_luminance_test;
1477 for(size_t c = 0; c < 3; ++c) sample[c] *= luma_correction * exposure;
1478 }
1479 }
1480
1481 // black point is evaluated by rawspeed on each picture using the dark pixels
1482 // we want our profile to be also independent from its discrepancies
1483 // so we convert back the patches to camera RGB space and search the best fit of
1484 // RGB_ref = exposure * (RGB_test - offset) for offset.
1485 float black = 0.f;
1486
1487 if(XYZ_to_CAM)
1488 {
1489 float mean_ref = 0.f;
1490 float mean_test = 0.f;
1491
1492 for(size_t k = 0; k < g->checker->patches; k++)
1493 {
1494 dt_aligned_pixel_t XYZ_ref, RGB_ref;
1495 dt_aligned_pixel_t XYZ_test, RGB_test;
1496
1497 for(size_t c = 0; c < 3; c++) XYZ_test[c] = patches[k * 4 + c];
1498 dt_Lab_to_XYZ(g->checker->values[k].Lab, XYZ_ref);
1499
1500 dot_product(XYZ_test, XYZ_to_CAM, RGB_test);
1501 dot_product(XYZ_ref, XYZ_to_CAM, RGB_ref);
1502
1503 // From now on, we have all the reference and test data in camera RGB space
1504 // where exposure and black level are applied
1505
1506 for(int c = 0; c < 3; c++)
1507 {
1508 mean_test += RGB_test[c];
1509 mean_ref += RGB_ref[c];
1510 }
1511 }
1512 mean_test /= 3.f * g->checker->patches;
1513 mean_ref /= 3.f * g->checker->patches;
1514
1515 float variance = 0.f;
1516 float covariance = 0.f;
1517
1518 for(size_t k = 0; k < g->checker->patches; k++)
1519 {
1520 dt_aligned_pixel_t XYZ_ref, RGB_ref;
1521 dt_aligned_pixel_t XYZ_test, RGB_test;
1522
1523 for(size_t c = 0; c < 3; c++) XYZ_test[c] = patches[k * 4 + c];
1524 dt_Lab_to_XYZ(g->checker->values[k].Lab, XYZ_ref);
1525
1526 dot_product(XYZ_test, XYZ_to_CAM, RGB_test);
1527 dot_product(XYZ_ref, XYZ_to_CAM, RGB_ref);
1528
1529 for(int c = 0; c < 3; c++)
1530 {
1531 variance += sqf(RGB_test[c] - mean_test);
1532 covariance += (RGB_ref[c] - mean_ref) * (RGB_test[c] - mean_ref);
1533 }
1534 }
1535 variance /= 3.f * g->checker->patches;
1536 covariance /= 3.f * g->checker->patches;
1537
1538 // Here, we solve the least-squares problem RGB_ref = exposure * RGB_test + offset
1539 // using :
1540 // exposure = covariance(RGB_test, RGB_ref) / variance(RGB_test)
1541 // offset = mean(RGB_ref) - exposure * mean(RGB_test)
1542 exposure = covariance / variance;
1543 black = mean_ref - exposure * mean_test;
1544 }
1545
1546 // the exposure module applies output = (input - offset) * exposure
1547 // but we compute output = input * exposure + offset
1548 // so, rescale offset to adapt our offset to exposure module GUI
1549 black /= -exposure;
1550
1551 result->black = black;
1552 result->exposure = exposure;
1553 return 0;
1554}
1555
1557int extract_color_checker(const float *const restrict in, float *const restrict out,
1559 const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_RGB,
1560 const dt_colormatrix_t XYZ_to_CAM,
1561 const dt_adaptation_t kind)
1562{
1563 float *const restrict patches = dt_alloc_align_float(g->checker->patches * 4);
1564 if(IS_NULL_PTR(patches)) return 1;
1565
1566 dt_simd_memcpy(in, out, (size_t)roi_in->width * roi_in->height * 4);
1567
1568 extraction_result_t extraction_result = { 0 };
1569 if(_extract_patches(out, roi_in, g, RGB_to_XYZ, XYZ_to_CAM, patches, TRUE, &extraction_result))
1570 {
1571 dt_free_align(patches);
1572 return 1;
1573 }
1574
1575 // Compute the delta E
1576 float pre_wb_delta_E = 0.f;
1577 float pre_wb_max_delta_E = 0.f;
1578 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &pre_wb_delta_E, &pre_wb_max_delta_E);
1579
1580 /* find the scene illuminant */
1581
1582 // find reference grey patch
1583 dt_aligned_pixel_t XYZ_grey_ref;
1584 dt_Lab_to_XYZ(g->checker->values[g->checker->middle_grey].Lab, XYZ_grey_ref);
1585
1586 // find test grey patch
1587 dt_aligned_pixel_t XYZ_grey_test;
1588 for(size_t c = 0; c < 3; c++) XYZ_grey_test[c] = patches[g->checker->middle_grey * 4 + c];
1589
1590 // compute reference illuminant
1591 dt_aligned_pixel_t D50_XYZ;
1592 illuminant_xy_to_XYZ(0.34567f, 0.35850f, D50_XYZ);
1593
1594 // normalize luminances - note : illuminant is normalized by definition
1595 const float Y_test = XYZ_grey_test[1];
1596 const float Y_ref = XYZ_grey_ref[1];
1597 for(size_t c = 0; c < 3; c++)
1598 {
1599 XYZ_grey_ref[c] /= Y_ref;
1600 XYZ_grey_test[c] /= Y_test;
1601 }
1602
1603 // convert XYZ to LMS
1604 dt_aligned_pixel_t LMS_grey_ref, LMS_grey_test, D50_LMS;
1605 dt_store_simd_aligned(LMS_grey_ref, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_grey_ref), kind));
1606 dt_store_simd_aligned(LMS_grey_test, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_grey_test), kind));
1607 dt_store_simd_aligned(D50_LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(D50_XYZ), kind));
1608
1609 // solve the equation to find the scene illuminant
1610 dt_aligned_pixel_t illuminant = { 0.0f };
1611 for(size_t c = 0; c < 3; c++) illuminant[c] = D50_LMS[c] * LMS_grey_test[c] / LMS_grey_ref[c];
1612
1613 // convert back the illuminant to XYZ then xyY
1614 dt_aligned_pixel_t illuminant_XYZ, illuminant_xyY = { .0f };
1615 dt_store_simd_aligned(illuminant_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(illuminant), kind));
1616 const float Y_illu = illuminant_XYZ[1];
1617 for(size_t c = 0; c < 3; c++) illuminant_XYZ[c] /= Y_illu;
1618 dt_XYZ_to_xyY(illuminant_XYZ, illuminant_xyY);
1619
1620 // save the illuminant in GUI struct for commit
1621 g->xy[0] = illuminant_xyY[0];
1622 g->xy[1] = illuminant_xyY[1];
1623
1624 // and recompute back the LMS to be sure we use the parameters that will be computed later
1625 illuminant_xy_to_XYZ(illuminant_xyY[0], illuminant_xyY[1], illuminant_XYZ);
1626 dt_store_simd_aligned(illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(illuminant_XYZ), kind));
1627 const float p = powf(0.818155f / illuminant[2], 0.0834f);
1628
1629 /* White-balance the patches */
1630 for(size_t k = 0; k < g->checker->patches; k++)
1631 {
1632 // keep in synch with loop_switch() from process()
1633 float *const sample = patches + k * 4;
1634 const float Y = sample[1];
1635 downscale_vector(sample, Y);
1636
1638 dt_store_simd_aligned(LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(sample), kind));
1639
1640 dt_aligned_pixel_t temp;
1641 const dt_aligned_pixel_simd_t LMS_v = dt_load_simd_aligned(LMS);
1642 const dt_aligned_pixel_simd_t illuminant_v = dt_load_simd_aligned(illuminant);
1643
1644 switch(kind)
1645 {
1647 {
1648 dt_store_simd_aligned(temp, bradford_adapt_D50(LMS_v, illuminant_v, p, TRUE));
1649 break;
1650 }
1652 {
1653 dt_store_simd_aligned(temp, bradford_adapt_D50(LMS_v, illuminant_v, 1.f, FALSE));
1654 break;
1655 }
1657 {
1658 dt_store_simd_aligned(temp, CAT16_adapt_D50(LMS_v, illuminant_v, 1.f, TRUE)); // force full-adaptation
1659 break;
1660 }
1661 case DT_ADAPTATION_XYZ:
1662 {
1663 dt_store_simd_aligned(temp, XYZ_adapt_D50(LMS_v, illuminant_v));
1664 break;
1665 }
1666 case DT_ADAPTATION_RGB:
1667 case DT_ADAPTATION_LAST:
1668 default:
1669 {
1670 // No white balance.
1671 for(size_t c = 0; c < 3; ++c) temp[c] = LMS[c];
1672 break;
1673 }
1674 }
1675
1676 dt_store_simd_aligned(sample, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), kind));
1677 upscale_vector(sample, Y);
1678 }
1679
1680 // Compute the delta E
1681 float post_wb_delta_E = 0.f;
1682 float post_wb_max_delta_E = 0.f;
1683 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &post_wb_delta_E, &post_wb_max_delta_E);
1684
1685 /* Compute the matrix of mix */
1686 double *const restrict Y = dt_alloc_align(g->checker->patches * 3 * sizeof(double));
1687 double *const restrict A = dt_alloc_align(g->checker->patches * 3 * 9 * sizeof(double));
1688 if(IS_NULL_PTR(Y) || IS_NULL_PTR(A))
1689 {
1690 dt_free_align(Y);
1692 dt_free_align(patches);
1693 return 1;
1694 }
1695
1696 for(size_t k = 0; k < g->checker->patches; k++)
1697 {
1698 float *const sample = patches + k * 4;
1699 dt_aligned_pixel_t LMS_test;
1700 dt_store_simd_aligned(LMS_test, convert_any_XYZ_to_LMS(dt_load_simd_aligned(sample), kind));
1701
1702 const float *const reference = g->checker->values[k].Lab;
1703 dt_aligned_pixel_t XYZ_ref, LMS_ref;
1704 dt_Lab_to_XYZ(reference, XYZ_ref);
1705 dt_store_simd_aligned(LMS_ref, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_ref), kind));
1706
1707 // get the optimization weights
1708 float w = 1.f;
1709 if(g->optimization == DT_SOLVE_OPTIMIZE_NONE)
1710 w = sqrtf(1.f / (float)g->checker->patches);
1711 else if(g->optimization == DT_SOLVE_OPTIMIZE_HIGH_SAT)
1712 w = sqrtf(hypotf(reference[1] / 128.f, reference[2] / 128.f));
1713 else if(g->optimization == DT_SOLVE_OPTIMIZE_LOW_SAT)
1714 w = sqrtf(1.f - hypotf(reference[1] / 128.f, reference[2] / 128.f));
1715 else if(g->optimization == DT_SOLVE_OPTIMIZE_SKIN)
1716 {
1717 // average skin hue angle is 1.0 rad, hue range is [0.75 ; 1.25]
1718 const float ref_hue = 1.f;
1719 GET_WEIGHT;
1720 }
1721 else if(g->optimization == DT_SOLVE_OPTIMIZE_FOLIAGE)
1722 {
1723 // average foliage hue angle is 2.23 rad, hue range is [1.94 ; 2.44]
1724 const float ref_hue = 2.23f;
1725 GET_WEIGHT;
1726 }
1727 else if(g->optimization == DT_SOLVE_OPTIMIZE_SKY)
1728 {
1729 // average sky/water hue angle is -1.93 rad, hue range is [-1.64 ; -2.41]
1730 const float ref_hue = -1.93f;
1731 GET_WEIGHT;
1732 }
1733 else if(g->optimization == DT_SOLVE_OPTIMIZE_AVG_DELTA_E)
1734 w = sqrtf(sqrtf(1.f / g->delta_E_in[k]));
1735 else if(g->optimization == DT_SOLVE_OPTIMIZE_MAX_DELTA_E)
1736 w = sqrtf(sqrtf(g->delta_E_in[k]));
1737
1738 // fill 3 rows of the y column vector
1739 for(size_t c = 0; c < 3; c++) Y[k * 3 + c] = w * LMS_ref[c];
1740
1741 // fill line one of the A matrix
1742 A[k * 3 * 9 + 0] = w * LMS_test[0];
1743 A[k * 3 * 9 + 1] = w * LMS_test[1];
1744 A[k * 3 * 9 + 2] = w * LMS_test[2];
1745 A[k * 3 * 9 + 3] = 0.f;
1746 A[k * 3 * 9 + 4] = 0.f;
1747 A[k * 3 * 9 + 5] = 0.f;
1748 A[k * 3 * 9 + 6] = 0.f;
1749 A[k * 3 * 9 + 7] = 0.f;
1750 A[k * 3 * 9 + 8] = 0.f;
1751
1752 // fill line two of the A matrix
1753 A[k * 3 * 9 + 9 + 0] = 0.f;
1754 A[k * 3 * 9 + 9 + 1] = 0.f;
1755 A[k * 3 * 9 + 9 + 2] = 0.f;
1756 A[k * 3 * 9 + 9 + 3] = w * LMS_test[0];
1757 A[k * 3 * 9 + 9 + 4] = w * LMS_test[1];
1758 A[k * 3 * 9 + 9 + 5] = w * LMS_test[2];
1759 A[k * 3 * 9 + 9 + 6] = 0.f;
1760 A[k * 3 * 9 + 9 + 7] = 0.f;
1761 A[k * 3 * 9 + 9 + 8] = 0.f;
1762
1763 // fill line three of the A matrix
1764 A[k * 3 * 9 + 18 + 0] = 0.f;
1765 A[k * 3 * 9 + 18 + 1] = 0.f;
1766 A[k * 3 * 9 + 18 + 2] = 0.f;
1767 A[k * 3 * 9 + 18 + 3] = 0.f;
1768 A[k * 3 * 9 + 18 + 4] = 0.f;
1769 A[k * 3 * 9 + 18 + 5] = 0.f;
1770 A[k * 3 * 9 + 18 + 6] = w * LMS_test[0];
1771 A[k * 3 * 9 + 18 + 7] = w * LMS_test[1];
1772 A[k * 3 * 9 + 18 + 8] = w * LMS_test[2];
1773 }
1774
1775 if(pseudo_solve_gaussian(A, Y, g->checker->patches * 3, 9, TRUE) != 0)
1776 {
1777 dt_free_align(Y);
1779 dt_free_align(patches);
1780 return 1;
1781 }
1782
1783 // repack the matrix
1785
1786 dt_free_align(Y);
1788
1789 // apply the matrix mix
1790 for(size_t k = 0; k < g->checker->patches; k++)
1791 {
1792 float *const sample = patches + k * 4;
1793 dt_aligned_pixel_t LMS_test;
1794 dt_aligned_pixel_t temp = { 0.f };
1795
1796 // Restore the original exposure of the patch
1797 for(size_t c = 0; c < 3; c++) temp[c] = sample[c];
1798
1799 dt_store_simd_aligned(LMS_test, convert_any_XYZ_to_LMS(dt_load_simd_aligned(temp), kind));
1800 dot_product(LMS_test, g->mix, temp);
1801 dt_store_simd_aligned(sample, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), kind));
1802 }
1803
1804 // Compute the delta E
1805 float post_mix_delta_E = 0.f;
1806 float post_mix_max_delta_E = 0.f;
1807 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &post_mix_delta_E, &post_mix_max_delta_E);
1808
1809 // get the temperature
1810 float temperature;
1811 dt_illuminant_t test_illuminant;
1812 float x = illuminant_xyY[0];
1813 float y = illuminant_xyY[1];
1814 check_if_close_to_daylight(x, y, &temperature, &test_illuminant, NULL);
1815 gchar *string;
1816 if(test_illuminant == DT_ILLUMINANT_D)
1817 string = _("(daylight)");
1818 else if(test_illuminant == DT_ILLUMINANT_BB)
1819 string = _("(black body)");
1820 else
1821 string = _("(invalid)");
1822
1823 gchar *diagnostic;
1824 if(post_mix_delta_E <= 1.2f)
1825 diagnostic = _("very good");
1826 else if(post_mix_delta_E <= 2.3f)
1827 diagnostic = _("good");
1828 else if(post_mix_delta_E <= 3.4f)
1829 diagnostic = _("passable");
1830 else
1831 diagnostic = _("bad");
1832
1833 g->profile_ready = TRUE;
1834
1835 // Update GUI label
1836 dt_free(g->delta_E_label_text);
1837 g->delta_E_label_text
1838 = g_strdup_printf(_("\n<b>Profile quality report: %s</b>\n"
1839 "input \316\224E: \tavg. %.2f ; \tmax. %.2f\n"
1840 "WB \316\224E: \tavg. %.2f; \tmax. %.2f\n"
1841 "output \316\224E: \tavg. %.2f; \tmax. %.2f\n\n"
1842 "<b>Profile data</b>\n"
1843 "illuminant: \t%.0f K \t%s\n"
1844 "matrix in adaptation space:\n"
1845 "<tt>%+.4f \t%+.4f \t%+.4f\n"
1846 "%+.4f \t%+.4f \t%+.4f\n"
1847 "%+.4f \t%+.4f \t%+.4f</tt>\n\n"
1848 "<b>Normalization values</b>\n"
1849 "exposure compensation: \t%+.2f EV\n"
1850 "black offset: \t%+.4f"
1851 ),
1852 diagnostic, pre_wb_delta_E, pre_wb_max_delta_E, post_wb_delta_E, post_wb_max_delta_E,
1853 post_mix_delta_E, post_mix_max_delta_E, temperature, string, g->mix[0][0], g->mix[0][1],
1854 g->mix[0][2], g->mix[1][0], g->mix[1][1], g->mix[1][2], g->mix[2][0], g->mix[2][1],
1855 g->mix[2][2], log2f(extraction_result.exposure), extraction_result.black);
1856
1857 dt_free_align(patches);
1858 return 0;
1859}
1860
1861int validate_color_checker(const float *const restrict in,
1863 const dt_colormatrix_t RGB_to_XYZ, const dt_colormatrix_t XYZ_to_RGB, const dt_colormatrix_t XYZ_to_CAM)
1864{
1865 float *const restrict patches = dt_alloc_align_float(4 * g->checker->patches);
1866 if(IS_NULL_PTR(patches)) return 1;
1867 extraction_result_t extraction_result = { 0 };
1868 if(_extract_patches(in, roi_in, g, RGB_to_XYZ, XYZ_to_CAM, patches, FALSE, &extraction_result))
1869 {
1870 dt_free_align(patches);
1871 return 1;
1872 }
1873
1874 // Compute the delta E
1875 float pre_wb_delta_E = 0.f;
1876 float pre_wb_max_delta_E = 0.f;
1877 compute_patches_delta_E(patches, g->checker, g->delta_E_in, &pre_wb_delta_E, &pre_wb_max_delta_E);
1878
1879 gchar *diagnostic;
1880 if(pre_wb_delta_E <= 1.2f)
1881 diagnostic = _("very good");
1882 else if(pre_wb_delta_E <= 2.3f)
1883 diagnostic = _("good");
1884 else if(pre_wb_delta_E <= 3.4f)
1885 diagnostic = _("passable");
1886 else
1887 diagnostic = _("bad");
1888
1889 // Update GUI label
1890 dt_free(g->delta_E_label_text);
1891 g->delta_E_label_text = g_strdup_printf(_("\n<b>Profile quality report: %s</b>\n"
1892 "output \316\224E: \tavg. %.2f; \tmax. %.2f\n\n"
1893 "<b>Normalization values</b>\n"
1894 "exposure compensation: \t%+.2f EV\n"
1895 "black offset: \t%+.4f"),
1896 diagnostic, pre_wb_delta_E, pre_wb_max_delta_E, log2f(extraction_result.exposure),
1897 extraction_result.black);
1898
1899 dt_free_align(patches);
1900 return 0;
1901}
1902
1903int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1904 const void *const restrict ivoid, void *const restrict ovoid)
1905{
1906 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1907 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1909 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
1910 const struct dt_iop_order_iccprofile_info_t *const input_profile = dt_ioppr_get_pipe_input_profile_info(pipe);
1912
1914
1915 dt_colormatrix_t RGB_to_XYZ;
1916 dt_colormatrix_t XYZ_to_RGB;
1917 dt_colormatrix_t XYZ_to_CAM;
1918
1919 // repack the matrices as flat AVX2-compliant matrice
1920 if(!IS_NULL_PTR(work_profile))
1921 {
1922 // work profile can't be fetched in commit_params since it is not yet initialised
1923 memcpy(RGB_to_XYZ, work_profile->matrix_in, sizeof(RGB_to_XYZ));
1924 memcpy(XYZ_to_RGB, work_profile->matrix_out, sizeof(XYZ_to_RGB));
1925 memcpy(XYZ_to_CAM, input_profile->matrix_out, sizeof(XYZ_to_CAM));
1926 }
1927
1928 const size_t ch = 4;
1929
1930 const float *const restrict in = (const float *const restrict)ivoid;
1931 float *const restrict out = (float *const restrict)ovoid;
1932
1933 // auto-detect WB upon request
1934 if(self->dev->gui_attached && g)
1935 {
1936 gboolean exit = FALSE;
1937
1938 if(g->run_profile && pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
1939 {
1941 const int err = extract_color_checker(in, out, roi_in, g, RGB_to_XYZ, XYZ_to_RGB, XYZ_to_CAM, data->adaptation);
1942 g->run_profile = FALSE;
1945 if(err) return 1;
1946 }
1947
1949 {
1950 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
1951 {
1952 // detection on full image only
1954 const int err = auto_detect_WB(in, data->illuminant_type, roi_in->width, roi_in->height, ch, RGB_to_XYZ, g->XYZ);
1956 if(err) return 1;
1957 }
1958
1959 // passthrough pixels
1960 dt_iop_image_copy_by_size(out, in, roi_in->width, roi_in->height, ch);
1961
1962 dt_control_log(_("auto-detection of white balance completed"));
1963
1964 exit = TRUE;
1965 }
1966
1967 if(exit) return 0;
1968 }
1969
1971 {
1972 // The camera illuminant is a behaviour rather than a preset of values:
1973 // it uses whatever is in the RAW EXIF. But it depends on what temperature.c is doing
1974 // and needs to be updated accordingly, to give a consistent result.
1975 // We initialise the CAT defaults using the temperature coeffs at startup, but if temperature
1976 // is changed later, we get no notification of the change here, so we can't update the defaults.
1977 // So we need to re-run the detection at runtime...
1978 float x, y;
1979 dt_aligned_pixel_t custom_wb;
1980 // Pipeline thread: source temperature's WB from the per-piece descriptor propagated in pipe
1981 // order (temperature writes piece->dsc_out.temperature.coeffs), NOT from dev->proxy.
1982 _custom_wb_from_coeffs(self, piece->dsc_in.temperature.coeffs, custom_wb);
1983
1984 if(find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(x), &(y)))
1985 {
1986 // Convert illuminant from xyY to XYZ
1989
1990 // Convert illuminant from XYZ to Bradford modified LMS
1991 dt_store_simd_aligned(data->illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), data->adaptation));
1992 data->illuminant[3] = 0.f;
1993 }
1994 else
1995 {
1996 // just use whatever was defined in commit_params hoping the defaults work...
1997 }
1998 }
1999
2000 // force loop unswitching in a controlled way
2001 switch(data->adaptation)
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_FULL_BRADFORD, data->version);
2009 break;
2010 }
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_LINEAR_BRADFORD, data->version);
2017 break;
2018 }
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_CAT16, data->version);
2025 break;
2026 }
2027 case DT_ADAPTATION_XYZ:
2028 {
2029 loop_switch(in, out, roi_out->width, roi_out->height, ch,
2030 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
2031 data->illuminant, data->saturation, data->lightness, data->grey,
2032 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_XYZ, data->version);
2033 break;
2034 }
2035 case DT_ADAPTATION_RGB:
2036 {
2037 loop_switch(in, out, roi_out->width, roi_out->height, ch,
2038 XYZ_to_RGB, RGB_to_XYZ, data->MIX,
2039 data->illuminant, data->saturation, data->lightness, data->grey,
2040 data->p, data->gamut, data->clip, data->apply_grey, DT_ADAPTATION_RGB, data->version);
2041 break;
2042 }
2043 case DT_ADAPTATION_LAST:
2044 default:
2045 {
2046 break;
2047 }
2048 }
2049
2050 // run dE validation at output
2051 if(self->dev->gui_attached && g)
2052 if(g->run_validation && pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
2053 {
2054 const int err = validate_color_checker(out, roi_out, g, RGB_to_XYZ, XYZ_to_RGB, XYZ_to_CAM);
2055 g->run_validation = FALSE;
2056 if(err) return 1;
2057 }
2058
2059 return 0;
2060}
2061
2062#if HAVE_OPENCL
2063int 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)
2064{
2065 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2068 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
2069 //dt_iop_channelmixer_rgb_gui_data_t *g = (dt_iop_channelmixer_rgb_gui_data_t *)self->gui_data;
2070
2072
2073 if(d->illuminant_type == DT_ILLUMINANT_CAMERA)
2074 {
2075 // The camera illuminant is a behaviour rather than a preset of values:
2076 // it uses whatever is in the RAW EXIF. But it depends on what temperature.c is doing
2077 // and needs to be updated accordingly, to give a consistent result.
2078 // We initialise the CAT defaults using the temperature coeffs at startup, but if temperature
2079 // is changed later, we get no notification of the change here, so we can't update the defaults.
2080 // So we need to re-run the detection at runtime...
2081 float x, y;
2082 dt_aligned_pixel_t custom_wb;
2083 // Pipeline thread: source temperature's WB from the per-piece descriptor propagated in pipe
2084 // order (temperature writes piece->dsc_out.temperature.coeffs), NOT from dev->proxy.
2085 _custom_wb_from_coeffs(self, piece->dsc_in.temperature.coeffs, custom_wb);
2086
2087 if(find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(x), &(y)))
2088 {
2089 // Convert illuminant from xyY to XYZ
2092
2093 // Convert illuminant from XYZ to Bradford modified LMS
2094 dt_store_simd_aligned(d->illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), d->adaptation));
2095 d->illuminant[3] = 0.f;
2096 }
2097 }
2098
2099 cl_int err = -999;
2100
2101 const int devid = pipe->devid;
2102 const int width = roi_in->width;
2103 const int height = roi_in->height;
2104
2105 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2106
2107 cl_mem input_matrix_cl = NULL;
2108 cl_mem output_matrix_cl = NULL;
2109
2110 float input_matrix_3x4[12];
2111 float output_matrix_3x4[12];
2112 float mix_matrix_3x4[12];
2113 pack_3xSSE_to_3x4(work_profile->matrix_in, input_matrix_3x4);
2114 pack_3xSSE_to_3x4(work_profile->matrix_out, output_matrix_3x4);
2115 pack_3xSSE_to_3x4(d->MIX, mix_matrix_3x4);
2116
2117 input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(input_matrix_3x4), input_matrix_3x4);
2118 output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(output_matrix_3x4), output_matrix_3x4);
2119 cl_mem MIX_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(mix_matrix_3x4), mix_matrix_3x4);
2120
2121 // select the right kernel for the current LMS space
2123
2124 switch(d->adaptation)
2125 {
2127 {
2129 break;
2130 }
2132 {
2134 break;
2135 }
2137 {
2139 break;
2140 }
2141 case DT_ADAPTATION_XYZ:
2142 {
2144 break;
2145 }
2146 case DT_ADAPTATION_RGB:
2147 case DT_ADAPTATION_LAST:
2148 default:
2149 {
2151 break;
2152 }
2153 }
2154
2155 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
2156 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
2157 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
2158 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
2159 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&input_matrix_cl);
2160 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), (void *)&output_matrix_cl);
2161 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), (void *)&MIX_cl);
2162 dt_opencl_set_kernel_arg(devid, kernel, 7, 4 * sizeof(float), (void *)&d->illuminant);
2163 dt_opencl_set_kernel_arg(devid, kernel, 8, 4 * sizeof(float), (void *)&d->saturation);
2164 dt_opencl_set_kernel_arg(devid, kernel, 9, 4 * sizeof(float), (void *)&d->lightness);
2165 dt_opencl_set_kernel_arg(devid, kernel, 10, 4 * sizeof(float), (void *)&d->grey);
2166 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(float), (void *)&d->p);
2167 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(float), (void *)&d->gamut);
2168 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(int), (void *)&d->clip);
2169 dt_opencl_set_kernel_arg(devid, kernel, 14, sizeof(int), (void *)&d->apply_grey);
2170 dt_opencl_set_kernel_arg(devid, kernel, 15, sizeof(int), (void *)&d->version);
2171 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
2172 if(err != CL_SUCCESS) goto error;
2173
2174 dt_opencl_release_mem_object(input_matrix_cl);
2175 dt_opencl_release_mem_object(output_matrix_cl);
2177 return TRUE;
2178
2179error:
2180 dt_opencl_release_mem_object(input_matrix_cl);
2181 dt_opencl_release_mem_object(output_matrix_cl);
2183 dt_print(DT_DEBUG_OPENCL, "[opencl_channelmixerrgb] couldn't enqueue kernel! %d\n", err);
2184 return FALSE;
2185}
2186
2188{
2189 const int program = 32; // extended.cl in programs.conf
2192
2193 module->data = gd;
2194 gd->kernel_channelmixer_rgb_cat16 = dt_opencl_create_kernel(program, "channelmixerrgb_CAT16");
2195 gd->kernel_channelmixer_rgb_bradford_full = dt_opencl_create_kernel(program, "channelmixerrgb_bradford_full");
2196 gd->kernel_channelmixer_rgb_bradford_linear = dt_opencl_create_kernel(program, "channelmixerrgb_bradford_linear");
2197 gd->kernel_channelmixer_rgb_xyz = dt_opencl_create_kernel(program, "channelmixerrgb_XYZ");
2198 gd->kernel_channelmixer_rgb_rgb = dt_opencl_create_kernel(program, "channelmixerrgb_RGB");
2199}
2200
2201
2212#endif
2213
2214
2216 const float x_increment, const float y_increment)
2217{
2218 // update box nodes
2219 for(size_t k = 0; k < 4; k++)
2220 {
2221 if(g->active_node[k])
2222 {
2223 g->box[k].x += x_increment;
2224 g->box[k].y += y_increment;
2225 }
2226 }
2227
2228 // update the homography
2229 get_homography(g->ideal_box, g->box, g->homography);
2230 get_homography(g->box, g->ideal_box, g->inverse_homography);
2231}
2232
2233static inline void init_bounding_box(dt_iop_channelmixer_rgb_gui_data_t *g, const float width, const float height)
2234{
2235 if(g->checker && !g->checker_ready)
2236 {
2237 // top left
2238 g->box[0].x = g->box[0].y = 10.;
2239
2240 // top right
2241 g->box[1].x = (width - 10.);
2242 g->box[1].y = g->box[0].y;
2243
2244 // bottom right
2245 g->box[2].x = g->box[1].x;
2246 g->box[2].y = (width - 10.) * g->checker->ratio;
2247
2248 // bottom left
2249 g->box[3].x = g->box[0].x;
2250 g->box[3].y = g->box[2].y;
2251
2252 g->checker_ready = TRUE;
2253 }
2254
2255 g->center_box.x = 0.5f;
2256 g->center_box.y = 0.5f;
2257
2258 g->ideal_box[0].x = 0.f;
2259 g->ideal_box[0].y = 0.f;
2260 g->ideal_box[1].x = 1.f;
2261 g->ideal_box[1].y = 0.f;
2262 g->ideal_box[2].x = 1.f;
2263 g->ideal_box[2].y = 1.f;
2264 g->ideal_box[3].x = 0.f;
2265 g->ideal_box[3].y = 1.f;
2266
2267 update_bounding_box(g, 0.f, 0.f);
2268}
2269
2270
2271
2272int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
2273{
2274 if(!self->enabled) return 0;
2275
2277 if(IS_NULL_PTR(g) || !g->is_profiling_started) return 0;
2278 if(g->box[0].x == -1.0f || g->box[1].y == -1.0f) return 0;
2279
2280 dt_develop_t *dev = self->dev;
2281 const float wd = dev->roi.preview_width;
2282 const float ht = dev->roi.preview_height;
2283 if(wd == 0.f || ht == 0.f) return 0;
2284
2285 float pzxpy[2] = { (float)x, (float)y };
2288 const float pzx = pzxpy[0];
2289 const float pzy = pzxpy[1];
2290
2291 // if dragging and dropping, don't update active nodes,
2292 // just update cursor coordinates then redraw
2293 // this ensure smooth updates
2294 if(g->drag_drop)
2295 {
2297 g->click_end.x = pzx;
2298 g->click_end.y = pzy;
2299
2300 update_bounding_box(g, g->click_end.x - g->click_start.x, g->click_end.y - g->click_start.y);
2301
2302 g->click_start.x = pzx;
2303 g->click_start.y = pzy;
2305
2307 return 1;
2308 }
2309
2310 // Find out if we are close to a node
2312 g->is_cursor_close = FALSE;
2313
2314 for(size_t k = 0; k < 4; k++)
2315 {
2316 if(hypotf(pzx - g->box[k].x, pzy - g->box[k].y) < 15.f)
2317 {
2318 g->active_node[k] = TRUE;
2319 g->is_cursor_close = TRUE;
2320 }
2321 else
2322 g->active_node[k] = FALSE;
2323 }
2325
2326 // if cursor is close from a node, remove the system pointer arrow to prevent hiding the spot behind it
2327 if(g->is_cursor_close)
2328 {
2330 }
2331 else
2332 {
2333 // fall back to default cursor
2336 }
2337
2339
2340 return 1;
2341}
2342
2343int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
2344 uint32_t state)
2345{
2346 if(!self->enabled) return 0;
2347
2349 if(IS_NULL_PTR(g) || !g->is_profiling_started) return 0;
2350
2351 dt_develop_t *dev = self->dev;
2352 const float wd = dev->roi.preview_width;
2353 const float ht = dev->roi.preview_height;
2354 if(wd == 0.f || ht == 0.f) return 0;
2355
2356 // double click : reset the perspective correction
2357 if(type == GDK_DOUBLE_BUTTON_PRESS)
2358 {
2360 g->checker_ready = FALSE;
2361 g->profile_ready = FALSE;
2362 init_bounding_box(g, wd, ht);
2364
2366 return 1;
2367 }
2368
2369 // bounded box not inited, abort
2370 if(g->box[0].x == -1.0f || g->box[1].y == -1.0f) return 0;
2371
2372 // cursor is not on a node, abort
2373 if(!g->is_cursor_close) return 0;
2374
2375 float pzxpy[2] = { (float)x, (float)y };
2378 const float pzx = pzxpy[0];
2379 const float pzy = pzxpy[1];
2380
2382 g->drag_drop = TRUE;
2383 g->click_start.x = pzx;
2384 g->click_start.y = pzy;
2386
2388
2389 return 1;
2390}
2391
2392int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
2393{
2394 if(!self->enabled) return 0;
2395
2397 if(IS_NULL_PTR(g) || !g->is_profiling_started) return 0;
2398 if(g->box[0].x == -1.0f || g->box[1].y == -1.0f) return 0;
2399 if(!g->is_cursor_close || !g->drag_drop) return 0;
2400
2401 dt_develop_t *dev = self->dev;
2402 const float wd = dev->roi.preview_width;
2403 const float ht = dev->roi.preview_height;
2404 if(wd == 0.f || ht == 0.f) return 0;
2405
2406 float pzxpy[2] = { (float)x, (float)y };
2409 const float pzx = pzxpy[0];
2410 const float pzy = pzxpy[1];
2411
2413 g->drag_drop = FALSE;
2414 g->click_end.x = pzx;
2415 g->click_end.y = pzy;
2416 update_bounding_box(g, g->click_end.x - g->click_start.x, g->click_end.y - g->click_start.y);
2418
2420
2421 return 1;
2422}
2423
2424void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
2425 int32_t pointerx, int32_t pointery)
2426{
2428 if(IS_NULL_PTR(work_profile)) return;
2429
2431 if(IS_NULL_PTR(g) || !g->is_profiling_started) return;
2432
2433 // Rescale and shift Cairo drawing coordinates
2434 dt_develop_t *dev = self->dev;
2435 if(dt_dev_rescale_roi(dev, cr, width, height)) return;
2436 const float zoom_scale = dt_dev_get_overlay_scale(dev);
2437
2438 cairo_set_line_width(cr, 2.0 / zoom_scale);
2439 const double origin = 9. / zoom_scale;
2440 const double destination = 18. / zoom_scale;
2441
2442 for(size_t k = 0; k < 4; k++)
2443 {
2444 if(g->active_node[k])
2445 {
2446 // draw cross hair
2447 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2448
2449 cairo_move_to(cr, g->box[k].x - origin, g->box[k].y);
2450 cairo_line_to(cr, g->box[k].x - destination, g->box[k].y);
2451
2452 cairo_move_to(cr, g->box[k].x + origin, g->box[k].y);
2453 cairo_line_to(cr, g->box[k].x + destination, g->box[k].y);
2454
2455 cairo_move_to(cr, g->box[k].x, g->box[k].y - origin);
2456 cairo_line_to(cr, g->box[k].x, g->box[k].y - destination);
2457
2458 cairo_move_to(cr, g->box[k].x, g->box[k].y + origin);
2459 cairo_line_to(cr, g->box[k].x, g->box[k].y + destination);
2460
2461 cairo_stroke(cr);
2462 }
2463
2464 // draw outline circle
2465 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2466 cairo_arc(cr, g->box[k].x, g->box[k].y, 8. / zoom_scale, 0, 2. * M_PI);
2467 cairo_stroke(cr);
2468
2469 // draw black dot
2470 cairo_set_source_rgba(cr, 0., 0., 0., 1.);
2471 cairo_arc(cr, g->box[k].x, g->box[k].y, 1.5 / zoom_scale, 0, 2. * M_PI);
2472 cairo_fill(cr);
2473 }
2474
2475 // draw symmetry axes
2476 cairo_set_line_width(cr, 1.5 / zoom_scale);
2477 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2478 const point_t top_ideal = { 0.5f, 1.f };
2479 const point_t top = apply_homography(top_ideal, g->homography);
2480 const point_t bottom_ideal = { 0.5f, 0.f };
2481 const point_t bottom = apply_homography(bottom_ideal, g->homography);
2482 cairo_move_to(cr, top.x, top.y);
2483 cairo_line_to(cr, bottom.x, bottom.y);
2484 cairo_stroke(cr);
2485
2486 const point_t left_ideal = { 0.f, 0.5f };
2487 const point_t left = apply_homography(left_ideal, g->homography);
2488 const point_t right_ideal = { 1.f, 0.5f };
2489 const point_t right = apply_homography(right_ideal, g->homography);
2490 cairo_move_to(cr, left.x, left.y);
2491 cairo_line_to(cr, right.x, right.y);
2492 cairo_stroke(cr);
2493
2494 /* For debug : display center of the image and center of the ideal target
2495 point_t new_target_center = apply_homography(target_center, g->homography);
2496 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2497 cairo_arc(cr, new_target_center.x, new_target_center.y, 7., 0, 2. * M_PI);
2498 cairo_stroke(cr);
2499
2500 cairo_set_source_rgba(cr, 0., 1., 1., 1.);
2501 cairo_arc(cr, 0.5 * wd, 0.5 * ht, 7., 0, 2. * M_PI);
2502 cairo_stroke(cr);
2503 */
2504 if(!g->checker || !g->checker->finished || !g->checker->values)
2505 {
2506 // no color data available, display a message
2507
2508 const point_t target_center = { 0.5f, 0.5f };
2509 point_t new_target_center = apply_homography(target_center, g->homography);
2510
2511 const char *msg = _("Error: No color data available for the selected chart.");
2512 cairo_set_font_size(cr, 20.0 / zoom_scale);
2513 cairo_text_extents_t extents;
2514 cairo_text_extents(cr, msg, &extents);
2515
2516 // Draw a white rectangle behind the text
2517 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2518 cairo_rectangle(cr,
2519 new_target_center.x - extents.width / 2.0 - 5.0 / zoom_scale,
2520 new_target_center.y - extents.height / 2.0 - 5.0 / zoom_scale,
2521 extents.width + 10.0 / zoom_scale,
2522 extents.height + 10.0 / zoom_scale);
2523 cairo_fill(cr);
2524
2525 // Draw the text in red
2526 cairo_set_source_rgba(cr, 1., 0., 0., 1.);
2527 cairo_select_font_face(cr, "roboto", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
2528 cairo_move_to(cr, new_target_center.x - extents.width / 2.0, new_target_center.y + extents.height / 2.0);
2529 cairo_show_text(cr, msg);
2530 return;
2531 }
2532
2533 const float radius_x = g->checker->radius * hypotf(1.f, g->checker->ratio) * g->safety_margin;
2534 const float radius_y = radius_x / g->checker->ratio;
2535
2536 for(size_t k = 0; k < g->checker->patches; k++)
2537 {
2538 // center of the patch in the ideal reference
2539 const point_t center = { g->checker->values[k].x, g->checker->values[k].y };
2540
2541 // corners of the patch in the ideal reference
2542 const point_t corners[4] = { {center.x - radius_x, center.y - radius_y},
2543 {center.x + radius_x, center.y - radius_y},
2544 {center.x + radius_x, center.y + radius_y},
2545 {center.x - radius_x, center.y + radius_y} };
2546
2547 // apply patch coordinates transform depending on perspective
2548 const point_t new_center = apply_homography(center, g->homography);
2549 // apply_homography_scaling gives a scaling of areas. we need to scale the
2550 // radius of the center circle so take a square root.
2551 const float scaling = sqrtf(apply_homography_scaling(center, g->homography));
2552 point_t new_corners[4];
2553 for(size_t c = 0; c < 4; c++) new_corners[c] = apply_homography(corners[c], g->homography);
2554
2555 cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
2556 cairo_set_source_rgba(cr, 0., 0., 0., 1.);
2557 cairo_move_to(cr, new_corners[0].x, new_corners[0].y);
2558 cairo_line_to(cr, new_corners[1].x, new_corners[1].y);
2559 cairo_line_to(cr, new_corners[2].x, new_corners[2].y);
2560 cairo_line_to(cr, new_corners[3].x, new_corners[3].y);
2561 cairo_line_to(cr, new_corners[0].x, new_corners[0].y);
2562
2563 if(g->delta_E_in)
2564 {
2565 // draw delta E feedback
2566 if(g->delta_E_in[k] > 2.3f)
2567 {
2568 // one diagonal if delta E > 3
2569 cairo_move_to(cr, new_corners[0].x, new_corners[0].y);
2570 cairo_line_to(cr, new_corners[2].x, new_corners[2].y);
2571 }
2572 if(g->delta_E_in[k] > 4.6f)
2573 {
2574 // the other diagonal if delta E > 6
2575 cairo_move_to(cr, new_corners[1].x, new_corners[1].y);
2576 cairo_line_to(cr, new_corners[3].x, new_corners[3].y);
2577 }
2578 }
2579
2580 cairo_set_line_width(cr, 5.0 / zoom_scale);
2581 cairo_stroke_preserve(cr);
2582 cairo_set_line_width(cr, 2.0 / zoom_scale);
2583 cairo_set_source_rgba(cr, 1., 1., 1., 1.);
2584 cairo_stroke(cr);
2585
2586 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
2587
2589 dt_ioppr_lab_to_rgb_matrix(g->checker->values[k].Lab, RGB, work_profile->matrix_out_transposed, work_profile->lut_out,
2590 work_profile->unbounded_coeffs_out, work_profile->lutsize,
2591 work_profile->nonlinearlut);
2592
2593 cairo_set_source_rgba(cr, RGB[0], RGB[1], RGB[2], 1.);
2594 cairo_arc(cr, new_center.x, new_center.y, 0.25 * (radius_x + radius_y) * scaling, 0, 2. * M_PI);
2595 cairo_fill(cr);
2596 }
2597}
2598
2599void color_list_visibility(dt_iop_module_t *self, const int checker_cmbbx_index)
2600{
2602
2603 if(checker_cmbbx_index >= COLOR_CHECKER_USER_REF)
2604 if(dt_bauhaus_combobox_get_entry(GTK_WIDGET(g->checkers_color_list), 0) != NULL)
2605 {
2606 gtk_widget_show(GTK_WIDGET(g->checkers_color_list));
2607 gtk_widget_hide(GTK_WIDGET(g->checker_msg));
2608 }
2609 else
2610 {
2611 gtk_widget_hide(GTK_WIDGET(g->checkers_color_list));
2612 gtk_widget_show_now(GTK_WIDGET(g->checker_msg));
2613 }
2614
2615 else
2616 {
2617 gtk_widget_hide(GTK_WIDGET(g->checkers_color_list));
2618 gtk_widget_hide(GTK_WIDGET(g->checker_msg));
2619 }
2620}
2621
2628{
2630
2631 if(!g) return;
2632 if(!g->colorcheckers) return;
2633
2634 const int selected_checker = dt_conf_get_int("darkroom/modules/channelmixerrgb/colorchecker");
2635 // early return
2636 if(g->n_colorcheckers < COLOR_CHECKER_USER_REF || selected_checker < COLOR_CHECKER_USER_REF) return;
2637
2638 // find all CGATS files if not already done
2639 if(!g->n_color)
2640 g->n_color = dt_colorchecker_find_color(&(g->colorcheckers_all_color));
2641 // no CGATS files found, early return
2642 if(g->n_color == 0) return;
2643
2644 // update the gui
2645 dt_bauhaus_combobox_clear(g->checkers_color_list);
2646 g_list_free(g->colorcheckers_color); // don't free_full because data pointers belong to g->colorcheckers_all_color
2647 g->colorcheckers_color = NULL;
2648
2649 const dt_colorchecker_label_t *checker_label = (const dt_colorchecker_label_t*)g_list_nth_data(g->colorcheckers, selected_checker);
2650 const int checker_patch_nb = checker_label ? checker_label->patch_nb : 0;
2651
2652 if(checker_patch_nb > 0)
2653 {
2654 for(GList *l = g_list_first(g->colorcheckers_all_color); l; l = g_list_next(l))
2655 {
2656 dt_colorchecker_label_t *cht_label = (dt_colorchecker_label_t *)l->data;
2657 // Filter out colorcheckers with different patch number.
2658 // A separate GList is required to associate the color checkers with their corresponding combobox item IDs.
2659 if(cht_label->patch_nb == checker_patch_nb)
2660 {
2661 dt_bauhaus_combobox_add(g->checkers_color_list, cht_label->name);
2662 g->colorcheckers_color = g_list_append(g->colorcheckers_color, cht_label);
2663 }
2664 }
2665 }
2666 dt_control_queue_redraw_widget(g->checkers_color_list);
2667}
2668
2670{
2672 if(IS_NULL_PTR(g)) return;
2673
2674 dt_colorchecker_label_list_cleanup(&(g->colorcheckers));
2675
2676 g->n_colorcheckers = 0;
2677 int pos = dt_colorchecker_find(&(g->colorcheckers));
2678 g->n_colorcheckers = pos;
2679
2680 // update the gui
2681 dt_bauhaus_combobox_clear(g->checkers_list);
2682 gboolean has_builtin_checker = FALSE;
2683 gboolean user_checker_separator_added = FALSE;
2684 for(GList *l = g_list_first(g->colorcheckers); l; l = g_list_next(l))
2685 {
2686 const dt_colorchecker_label_t *checker_data = (const dt_colorchecker_label_t*)l->data;
2687
2688 if(checker_data->type < COLOR_CHECKER_USER_REF)
2689 {
2690 has_builtin_checker = TRUE;
2691 }
2692 else if(has_builtin_checker && !user_checker_separator_added)
2693 {
2694 dt_bauhaus_combobox_add_separator(g->checkers_list);
2695 user_checker_separator_added = TRUE;
2696 }
2697
2698 dt_bauhaus_combobox_add(g->checkers_list, checker_data->name);
2699 }
2700}
2701
2702static void optimize_changed_callback(GtkWidget *widget, gpointer user_data)
2703{
2704 if(dt_gui_widgets_suppressed()) return;
2705 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2707
2708 const int i = dt_bauhaus_combobox_get(widget);
2709 dt_conf_set_int("darkroom/modules/channelmixerrgb/optimization", i);
2710
2712 g->optimization = i;
2714}
2715
2716static void checker_color_changed_callback(GtkWidget *widget, gpointer user_data)
2717{
2718 if(dt_gui_widgets_suppressed()) return;
2719 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2721
2722 const int selected_cmbbx_index = dt_bauhaus_combobox_get(widget);
2723 dt_conf_set_int("darkroom/modules/channelmixerrgb/colorchecker_color", selected_cmbbx_index);
2724
2725 const int n_chkr = dt_bauhaus_combobox_get(g->checkers_list);
2726
2727 const dt_colorchecker_label_t *color_label = (selected_cmbbx_index >= 0 && g->colorcheckers_color) ?
2728 (const dt_colorchecker_label_t*)g_list_nth_data(g->colorcheckers_color, selected_cmbbx_index) : NULL;
2729
2730 const char *color_path = color_label ? color_label->path : NULL;
2731
2732 dt_colorchecker_cleanup(g->checker);
2733 g->checker = dt_get_color_checker(n_chkr, &(g->colorcheckers), color_path);
2734
2735 dt_develop_t *dev = self->dev;
2736 const float wd = dev->roi.preview_width;
2737 const float ht = dev->roi.preview_height;
2738 if(wd == 0.f || ht == 0.f) return;
2739
2741 g->profile_ready = FALSE;
2742 init_bounding_box(g, wd, ht);
2744
2746}
2747
2748void checker_changed_callback(GtkWidget *widget, gpointer user_data)
2749{
2750 if(dt_gui_widgets_suppressed()) return;
2751 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2753
2754 const int checker_cmbbx_index = dt_bauhaus_combobox_get(widget);
2755 dt_conf_set_int("darkroom/modules/channelmixerrgb/colorchecker", checker_cmbbx_index);
2758 color_list_visibility(self, checker_cmbbx_index);
2760
2761 const int n_color = dt_bauhaus_combobox_get(g->checkers_color_list);
2762 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;
2763 const char *color_path = color_label ? color_label->path : NULL;
2764
2765 dt_colorchecker_cleanup(g->checker);
2766 g->checker = dt_get_color_checker(checker_cmbbx_index, &(g->colorcheckers), color_path);
2767
2768 dt_develop_t *dev = self->dev;
2769 const float wd = dev->roi.preview_width;
2770 const float ht = dev->roi.preview_height;
2771 if(wd == 0.f || ht == 0.f) return;
2772
2774 g->profile_ready = FALSE;
2775 init_bounding_box(g, wd, ht);
2777
2779}
2780
2781static void safety_changed_callback(GtkWidget *widget, gpointer user_data)
2782{
2783 if(dt_gui_widgets_suppressed()) return;
2784 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2786
2788 g->safety_margin = dt_bauhaus_slider_get(widget);
2790
2791 dt_conf_set_float("darkroom/modules/channelmixerrgb/safety", g->safety_margin);
2793}
2794
2795
2796static void start_profiling_callback(GtkWidget *togglebutton, dt_iop_module_t *self)
2797{
2798 if(dt_gui_widgets_suppressed()) return;
2800 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
2801
2802 dt_develop_t *dev = self->dev;
2803 const float wd = dev->roi.preview_width;
2804 const float ht = dev->roi.preview_height;
2805 if(wd == 0.f || ht == 0.f) return;
2806
2808 g->is_profiling_started = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->cs.toggle));
2809
2810 // init bounding box
2812 init_bounding_box(g, wd, ht);
2814
2816}
2817
2818static void run_profile_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2819{
2820 if(dt_gui_widgets_suppressed()) return;
2821 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2823
2825 g->run_profile = TRUE;
2829}
2830
2831static void run_validation_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2832{
2833 if(dt_gui_widgets_suppressed()) return;
2834 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2836
2838 g->run_validation = TRUE;
2842}
2843
2844static void commit_profile_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2845{
2846 if(dt_gui_widgets_suppressed()) return;
2847 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2850
2851 if(!g->profile_ready) return;
2852
2854
2855 p->x = g->xy[0];
2856 p->y = g->xy[1];
2857 p->illuminant = DT_ILLUMINANT_CUSTOM;
2858 check_if_close_to_daylight(p->x, p->y, &p->temperature, NULL, NULL);
2859
2860 p->red[0] = g->mix[0][0];
2861 p->red[1] = g->mix[0][1];
2862 p->red[2] = g->mix[0][2];
2863
2864 p->green[0] = g->mix[1][0];
2865 p->green[1] = g->mix[1][1];
2866 p->green[2] = g->mix[1][2];
2867
2868 p->blue[0] = g->mix[2][0];
2869 p->blue[1] = g->mix[2][1];
2870 p->blue[2] = g->mix[2][2];
2871
2873
2875 dt_bauhaus_combobox_set(g->illuminant, p->illuminant);
2876 dt_bauhaus_slider_set(g->temperature, p->temperature);
2877
2878 dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
2879 dt_aligned_pixel_t Lch = { 0 };
2880 dt_xyY_to_Lch(xyY, Lch);
2881 dt_bauhaus_slider_set(g->illum_x, Lch[2] / M_PI * 180.f);
2882 dt_bauhaus_slider_set(g->illum_y, Lch[1]);
2883
2884 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
2885 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
2886 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
2887
2888 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
2889 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
2890 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
2891
2892 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
2893 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
2894 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
2895
2897
2898 gui_changed(self, NULL, NULL);
2899
2900 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=commit_profile\n");
2902}
2903
2904static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
2905{
2906 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2909
2910 if(IS_NULL_PTR(g)) return;
2911 if(p->illuminant != DT_ILLUMINANT_DETECT_EDGES && p->illuminant != DT_ILLUMINANT_DETECT_SURFACES)
2912 return;
2913
2915 p->x = g->XYZ[0];
2916 p->y = g->XYZ[1];
2918
2919 check_if_close_to_daylight(p->x, p->y, &p->temperature, &p->illuminant, &p->adaptation);
2920
2922
2923 dt_bauhaus_slider_set(g->temperature, p->temperature);
2924 dt_bauhaus_combobox_set(g->illuminant, p->illuminant);
2925 dt_bauhaus_combobox_set(g->adaptation, p->adaptation);
2926
2927 const dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
2929 dt_xyY_to_Lch(xyY, Lch);
2930 dt_bauhaus_slider_set(g->illum_x, Lch[2] / M_PI * 180.f);
2931 dt_bauhaus_slider_set(g->illum_y, Lch[1]);
2932
2933 update_illuminants(self);
2934 update_approx_cct(self);
2937
2939
2940 gui_changed(self, NULL, NULL);
2941
2942 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=ui_pipe_finished\n");
2944}
2945
2946static void _preview_pipe_finished_callback(gpointer instance, gpointer user_data)
2947{
2948 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2950
2952 gtk_label_set_markup(GTK_LABEL(g->label_delta_E), g->delta_E_label_text);
2954}
2955
2958{
2962
2963 d->version = p->version;
2964
2965 float norm_R = 1.0f;
2966 if(p->normalize_R) norm_R = p->red[0] + p->red[1] + p->red[2];
2967
2968 float norm_G = 1.0f;
2969 if(p->normalize_G) norm_G = p->green[0] + p->green[1] + p->green[2];
2970
2971 float norm_B = 1.0f;
2972 if(p->normalize_B) norm_B = p->blue[0] + p->blue[1] + p->blue[2];
2973
2974 float norm_sat = 0.0f;
2975 if(p->normalize_sat) norm_sat = (p->saturation[0] + p->saturation[1] + p->saturation[2]) / 3.f;
2976
2977 float norm_light = 0.0f;
2978 if(p->normalize_light) norm_light = (p->lightness[0] + p->lightness[1] + p->lightness[2]) / 3.f;
2979
2980 float norm_grey = p->grey[0] + p->grey[1] + p->grey[2];
2981 d->apply_grey = (p->grey[0] != 0.f) || (p->grey[1] != 0.f) || (p->grey[2] != 0.f);
2982 if(!p->normalize_grey || norm_grey == 0.f) norm_grey = 1.f;
2983
2984 for(int i = 0; i < 3; i++)
2985 {
2986 d->MIX[0][i] = p->red[i] / norm_R;
2987 d->MIX[1][i] = p->green[i] / norm_G;
2988 d->MIX[2][i] = p->blue[i] / norm_B;
2989 d->saturation[i] = -p->saturation[i] + norm_sat;
2990 d->lightness[i] = p->lightness[i] - norm_light;
2991 d->grey[i] = p->grey[i] / norm_grey; // = NaN if (norm_grey == 0.f) but we don't care since (d->apply_grey == FALSE)
2992 }
2993
2994 if(p->version == CHANNELMIXERRGB_V_1)
2995 {
2996 // for the v1 saturation algo, the effect of R and B coeffs is reversed
2997 d->saturation[0] = -p->saturation[2] + norm_sat;
2998 d->saturation[2] = -p->saturation[0] + norm_sat;
2999 }
3000
3001 // just in case compiler feels clever and uses SSE 4x1 dot product
3002 d->saturation[CHANNEL_SIZE - 1] = 0.0f;
3003 d->lightness[CHANNEL_SIZE - 1] = 0.0f;
3004 d->grey[CHANNEL_SIZE - 1] = 0.0f;
3005
3006 d->adaptation = p->adaptation;
3007 d->clip = p->clip;
3008 d->gamut = (p->gamut == 0.f) ? p->gamut : 1.f / p->gamut;
3009
3010 // find x y coordinates of illuminant for CIE 1931 2° observer
3011 float x = p->x;
3012 float y = p->y;
3013 dt_aligned_pixel_t custom_wb;
3014 // Pipeline thread: source temperature's WB from the per-piece descriptor propagated in pipe order
3015 // (temperature writes piece->dsc_out.temperature.coeffs upstream), NOT from dev->proxy.
3016 _custom_wb_from_coeffs(self, piece->dsc_in.temperature.coeffs, custom_wb);
3017 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led);
3018
3019 // if illuminant is set as camera, x and y are set on-the-fly at commit time, so we need to set adaptation too
3020 if(p->illuminant == DT_ILLUMINANT_CAMERA)
3021 check_if_close_to_daylight(x, y, NULL, NULL, &(d->adaptation));
3022
3023 d->illuminant_type = p->illuminant;
3024
3025 // Convert illuminant from xyY to XYZ
3028
3029 // Convert illuminant from XYZ to Bradford modified LMS
3030 dt_store_simd_aligned(d->illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), d->adaptation));
3031 d->illuminant[3] = 0.f;
3032
3033 dt_iop_fmt_log(self, "commit: class=%s matrix_supported=%d illuminant=%d adaptation=%d custom_wb=[%.4f %.4f %.4f %.4f] xy=[%.4f %.4f]",
3036 p->illuminant, d->adaptation, custom_wb[0], custom_wb[1], custom_wb[2], custom_wb[3], x, y);
3037
3038 //fprintf(stdout, "illuminant: %i\n", p->illuminant);
3039 //fprintf(stdout, "x: %f, y: %f\n", x, y);
3040 //fprintf(stdout, "X: %f - Y: %f - Z: %f\n", XYZ[0], XYZ[1], XYZ[2]);
3041 //fprintf(stdout, "L: %f - M: %f - S: %f\n", d->illuminant[0], d->illuminant[1], d->illuminant[2]);
3042
3043 // blue compensation for Bradford transform = (test illuminant blue / reference illuminant blue)^0.0834
3044 // reference illuminant is hard-set D50 for darktable's pipeline
3045 // test illuminant is user params
3046 d->p = powf(0.818155f / d->illuminant[2], 0.0834f);
3047
3048 // Disable OpenCL path if we are in any kind of diagnose mode (only C path has diagnostics)
3049 if(self->dev->gui_attached && g)
3050 {
3051 if( (g->run_profile && pipe->type == DT_DEV_PIXELPIPE_PREVIEW) || // color checker extraction mode
3052 (g->run_validation && pipe->type == DT_DEV_PIXELPIPE_PREVIEW) || // delta E validation
3053 ( (d->illuminant_type == DT_ILLUMINANT_DETECT_EDGES ||
3054 d->illuminant_type == DT_ILLUMINANT_DETECT_SURFACES ) && // WB extraction mode
3055 pipe->type == DT_DEV_PIXELPIPE_FULL ) )
3056 {
3057 piece->process_cl_ready = 0;
3058 }
3059 }
3060}
3061
3062
3064{
3067
3068 if(p->adaptation == DT_ADAPTATION_RGB || p->adaptation == DT_ADAPTATION_LAST)
3069 {
3070 // user disabled CAT at all, hide everything and exit
3071 gtk_widget_set_visible(g->illuminant, FALSE);
3072 gtk_widget_set_visible(g->illum_color, FALSE);
3073 gtk_widget_set_visible(g->approx_cct, FALSE);
3074 gtk_widget_set_visible(g->color_picker, FALSE);
3075 gtk_widget_set_visible(g->temperature, FALSE);
3076 gtk_widget_set_visible(g->illum_fluo, FALSE);
3077 gtk_widget_set_visible(g->illum_led, FALSE);
3078 gtk_widget_set_visible(g->illum_x, FALSE);
3079 gtk_widget_set_visible(g->illum_y, FALSE);
3080 return;
3081 }
3082 else
3083 {
3084 // set everything visible again and carry on
3085 gtk_widget_set_visible(g->illuminant, TRUE);
3086 gtk_widget_set_visible(g->illum_color, TRUE);
3087 gtk_widget_set_visible(g->approx_cct, TRUE);
3088 gtk_widget_set_visible(g->color_picker, TRUE);
3089 gtk_widget_set_visible(g->temperature, TRUE);
3090 gtk_widget_set_visible(g->illum_fluo, TRUE);
3091 gtk_widget_set_visible(g->illum_led, TRUE);
3092 gtk_widget_set_visible(g->illum_x, TRUE);
3093 }
3094
3095 // Display only the relevant sliders
3096 switch(p->illuminant)
3097 {
3098 case DT_ILLUMINANT_PIPE:
3099 case DT_ILLUMINANT_A:
3100 case DT_ILLUMINANT_E:
3101 {
3102 gtk_widget_set_visible(g->adaptation, TRUE);
3103 gtk_widget_set_visible(g->temperature, FALSE);
3104 gtk_widget_set_visible(g->illum_fluo, FALSE);
3105 gtk_widget_set_visible(g->illum_led, FALSE);
3106 gtk_widget_set_visible(g->illum_x, FALSE);
3107 gtk_widget_set_visible(g->illum_y, FALSE);
3108 break;
3109 }
3110 case DT_ILLUMINANT_D:
3111 case DT_ILLUMINANT_BB:
3112 {
3113 gtk_widget_set_visible(g->adaptation, TRUE);
3114 gtk_widget_set_visible(g->temperature, TRUE);
3115 gtk_widget_set_visible(g->illum_fluo, FALSE);
3116 gtk_widget_set_visible(g->illum_led, FALSE);
3117 gtk_widget_set_visible(g->illum_x, FALSE);
3118 gtk_widget_set_visible(g->illum_y, FALSE);
3119 break;
3120 }
3121 case DT_ILLUMINANT_F:
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, TRUE);
3126 gtk_widget_set_visible(g->illum_led, FALSE);
3127 gtk_widget_set_visible(g->illum_x, FALSE);
3128 gtk_widget_set_visible(g->illum_y, FALSE);
3129 break;
3130 }
3131 case DT_ILLUMINANT_LED:
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, TRUE);
3137 gtk_widget_set_visible(g->illum_x, FALSE);
3138 gtk_widget_set_visible(g->illum_y, FALSE);
3139 break;
3140 }
3142 {
3143 gtk_widget_set_visible(g->adaptation, TRUE);
3144 gtk_widget_set_visible(g->temperature, FALSE);
3145 gtk_widget_set_visible(g->illum_fluo, FALSE);
3146 gtk_widget_set_visible(g->illum_led, FALSE);
3147 gtk_widget_set_visible(g->illum_x, TRUE);
3148 gtk_widget_set_visible(g->illum_y, TRUE);
3149 break;
3150 }
3152 {
3153 gtk_widget_set_visible(g->adaptation, TRUE);
3154 gtk_widget_set_visible(g->temperature, FALSE);
3155 gtk_widget_set_visible(g->illum_fluo, FALSE);
3156 gtk_widget_set_visible(g->illum_led, FALSE);
3157 gtk_widget_set_visible(g->illum_x, FALSE);
3158 gtk_widget_set_visible(g->illum_y, FALSE);
3159 break;
3160 }
3163 {
3164 gtk_widget_set_visible(g->adaptation, FALSE);
3165 gtk_widget_set_visible(g->temperature, FALSE);
3166 gtk_widget_set_visible(g->illum_fluo, FALSE);
3167 gtk_widget_set_visible(g->illum_led, FALSE);
3168 gtk_widget_set_visible(g->illum_x, FALSE);
3169 gtk_widget_set_visible(g->illum_y, FALSE);
3170 break;
3171 }
3172 case DT_ILLUMINANT_LAST:
3173 default:
3174 {
3175 break;
3176 }
3177 }
3178}
3179
3208{
3209 // update the fill background color of x, y sliders
3212
3213 if(IS_NULL_PTR(g) || IS_NULL_PTR(p) || IS_NULL_PTR(g->illum_x) || IS_NULL_PTR(g->illum_y)) return;
3214
3215 // Varies x in range around current y param
3216 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
3217 {
3218 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
3219 const float x = stop * ILLUM_X_MAX;
3220 dt_aligned_pixel_t RGB = { 0 };
3221
3222 dt_aligned_pixel_t Lch = { 100.f, 50.f, x / 180.f * M_PI };
3223 dt_aligned_pixel_t xyY = { 0 };
3224 dt_Lch_to_xyY(Lch, xyY);
3226 dt_bauhaus_slider_set_stop(g->illum_x, stop, RGB[0], RGB[1], RGB[2]);
3227
3228 const float y = stop * ILLUM_Y_MAX / 2.0f;
3229
3230 // Find current hue
3231 const dt_aligned_pixel_t xyY2 = { p->x, p->y, 1.f };
3232 dt_xyY_to_Lch(xyY2, Lch);
3233
3234 // Replace chroma by current step
3235 Lch[0] = 75.f;
3236 Lch[1] = y;
3237
3238 // Go back to xyY
3239 dt_Lch_to_xyY(Lch, xyY);
3241 dt_bauhaus_slider_set_stop(g->illum_y, stop, RGB[0], RGB[1], RGB[2]);
3242 }
3243
3244 gtk_widget_queue_draw(g->illum_x);
3245 gtk_widget_queue_draw(g->illum_y);
3246}
3247
3248static void paint_hue(dt_iop_module_t *self)
3249{
3250 // update the fill background color of LCh sliders
3252
3253 const float hue = dt_bauhaus_slider_get(g->hue_spot);
3254
3255 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
3256 {
3257 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
3258 dt_aligned_pixel_t RGB = { 0 }, Lab = { 0 }, XYZ = { 0 };
3259
3260 const dt_aligned_pixel_t Lch_hue = { 67.f, 96.f, (stop * HUE_MAX) / 360.f};
3261
3262 dt_LCH_2_Lab(Lch_hue, Lab);
3265
3266 dt_bauhaus_slider_set_stop(g->hue_spot, stop, RGB[0], RGB[1], RGB[2]);
3267
3268 const dt_aligned_pixel_t Lch_lightness = { stop * LIGHTNESS_MAX, 0.f, 0. };
3269
3270 dt_LCH_2_Lab(Lch_lightness, Lab);
3273
3274 dt_bauhaus_slider_set_stop(g->lightness_spot, stop, RGB[0], RGB[1], RGB[2]);
3275
3276 const dt_aligned_pixel_t Lch_chroma = { 50., stop * CHROMA_MAX, hue / 360.f };
3277
3278 dt_LCH_2_Lab(Lch_chroma, Lab);
3281
3282 dt_bauhaus_slider_set_stop(g->chroma_spot, stop, RGB[0], RGB[1], RGB[2]);
3283 }
3284
3285 gtk_widget_queue_draw(g->hue_spot);
3286 gtk_widget_queue_draw(g->lightness_spot);
3287 gtk_widget_queue_draw(g->chroma_spot);
3288 gtk_widget_queue_draw(g->target_spot);
3289}
3290
3291
3300{
3303 GtkWidget *const widgets[9]
3304 = { g->primaries_achromatic_hue, g->primaries_achromatic_purity, g->primaries_red_hue,
3305 g->primaries_red_purity, g->primaries_green_hue, g->primaries_green_purity,
3306 g->primaries_blue_hue, g->primaries_blue_purity, g->primaries_gain };
3310 const float rows[3][3] = { { p->red[0], p->red[1], p->red[2] },
3311 { p->green[0], p->green[1], p->green[2] },
3312 { p->blue[0], p->blue[1], p->blue[2] } };
3313 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
3314 float M[3][3] = { { 0.f } };
3315 float roundtrip[3][3] = { { 0.f } };
3316
3318 if(!dt_iop_channelmixer_shared_primaries_from_matrix(basis, M, &primaries)) return FALSE;
3319 if(!dt_iop_channelmixer_shared_primaries_to_matrix(basis, &primaries, roundtrip)) return FALSE;
3320
3321 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
3322 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
3323
3327 return isfinite(roundtrip_error) && roundtrip_error <= DT_CHANNELMIXERRGB_SIMPLE_EPS;
3328}
3329
3331{
3335 const dt_iop_order_iccprofile_info_t *const display_profile
3337 GtkWidget *const widgets[9]
3338 = { g->primaries_achromatic_hue, g->primaries_achromatic_purity, g->primaries_red_hue,
3339 g->primaries_red_purity, g->primaries_green_hue, g->primaries_green_purity,
3340 g->primaries_blue_hue, g->primaries_blue_purity, g->primaries_gain };
3344
3346 dt_iop_channelmixer_shared_paint_primaries_sliders(p->adaptation, work_profile, display_profile, basis, &primaries,
3347 widgets);
3348}
3349
3351 const struct dt_iop_order_iccprofile_info_t *const work_profile,
3352 const struct dt_iop_order_iccprofile_info_t *const display_profile,
3354{
3355 dt_aligned_pixel_t work_rgb = { LMS[0], LMS[1], LMS[2], 0.f };
3356
3357 if(p->adaptation != DT_ADAPTATION_RGB)
3358 {
3359 convert_any_LMS_to_RGB(LMS, work_rgb, p->adaptation);
3360 }
3361 else
3362 {
3363 for(size_t c = 0; c < 3; c++) work_rgb[c] = LMS[c];
3364 }
3365
3366 dt_iop_channelmixer_shared_work_rgb_to_display(work_rgb, work_profile, display_profile, RGB);
3367}
3368
3370 const struct dt_iop_order_iccprofile_info_t *const work_profile,
3371 const struct dt_iop_order_iccprofile_info_t *const display_profile,
3372 GtkWidget *w, float stop, float c, float r, float g, float b)
3373{
3374 const dt_aligned_pixel_t LMS = { 0.5f * (c * r + 1 - r),
3375 0.5f * (c * g + 1 - g),
3376 0.5f * (c * b + 1 - b)};
3377 dt_aligned_pixel_t RGB_t = { 0.5f };
3378 _convert_GUI_colors(p, work_profile, display_profile, LMS, RGB_t);
3379 dt_bauhaus_slider_set_stop(w, stop, RGB_t[0], RGB_t[1], RGB_t[2]);
3380}
3381
3382static void _update_RGB_colors(dt_iop_module_t *self, float r, float g, float b, gboolean normalize, float *a,
3383 GtkWidget *w_r, GtkWidget *w_g, GtkWidget *w_b)
3384{
3385 // update the fill background color of x, y sliders
3387 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(self->dev->pipe);
3388 const struct dt_iop_order_iccprofile_info_t *const display_profile = dt_ioppr_get_pipe_output_profile_info(self->dev->pipe);
3389
3390 // scale params if needed
3391 dt_aligned_pixel_t RGB = { a[0], a[1], a[2] };
3392
3393 if(normalize)
3394 {
3395 const float sum = RGB[0] + RGB[1] + RGB[2];
3396 if(sum != 0.f) for(int c = 0; c < 3; c++) RGB[c] /= sum;
3397 }
3398
3399 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
3400 {
3401 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
3402 // Use the hard bounds of the sliders; drawing will take into account the possible soft rescaling
3403 const float range_x = COLOR_MAX - COLOR_MIN;
3404
3405 const float x = COLOR_MIN + stop * range_x;
3406
3407 _update_RGB_slider_stop(p, work_profile, display_profile, w_r, stop, x + RGB[1] + RGB[2], r, g, b);
3408 _update_RGB_slider_stop(p, work_profile, display_profile, w_g, stop, RGB[0] + x + RGB[2], r, g, b);
3409 _update_RGB_slider_stop(p, work_profile, display_profile, w_b, stop, RGB[0] + RGB[1] + x , r, g, b);
3410 }
3411
3412 gtk_widget_queue_draw(w_r);
3413 gtk_widget_queue_draw(w_b);
3414 gtk_widget_queue_draw(w_g);
3415}
3416
3425{
3428 GtkWidget *const widgets[6]
3429 = { g->simple_theta, g->simple_psi, g->simple_stretch_1, g->simple_stretch_2, g->simple_coupling_1,
3430 g->simple_coupling_2 };
3431 const float rows[3][3] = { { p->red[0], p->red[1], p->red[2] },
3432 { p->green[0], p->green[1], p->green[2] },
3433 { p->blue[0], p->blue[1], p->blue[2] } };
3434 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
3435 float M[3][3] = { { 0.f } };
3436 float roundtrip[3][3] = { { 0.f } };
3438
3439 if(!IS_NULL_PTR(error)) *error = INFINITY;
3442
3445
3446 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
3447 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
3448
3452
3453 return isfinite(roundtrip_error) && roundtrip_error <= DT_CHANNELMIXERRGB_SIMPLE_EPS;
3454}
3455
3467{
3471 const dt_iop_order_iccprofile_info_t *const display_profile
3473 GtkWidget *const widgets[6]
3474 = { g->simple_theta, g->simple_psi, g->simple_stretch_1, g->simple_stretch_2, g->simple_coupling_1,
3475 g->simple_coupling_2 };
3477
3479 dt_iop_channelmixer_shared_paint_simple_sliders(p->adaptation, work_profile, display_profile, &simple, widgets);
3480}
3481
3482
3484{
3486 if(IS_NULL_PTR(g)) return;
3487
3488 if(!IS_NULL_PTR(g->illum_color))
3489 gtk_widget_queue_draw(g->illum_color);
3490
3491 update_xy_color(self);
3492}
3493
3494static gboolean illuminant_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3495{
3496 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3498
3499 // Init
3500 GtkAllocation allocation;
3501 gtk_widget_get_allocation(widget, &allocation);
3502 int width = allocation.width, height = allocation.height;
3503 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3504 cairo_t *cr = cairo_create(cst);
3505
3506 // Margins
3507 const float margin = 2. * DT_PIXEL_APPLY_DPI(1.5);
3509 height -= 2 * margin;
3510
3511 // Paint illuminant color - we need to recompute it in full in case camera RAW is chosen
3512 float x = p->x;
3513 float y = p->y;
3514 dt_aligned_pixel_t RGB = { 0 };
3515 dt_aligned_pixel_t custom_wb;
3516 get_white_balance_coeff(self, custom_wb);
3517 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb,
3518 &x, &y, p->temperature, p->illum_fluo, p->illum_led);
3520 cairo_set_source_rgb(cr, RGB[0], RGB[1], RGB[2]);
3521 cairo_rectangle(cr, INNER_PADDING, margin, width, height);
3522 cairo_fill(cr);
3523
3524 // Clean
3525 cairo_stroke(cr);
3526 cairo_destroy(cr);
3527 cairo_set_source_surface(crf, cst, 0, 0);
3528 cairo_paint(crf);
3529 cairo_surface_destroy(cst);
3530 return TRUE;
3531}
3532
3533static gboolean target_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3534{
3535 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3537
3538 // Init
3539 GtkAllocation allocation;
3540 gtk_widget_get_allocation(widget, &allocation);
3541 int width = allocation.width, height = allocation.height;
3542 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3543 cairo_t *cr = cairo_create(cst);
3544
3545 // Margins
3546 const float margin = 2. * DT_PIXEL_APPLY_DPI(1.5);
3548 height -= 2 * margin;
3549
3550 // Paint target color
3551 dt_aligned_pixel_t RGB = { 0 };
3552 dt_aligned_pixel_t Lch = { 0 };
3553 dt_aligned_pixel_t Lab = { 0 };
3554 dt_aligned_pixel_t XYZ = { 0 };
3555 Lch[0] = dt_bauhaus_slider_get(g->lightness_spot);
3556 Lch[1] = dt_bauhaus_slider_get(g->chroma_spot);
3557 Lch[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f;
3558 dt_LCH_2_Lab(Lch, Lab);
3561
3562 cairo_set_source_rgb(cr, RGB[0], RGB[1], RGB[2]);
3563 cairo_rectangle(cr, INNER_PADDING, margin, width, height);
3564 cairo_fill(cr);
3565
3566 // Clean
3567 cairo_stroke(cr);
3568 cairo_destroy(cr);
3569 cairo_set_source_surface(crf, cst, 0, 0);
3570 cairo_paint(crf);
3571 cairo_surface_destroy(cst);
3572 return TRUE;
3573}
3574
3575static gboolean origin_color_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3576{
3577 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3579
3580 // Init
3581 GtkAllocation allocation;
3582 gtk_widget_get_allocation(widget, &allocation);
3583 int width = allocation.width, height = allocation.height;
3584 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3585 cairo_t *cr = cairo_create(cst);
3586
3587 // Margins
3588 const float margin = 2. * DT_PIXEL_APPLY_DPI(1.5);
3590 height -= 2 * margin;
3591
3592 cairo_set_source_rgb(cr, g->spot_RGB[0], g->spot_RGB[1], g->spot_RGB[2]);
3593 cairo_rectangle(cr, INNER_PADDING, margin, width, height);
3594 cairo_fill(cr);
3595
3596 // Clean
3597 cairo_stroke(cr);
3598 cairo_destroy(cr);
3599 cairo_set_source_surface(crf, cst, 0, 0);
3600 cairo_paint(crf);
3601 cairo_surface_destroy(cst);
3602 return TRUE;
3603}
3604
3606{
3609
3610 float x = p->x;
3611 float y = p->y;
3612 dt_aligned_pixel_t custom_wb;
3613 get_white_balance_coeff(self, custom_wb);
3614 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led);
3615
3616 dt_illuminant_t test_illuminant;
3617 float t = 5000.f;
3618 check_if_close_to_daylight(x, y, &t, &test_illuminant, NULL);
3619
3620 gchar *str;
3621 if(t > 1667.f && t < 25000.f)
3622 {
3623 if(test_illuminant == DT_ILLUMINANT_D)
3624 {
3625 str = g_strdup_printf(_("CCT: %.0f K (daylight)"), t);
3626 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3627 _("approximated correlated color temperature.\n"
3628 "this illuminant can be accurately modeled by a daylight spectrum,\n"
3629 "so its temperature is relevant and meaningful with a D illuminant."));
3630 }
3631 else if(test_illuminant == DT_ILLUMINANT_BB)
3632 {
3633 str = g_strdup_printf(_("CCT: %.0f K (black body)"), t);
3634 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3635 _("approximated correlated color temperature.\n"
3636 "this illuminant can be accurately modeled by a black body spectrum,\n"
3637 "so its temperature is relevant and meaningful with a Planckian illuminant."));
3638 }
3639 else
3640 {
3641 str = g_strdup_printf(_("CCT: %.0f K (invalid)"), t);
3642 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3643 _("approximated correlated color temperature.\n"
3644 "this illuminant cannot be accurately modeled by a daylight or black body spectrum,\n"
3645 "so its temperature is not relevant and meaningful and you need to use a custom illuminant."));
3646 }
3647 }
3648 else
3649 {
3650 str = g_strdup_printf(_("CCT: undefined"));
3651 gtk_widget_set_tooltip_text(GTK_WIDGET(g->approx_cct),
3652 _("the approximated correlated color temperature\n"
3653 "cannot be computed at all so you need to use a custom illuminant."));
3654 }
3655 gtk_label_set_text(GTK_LABEL(g->approx_cct), str);
3656 dt_free(str);
3657}
3658
3659
3660static void illum_xy_callback(GtkWidget *slider, gpointer user_data)
3661{
3662 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3663 if(dt_gui_widgets_suppressed()) return;
3666
3667 dt_aligned_pixel_t Lch = { 0 };
3668 Lch[0] = 100.f;
3669 Lch[2] = dt_bauhaus_slider_get(g->illum_x) / 180. * M_PI;
3670 Lch[1] = dt_bauhaus_slider_get(g->illum_y);
3671
3672 dt_aligned_pixel_t xyY = { 0 };
3673 dt_Lch_to_xyY(Lch, xyY);
3674 p->x = xyY[0];
3675 p->y = xyY[1];
3676
3677 float t = xy_to_CCT(p->x, p->y);
3678 // xy_to_CCT is valid only above 3000 K
3679 if(t < 3000.f) t = CCT_reverse_lookup(p->x, p->y);
3680 p->temperature = t;
3681
3683 dt_bauhaus_slider_set(g->temperature, p->temperature);
3684 update_approx_cct(self);
3688
3689 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=illum_xy_callback slider=%p\n",
3690 (void *)slider);
3692}
3693
3699
3701{
3702 self->dev->proxy.chroma_adaptation = NULL;
3703 dt_free_align(piece->data);
3704 piece->data = NULL;
3705}
3706
3708{
3710 g->is_profiling_started = FALSE;
3712 gui_changed(self, NULL, NULL);
3713}
3714
3715void gui_update(struct dt_iop_module_t *self)
3716{
3717 dt_iop_module_t *module = (dt_iop_module_t *)self;
3720 float simple_error = INFINITY;
3721 float primaries_error = INFINITY;
3722
3724
3725 /* Restoring GUI state from params must not re-enter widget callbacks.
3726 Channelmixer's spot/color widgets can otherwise rerun auto-picking while
3727 a history refresh is already in flight, which feeds TOP_CHANGED/full-pipe
3728 loops when the picker is active. */
3730
3731 // always reset the mode the correct
3733
3734 // get the saved params
3736
3737 gboolean use_mixing = TRUE;
3738 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/use_mixing"))
3739 use_mixing = dt_conf_get_bool("darkroom/modules/channelmixerrgb/use_mixing");
3740 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->use_mixing), use_mixing);
3741
3742 float lightness = 50.f;
3743 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/lightness"))
3744 lightness = dt_conf_get_float("darkroom/modules/channelmixerrgb/lightness");
3745 dt_bauhaus_slider_set(g->lightness_spot, lightness);
3746
3747 float hue = 0.f;
3748 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/hue"))
3749 hue = dt_conf_get_float("darkroom/modules/channelmixerrgb/hue");
3750 dt_bauhaus_slider_set(g->hue_spot, hue);
3751
3752 float chroma = 0.f;
3753 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/chroma"))
3754 chroma = dt_conf_get_float("darkroom/modules/channelmixerrgb/chroma");
3755 dt_bauhaus_slider_set(g->chroma_spot, chroma);
3756
3758
3759 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->clip), p->clip);
3760 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), p->normalize_R);
3761 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), p->normalize_G);
3762 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), p->normalize_B);
3763
3764 const gboolean simple_ok = _channelmixerrgb_sync_simple_from_params(self, &simple_error);
3765 const gboolean primaries_ok = _channelmixerrgb_sync_primaries_from_params(self, &primaries_error);
3766 const dt_iop_channelmixer_rgb_mixer_mode_t requested_mode
3771 = requested_mode == DT_CHANNELMIXERRGB_MIXER_SIMPLE && simple_ok
3773 : requested_mode == DT_CHANNELMIXERRGB_MIXER_PRIMARIES && primaries_ok
3776 dt_bauhaus_combobox_set(g->mixer_mode, mixer_mode);
3778
3779 if(p->version != CHANNELMIXERRGB_V_3)
3780 dt_bauhaus_combobox_set(g->saturation_version, p->version);
3781 else
3782 gtk_widget_hide(GTK_WIDGET(g->saturation_version));
3783
3784 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_sat), p->normalize_sat);
3785 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_light), p->normalize_light);
3786 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_grey), p->normalize_grey);
3787
3789
3792
3793 const int selected_checker = dt_conf_get_int("darkroom/modules/channelmixerrgb/colorchecker");
3794 dt_bauhaus_combobox_set(g->checkers_list, selected_checker);
3795
3796 const int selected_color = dt_conf_get_int("darkroom/modules/channelmixerrgb/colorchecker_color");
3797 dt_bauhaus_combobox_set(g->checkers_color_list, selected_color);
3798
3799 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;
3800 const char *color_path = label ? label->path : NULL;
3801
3802 dt_colorchecker_cleanup(g->checker);
3803 g->checker = dt_get_color_checker(selected_checker, &(g->colorcheckers), color_path);
3804
3805 color_list_visibility(self, selected_checker);
3806
3807 const int j = dt_conf_get_int("darkroom/modules/channelmixerrgb/optimization");
3808 dt_bauhaus_combobox_set(g->optimize, j);
3809 g->optimization = j;
3810
3811 g->safety_margin = dt_conf_get_float("darkroom/modules/channelmixerrgb/safety");
3812 dt_bauhaus_slider_set(g->safety, g->safety_margin);
3813
3815
3816 // always disable profiling mode by default
3817 g->is_profiling_started = FALSE;
3818
3821
3822 g->spot_RGB[0] = 0.f;
3823 g->spot_RGB[1] = 0.f;
3824 g->spot_RGB[2] = 0.f;
3825 g->spot_RGB[3] = 0.f;
3826
3827 // Widget defaults (reset values) and the image-dependent "as shot in camera" illuminant entry,
3828 // derived from the per-image defaults that reload_defaults() already computed into default_params.
3829 // This lives here, not in reload_defaults(), so it only touches widgets when they exist and on the
3830 // GUI thread (reload_defaults also runs on export/thumbnail devs that have no widgets).
3831 {
3833 const dt_image_t *img = &self->dev->image_storage;
3834 const dt_aligned_pixel_t xyY = { d->x, d->y, 1.f };
3835 dt_aligned_pixel_t Lch = { 0 };
3836 dt_xyY_to_Lch(xyY, Lch);
3837
3838 dt_bauhaus_slider_set_default(g->illum_x, Lch[2] / M_PI * 180.f);
3839 dt_bauhaus_slider_set_default(g->illum_y, Lch[1]);
3840 dt_bauhaus_slider_set_default(g->temperature, d->temperature);
3841 dt_bauhaus_combobox_set_default(g->illuminant, d->illuminant);
3842 dt_bauhaus_combobox_set_default(g->adaptation, d->adaptation);
3843 if(g->delta_E_label_text)
3844 {
3845 dt_free(g->delta_E_label_text);
3846 g->delta_E_label_text = NULL;
3847 }
3848
3850 {
3852 dt_bauhaus_combobox_add_full(g->illuminant, _("as shot in camera"), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT,
3853 GINT_TO_POINTER(DT_ILLUMINANT_CAMERA), NULL, TRUE);
3854 }
3855 else
3857 }
3858
3860
3861 gui_changed(self, NULL, NULL);
3862}
3863
3865{
3866 dt_iop_default_init(module);
3867
3869 d->red[0] = d->green[1] = d->blue[2] = 1.0;
3870 d->normalize_R = TRUE;
3871 d->normalize_G = TRUE;
3872 d->normalize_B = TRUE;
3873}
3874
3876{
3878
3879 // Our per-image defaults (illuminant chromaticity x/y, temperature) are derived from the white
3880 // balance temperature publishes into dev->proxy.wb_coeffs (see get_white_balance_coeff()).
3881 // temperature publishes the per-image DEFAULT WB from its own reload_defaults() (main thread, no
3882 // pipeline involved). For that seed to be visible here, temperature must run before us:
3883 // dt_dev_init_default_history() iterates modules in iop order, and temperature's iop_order is far
3884 // below ours, so this holds -- but it is a silent data dependency, so assert it. A future
3885 // iop-order change that put us first would otherwise regress our defaults on a brand-new history.
3886 // (The EFFECTIVE/history WB is published later via temperature's commit_proxy(); our defaults are
3887 // intentionally keyed on the metadata-default WB so a fresh init always matches a manual reset.)
3888 const dt_iop_module_t *const temp = dt_iop_get_module_from_list(module->dev->iop, "temperature");
3889 if(temp && temp->iop_order >= module->iop_order)
3890 fprintf(stderr, "[channelmixerrgb] BUG: temperature (iop_order %d) must run before channelmixerrgb "
3891 "(iop_order %d); white-balance-derived defaults will be wrong on a fresh history\n",
3892 temp->iop_order, module->iop_order);
3893 assert(!temp || temp->iop_order < module->iop_order);
3894
3895 d->normalize_R = TRUE;
3896 d->normalize_G = TRUE;
3897 d->normalize_B = TRUE;
3898
3899 d->x = module->get_f("x")->Float.Default;
3900 d->y = module->get_f("y")->Float.Default;
3901 d->temperature = module->get_f("temperature")->Float.Default;
3902 d->illuminant = module->get_f("illuminant")->Enum.Default;
3903 d->adaptation = module->get_f("adaptation")->Enum.Default;
3904
3905 // Note : this is not an user param anymore, and is set to modern by default
3906 // except for old histories using temperature without having an history entry for it.
3907 // see develop/develop.c/_dev_auto_apply_presets()
3908 const gboolean is_modern =
3909 dt_conf_is_equal("plugins/darkroom/chromatic-adaptation", "modern");
3910
3911 // note that if there is already an instance of this module with an
3912 // adaptation set we default to RGB (none) in this instance.
3913 // try to register the CAT here
3914 declare_cat_on_pipe(module, is_modern);
3915 const dt_image_t *img = &module->dev->image_storage;
3916
3917 // check if we could register
3918 gboolean CAT_already_applied =
3919 (!IS_NULL_PTR(module->dev->proxy.chroma_adaptation)) // CAT exists
3920 && (module->dev->proxy.chroma_adaptation != module) // and it is not us
3921 && (!dt_image_is_monochrome(img));
3922
3923 module->default_enabled = FALSE;
3924
3925 dt_aligned_pixel_t custom_wb = { 1.f, 1.f, 1.f, 1.f };
3926 if(!CAT_already_applied
3927 && is_modern
3928 && !get_white_balance_coeff(module, custom_wb)
3929 && !dt_image_is_monochrome(img))
3930 {
3931 // if workflow = modern and we find WB coeffs, take care of white balance here
3932 if(find_temperature_from_raw_coeffs(img, custom_wb, &(d->x), &(d->y)))
3933 d->illuminant = DT_ILLUMINANT_CAMERA;
3934
3935 check_if_close_to_daylight(d->x, d->y, &(d->temperature), &(d->illuminant), &(d->adaptation));
3936 module->workflow_enabled = dt_image_needs_rawprepare(img);
3937 }
3938 else
3939 {
3940 // otherwise, simple channel mixer
3941 d->illuminant = DT_ILLUMINANT_PIPE;
3942 d->adaptation = DT_ADAPTATION_RGB;
3943 }
3944
3945 // NOTE: widget state (slider/combo defaults, the image-dependent "as shot in camera" illuminant
3946 // entry) is intentionally NOT set here. reload_defaults() runs on export/thumbnail devs with no
3947 // widgets and off the GUI thread; touching bauhaus here re-entered callbacks against half-built
3948 // widgets and crashed. That work now lives in gui_update(), reading these same default_params.
3949}
3950
3951
3953{
3954 if(dt_gui_widgets_suppressed()) return;
3955
3957
3958 dt_aligned_pixel_t Lch_target = { 0.f };
3959
3961 Lch_target[0] = dt_bauhaus_slider_get(g->lightness_spot);
3962 Lch_target[1] = dt_bauhaus_slider_get(g->chroma_spot);
3963 Lch_target[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f;
3964 const gboolean use_mixing = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->use_mixing));
3966
3967 // Save the color on change
3968 dt_conf_set_float("darkroom/modules/channelmixerrgb/lightness", Lch_target[0]);
3969 dt_conf_set_float("darkroom/modules/channelmixerrgb/chroma", Lch_target[1]);
3970 dt_conf_set_float("darkroom/modules/channelmixerrgb/hue", Lch_target[2] * 360.f);
3971 dt_conf_set_bool("darkroom/modules/channelmixerrgb/use_mixing", use_mixing);
3972
3974 paint_hue(self);
3976
3977 // Re-run auto illuminant only for the module that currently owns the active picker.
3978 const dt_spot_mode_t mode = dt_bauhaus_combobox_get(g->spot_mode);
3979 const gboolean picker_active = dt_iop_color_picker_is_active_module(self);
3980 if(mode == DT_SPOT_MODE_CORRECT && picker_active)
3981 _auto_set_illuminant(self, self->dev->pipe);
3982 // else : just record new values and do nothing
3983}
3984
3987{
3988 gtk_stack_set_visible_child_name(GTK_STACK(g->mixer_stack),
3989 mode == DT_CHANNELMIXERRGB_MIXER_SIMPLE ? "simple"
3990 : mode == DT_CHANNELMIXERRGB_MIXER_PRIMARIES ? "primaries"
3991 : "complete");
3992}
3993
3994static void _channelmixerrgb_mixer_mode_callback(GtkWidget *combo, gpointer user_data)
3995{
3996 if(dt_gui_widgets_suppressed()) return;
3997
3998 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4002
4004
4006 {
4008 return;
4009 }
4010
4012 {
4013 float error = INFINITY;
4015 {
4016 dt_control_log(_("simple mixer mode requires all three output rows to be normalized with non-zero sums."));
4022 return;
4023 }
4024
4026 gui_changed(self, NULL, NULL);
4027 return;
4028 }
4029
4031 {
4032 const float rows[3][3] = { { p->red[0], p->red[1], p->red[2] },
4033 { p->green[0], p->green[1], p->green[2] },
4034 { p->blue[0], p->blue[1], p->blue[2] } };
4035 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
4036 float M[3][3] = { { 0.f } };
4037 float error = INFINITY;
4040 {
4041 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
4047 return;
4048 }
4049
4050 gboolean changed = p->normalize_R || p->normalize_G || p->normalize_B;
4051 for(int col = 0; col < 3; col++)
4052 {
4053 changed = changed || p->red[col] != M[0][col] || p->green[col] != M[1][col] || p->blue[col] != M[2][col];
4054 p->red[col] = M[0][col];
4055 p->green[col] = M[1][col];
4056 p->blue[col] = M[2][col];
4057 }
4058
4059 p->normalize_R = FALSE;
4060 p->normalize_G = FALSE;
4061 p->normalize_B = FALSE;
4062
4064 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), FALSE);
4065 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), FALSE);
4066 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), FALSE);
4067 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
4068 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
4069 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
4070 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
4071 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
4072 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
4073 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
4074 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
4075 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
4077
4079 gui_changed(self, NULL, NULL);
4080
4081 if(changed)
4082 {
4083 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] history commit source=mixer_mode_primaries\n");
4085 }
4086 }
4087}
4088
4089static void _channelmixerrgb_simple_slider_callback(GtkWidget *slider, gpointer user_data)
4090{
4091 if(dt_gui_widgets_suppressed()) return;
4092
4093 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4096 GtkWidget *const widgets[6]
4097 = { g->simple_theta, g->simple_psi, g->simple_stretch_1, g->simple_stretch_2, g->simple_coupling_1,
4098 g->simple_coupling_2 };
4100 float M[3][3] = { { 0.f } };
4101
4104
4105 for(int col = 0; col < 3; col++)
4106 {
4107 p->red[col] = M[0][col];
4108 p->green[col] = M[1][col];
4109 p->blue[col] = M[2][col];
4110 }
4111
4113 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), p->normalize_R);
4114 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), p->normalize_G);
4115 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), p->normalize_B);
4116
4117 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
4118 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
4119 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
4120 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
4121 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
4122 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
4123 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
4124 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
4125 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
4127
4128 gui_changed(self, slider, NULL);
4129
4130 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] history commit source=simple_slider slider=%p\n", (void *)slider);
4132}
4133
4134static void _channelmixerrgb_primaries_slider_callback(GtkWidget *slider, gpointer user_data)
4135{
4136 if(dt_gui_widgets_suppressed()) return;
4137
4138 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4141 GtkWidget *const widgets[9]
4142 = { g->primaries_achromatic_hue, g->primaries_achromatic_purity, g->primaries_red_hue,
4143 g->primaries_red_purity, g->primaries_green_hue, g->primaries_green_purity,
4144 g->primaries_blue_hue, g->primaries_blue_purity, g->primaries_gain };
4148 float M[3][3] = { { 0.f } };
4149
4151 if(!dt_iop_channelmixer_shared_primaries_to_matrix(basis, &primaries, M))
4152 {
4153 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
4154 return;
4155 }
4156
4157 for(int col = 0; col < 3; col++)
4158 {
4159 p->red[col] = M[0][col];
4160 p->green[col] = M[1][col];
4161 p->blue[col] = M[2][col];
4162 }
4163 p->normalize_R = FALSE;
4164 p->normalize_G = FALSE;
4165 p->normalize_B = FALSE;
4166
4168 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_R), FALSE);
4169 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_G), FALSE);
4170 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->normalize_B), FALSE);
4171
4172 dt_bauhaus_slider_set(g->scale_red_R, p->red[0]);
4173 dt_bauhaus_slider_set(g->scale_red_G, p->red[1]);
4174 dt_bauhaus_slider_set(g->scale_red_B, p->red[2]);
4175 dt_bauhaus_slider_set(g->scale_green_R, p->green[0]);
4176 dt_bauhaus_slider_set(g->scale_green_G, p->green[1]);
4177 dt_bauhaus_slider_set(g->scale_green_B, p->green[2]);
4178 dt_bauhaus_slider_set(g->scale_blue_R, p->blue[0]);
4179 dt_bauhaus_slider_set(g->scale_blue_G, p->blue[1]);
4180 dt_bauhaus_slider_set(g->scale_blue_B, p->blue[2]);
4182
4183 gui_changed(self, slider, NULL);
4184
4185 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] history commit source=primaries_slider slider=%p\n", (void *)slider);
4187}
4188
4189
4190void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
4191{
4194 const gboolean simple_widget
4195 = w == g->simple_theta || w == g->simple_psi || w == g->simple_stretch_1 || w == g->simple_stretch_2
4196 || w == g->simple_coupling_1 || w == g->simple_coupling_2;
4197 const gboolean primaries_widget
4198 = w == g->primaries_achromatic_hue || w == g->primaries_achromatic_purity || w == g->primaries_red_hue
4199 || w == g->primaries_red_purity || w == g->primaries_green_hue || w == g->primaries_green_purity
4200 || w == g->primaries_blue_hue || w == g->primaries_blue_purity || w == g->primaries_gain;
4201 const gboolean normalize[3] = { p->normalize_R, p->normalize_G, p->normalize_B };
4202 const gboolean rows_are_normalized = dt_iop_channelmixer_shared_rows_are_normalized(normalize);
4203 const gboolean complete_widget
4204 = w == g->scale_red_R || w == g->scale_red_G || w == g->scale_red_B || w == g->scale_green_R
4205 || w == g->scale_green_G || w == g->scale_green_B || w == g->scale_blue_R || w == g->scale_blue_G
4206 || w == g->scale_blue_B || w == g->normalize_R || w == g->normalize_G || w == g->normalize_B;
4207 // Some Bauhaus setters emit during gui_init(), so synchronize the illuminant group only after
4208 // every widget sharing the same xyY/Lch state has been created.
4209 const gboolean illuminant_widgets_ready
4210 = !IS_NULL_PTR(g->illuminant) && !IS_NULL_PTR(g->illum_fluo) && !IS_NULL_PTR(g->illum_led)
4211 && !IS_NULL_PTR(g->temperature) && !IS_NULL_PTR(g->illum_color) && !IS_NULL_PTR(g->illum_x)
4212 && !IS_NULL_PTR(g->illum_y);
4213
4214 if(!IS_NULL_PTR(w) && w == g->illuminant)
4215 {
4216 if(!IS_NULL_PTR(previous))
4217 {
4218 dt_illuminant_t *prev_illuminant = (dt_illuminant_t *)previous;
4219 if(*prev_illuminant == DT_ILLUMINANT_CAMERA)
4220 {
4221 // If illuminant was previously set with "as set in camera",
4222 // when changing it, we need to ensure the temperature and chromaticity
4223 // are inited with the correct values taken from camera EXIF.
4224 // Otherwise, if using a preset defining illuminant = "as set in camera",
4225 // temperature and chromaticity are inited with the preset content when illuminant is changed.
4226 dt_aligned_pixel_t custom_wb;
4227 get_white_balance_coeff(self, custom_wb);
4228 find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(p->x), &(p->y));
4229 check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation));
4230 }
4231 }
4232 if(p->illuminant == DT_ILLUMINANT_CAMERA)
4233 {
4234 // Get camera WB and update illuminant
4235 dt_aligned_pixel_t custom_wb;
4236 get_white_balance_coeff(self, custom_wb);
4237 const int found = find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(p->x), &(p->y));
4238 check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation));
4239
4240 if(found)
4241 dt_control_log(_("white balance successfully extracted from raw image"));
4242 }
4243 else if(p->illuminant == DT_ILLUMINANT_DETECT_EDGES
4244 || p->illuminant == DT_ILLUMINANT_DETECT_SURFACES)
4245 {
4246 // We need to recompute only the full preview
4247 dt_control_log(_("auto-detection of white balance started..."));
4248 }
4249 }
4250
4251 if(!IS_NULL_PTR(w) && (w == g->illuminant || w == g->illum_fluo || w == g->illum_led || w == g->temperature))
4252 {
4253 // Convert and synchronize all the possible ways to define an illuminant to allow swapping modes
4254
4255 if(p->illuminant != DT_ILLUMINANT_CUSTOM && p->illuminant != DT_ILLUMINANT_CAMERA)
4256 {
4257 // We are in any mode defining (x, y) indirectly from an interface, so commit (x, y) explicitly
4258 illuminant_to_xy(p->illuminant, NULL, NULL, &(p->x), &(p->y), p->temperature, p->illum_fluo, p->illum_led);
4259 }
4260
4261 if(p->illuminant != DT_ILLUMINANT_D && p->illuminant != DT_ILLUMINANT_BB && p->illuminant != DT_ILLUMINANT_CAMERA)
4262 {
4263 // We are in any mode not defining explicitly a temperature, so find the the closest CCT and commit it
4264 check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, NULL);
4265 }
4266 }
4267
4269
4270 if(IS_NULL_PTR(w) || w == g->hue_spot || w == g->chroma_spot || w == g->lightness_spot || w == g->spot_settings)
4271 {
4272 paint_hue(self);
4273 }
4274
4275 if((IS_NULL_PTR(w) || w == g->illuminant || w == g->illum_fluo || w == g->illum_led || w == g->temperature)
4276 && illuminant_widgets_ready)
4277 {
4278 update_illuminants(self);
4279 update_approx_cct(self);
4281
4282 // force-update all the illuminant sliders in case something above changed them
4283 // notice the hue/chroma of the illuminant has to be computed on-the-fly anyway
4284 dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
4286 dt_xyY_to_Lch(xyY, Lch);
4287
4288 // If the chroma is zero then there is not a meaningful hue angle. In this case
4289 // leave the hue slider where it was, so that if chroma is set to zero and then
4290 // set to a nonzero value, the hue setting will remain unchanged.
4291 if(Lch[1] > 0)
4292 dt_bauhaus_slider_set(g->illum_x, Lch[2] / M_PI * 180.f);
4293 dt_bauhaus_slider_set(g->illum_y, Lch[1]);
4294
4295 // Redraw the temperature background color taking new soft bounds into account
4296 dt_bauhaus_slider_set(g->temperature, p->temperature);
4298 }
4299
4300 if(w == g->adaptation)
4301 update_illuminants(self);
4302
4303 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)
4304 _update_RGB_colors(self, 1, 0, 0, p->normalize_R, p->red, g->scale_red_R, g->scale_red_G, g->scale_red_B);
4305 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)
4306 _update_RGB_colors(self, 0, 1, 0, p->normalize_G, p->green, g->scale_green_R, g->scale_green_G, g->scale_green_B);
4307 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)
4308 _update_RGB_colors(self, 0, 0, 1, p->normalize_B, p->blue, g->scale_blue_R, g->scale_blue_G, g->scale_blue_B);
4309
4310 if(rows_are_normalized && !simple_widget && (IS_NULL_PTR(w) || complete_widget))
4311 {
4312 float error = INFINITY;
4314 {
4316 {
4317 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] simple mixer rejected error=%g normalized=%d%d%d\n",
4318 error, p->normalize_R, p->normalize_G, p->normalize_B);
4319 dt_control_log(_("simple mixer mode requires all three output rows to be normalized with non-zero sums."));
4323 }
4324 else
4325 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] simple mixer roundtrip error=%g\n", error);
4326 }
4327 }
4328
4329 if(!primaries_widget && (IS_NULL_PTR(w) || complete_widget || simple_widget || w == g->adaptation))
4330 {
4331 float error = INFINITY;
4333 {
4335 {
4336 dt_print(DT_DEBUG_DEV, "[channelmixerrgb] primaries mixer roundtrip error=%g\n", error);
4340 }
4341 }
4342 }
4343
4344 if(IS_NULL_PTR(w) || w == g->adaptation || complete_widget || simple_widget || primaries_widget)
4346
4347 if(IS_NULL_PTR(w) || w == g->adaptation || complete_widget || simple_widget || primaries_widget)
4349
4350 // if grey channel is used and norm = 0 and normalization = ON, we are going to have a division by zero
4351 // in commit_param, we avoid dividing by zero automatically, but user needs a notification
4352 if((p->grey[0] != 0.f) || (p->grey[1] != 0.f) || (p->grey[2] != 0.f))
4353 if((p->grey[0] + p->grey[1] + p->grey[2] == 0.f) && p->normalize_grey)
4354 dt_control_log(_("color calibration: the sum of the gray channel parameters is zero, normalization will be disabled."));
4355
4356 // If "as shot in camera" illuminant is used, CAT space is forced automatically
4357 // therefore, make the control insensitive
4358 gtk_widget_set_sensitive(g->adaptation, p->illuminant != DT_ILLUMINANT_CAMERA);
4359 gtk_widget_queue_draw(g->adaptation);
4360
4362
4364}
4365
4367{
4370 const dt_iop_channelmixer_rgb_params_t previous = *p;
4371
4372 // capture gui color picked event.
4373 if(self->picked_color_max[0] < self->picked_color_min[0])
4374 {
4375 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] rejected invalid min/max min=%g max=%g\n",
4376 self->picked_color_min[0], self->picked_color_max[0]);
4377 return;
4378 }
4379 const float *RGB = self->picked_color;
4380 if(!isfinite(RGB[0]) || !isfinite(RGB[1]) || !isfinite(RGB[2]))
4381 {
4382 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] rejected nonfinite RGB=(%g,%g,%g)\n",
4383 RGB[0], RGB[1], RGB[2]);
4384 return;
4385 }
4386 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] RGB=(%g,%g,%g) pipe=%p\n",
4387 RGB[0], RGB[1], RGB[2], (void *)pipe);
4388
4389 // Get the module-stage profile matching the sampled buffer.
4390 const dt_iop_order_iccprofile_info_t *const current_profile
4392 if(IS_NULL_PTR(current_profile))
4393 {
4394 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] missing current profile\n");
4395 return;
4396 }
4397
4398 // Convert the sampled linear RGB code values to XYZ in the module profile.
4399 // The picker already sampled the live module buffer, so keep the conversion explicit here.
4400 dt_aligned_pixel_t XYZ = { 0.f };
4401 dot_product(RGB, current_profile->matrix_in, XYZ);
4402 if(!isfinite(XYZ[0]) || !isfinite(XYZ[1]) || !isfinite(XYZ[2])) return;
4403 dt_XYZ_to_sRGB(XYZ, g->spot_RGB);
4404
4405 // Convert to Lch for GUI feedback (input)
4409 dt_Lab_2_LCH(Lab, Lch);
4410
4411 // Write report in GUI
4413 gtk_label_set_text(GTK_LABEL(g->Lch_origin),
4414 g_strdup_printf(_("L: \t%.1f %%\nh: \t%.1f \302\260\nc: \t%.1f"),
4415 Lch[0], Lch[2] * 360.f, Lch[1] ));
4416 gtk_widget_queue_draw(g->origin_spot);
4418
4419 const dt_spot_mode_t mode = dt_bauhaus_combobox_get(g->spot_mode);
4420 const gboolean use_mixing = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->use_mixing));
4421
4422 // build the channel mixing matrix - keep in synch with commit_params()
4423 dt_colormatrix_t MIX = { { 0.f } };
4424
4425 float norm_R = 1.0f;
4426 if(p->normalize_R) norm_R = p->red[0] + p->red[1] + p->red[2];
4427
4428 float norm_G = 1.0f;
4429 if(p->normalize_G) norm_G = p->green[0] + p->green[1] + p->green[2];
4430
4431 float norm_B = 1.0f;
4432 if(p->normalize_B) norm_B = p->blue[0] + p->blue[1] + p->blue[2];
4433
4434 for(int i = 0; i < 3; i++)
4435 {
4436 MIX[0][i] = p->red[i] / norm_R;
4437 MIX[1][i] = p->green[i] / norm_G;
4438 MIX[2][i] = p->blue[i] / norm_B;
4439 }
4440
4441 if(mode == DT_SPOT_MODE_MEASURE)
4442 {
4443 // Keep the following in sync with commit_params()
4444
4445 // find x y coordinates of illuminant for CIE 1931 2° observer
4446 float x = p->x;
4447 float y = p->y;
4448 dt_adaptation_t adaptation = p->adaptation;
4449 dt_aligned_pixel_t custom_wb;
4450 get_white_balance_coeff(self, custom_wb);
4451 illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led);
4452
4453 // if illuminant is set as camera, x and y are set on-the-fly at commit time, so we need to set adaptation too
4454 if(p->illuminant == DT_ILLUMINANT_CAMERA) check_if_close_to_daylight(x, y, NULL, NULL, &adaptation);
4455
4456 // Convert illuminant from xyY to XYZ
4457 dt_aligned_pixel_t XYZ_illuminant = { 0.f };
4458 illuminant_xy_to_XYZ(x, y, XYZ_illuminant);
4459
4460 // Convert illuminant from XYZ to Bradford modified LMS
4461 dt_aligned_pixel_t LMS_illuminant = { 0.f };
4462 dt_store_simd_aligned(LMS_illuminant, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_illuminant), adaptation));
4463
4464 // For the non-linear Bradford
4465 const float pp = powf(0.818155f / LMS_illuminant[2], 0.0834f);
4466
4467 //fprintf(stdout, "illuminant: %i\n", p->illuminant);
4468 //fprintf(stdout, "x: %f, y: %f\n", x, y);
4469 //fprintf(stdout, "X: %f - Y: %f - Z: %f\n", XYZ_illuminant[0], XYZ_illuminant[1], XYZ_illuminant[2]);
4470 //fprintf(stdout, "L: %f - M: %f - S: %f\n", LMS_illuminant[0], LMS_illuminant[1], LMS_illuminant[2]);
4471
4472 // Finally, chroma-adapt the pixel
4473 dt_aligned_pixel_t XYZ_output = { 0.f };
4474 dt_store_simd_aligned(XYZ_output, chroma_adapt_pixel(dt_load_simd_aligned(XYZ),
4475 dt_load_simd_aligned(LMS_illuminant),
4476 adaptation, pp));
4477
4478 // Optionaly, apply the channel mixing
4479 if(use_mixing)
4480 {
4481 dt_aligned_pixel_t LMS_output = { 0.f };
4482 dt_store_simd_aligned(LMS_output, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_output), adaptation));
4483 dt_aligned_pixel_t temp = { 0.f };
4484 dot_product(LMS_output, MIX, temp);
4485 dt_store_simd_aligned(XYZ_output, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), adaptation));
4486 }
4487
4488 // Convert to Lab and Lch for GUI feedback
4489 dt_aligned_pixel_t Lab_output = { 0.f };
4490 dt_aligned_pixel_t Lch_output = { 0.f };
4491 dt_XYZ_to_Lab(XYZ_output, Lab_output);
4492 dt_Lab_2_LCH(Lab_output, Lch_output);
4493
4494 // Return the values in sliders
4496 dt_bauhaus_slider_set(g->lightness_spot, Lch_output[0]);
4497 dt_bauhaus_slider_set(g->chroma_spot, Lch_output[1]);
4498 dt_bauhaus_slider_set(g->hue_spot, Lch_output[2] * 360.f);
4499 paint_hue(self);
4501
4502 dt_conf_set_float("darkroom/modules/channelmixerrgb/lightness", Lch_output[0]);
4503 dt_conf_set_float("darkroom/modules/channelmixerrgb/chroma", Lch_output[1]);
4504 dt_conf_set_float("darkroom/modules/channelmixerrgb/hue", Lch_output[2] * 360.f);
4505 dt_conf_set_bool("darkroom/modules/channelmixerrgb/use_mixing", use_mixing);
4506 }
4507 else if(mode == DT_SPOT_MODE_CORRECT)
4508 {
4509 // Get the target color in LMS space
4510 dt_aligned_pixel_t Lch_target = { 0.f };
4511 dt_aligned_pixel_t Lab_target = { 0.f };
4512 dt_aligned_pixel_t XYZ_target = { 0.f };
4513 dt_aligned_pixel_t LMS_target = { 0.f };
4514
4516 Lch_target[0] = dt_bauhaus_slider_get(g->lightness_spot);
4517 Lch_target[1] = dt_bauhaus_slider_get(g->chroma_spot);
4518 Lch_target[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f;
4520
4521 dt_LCH_2_Lab(Lch_target, Lab_target);
4522 dt_Lab_to_XYZ(Lab_target, XYZ_target);
4523 const float Y_target = XYZ_target[1];
4524 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_target;
4525 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4526
4527 // optionaly, apply the inverse mixing on the target
4528 if(use_mixing)
4529 {
4530 // Repack the MIX matrix to 3x3 to support the pseudoinverse function
4531 // I'm just too lazy to rewrite the pseudo-inverse for 3x4 padded input
4532 float MIX_3x3[9];
4533 pack_3xSSE_to_3x3(MIX, MIX_3x3);
4534
4535 /* DEBUG
4536 fprintf(stdout, "Repacked channel mixer matrix :\n");
4537 fprintf(stdout, "%f \t%f \t%f\n", MIX_3x3[0][0], MIX_3x3[0][1], MIX_3x3[0][2]);
4538 fprintf(stdout, "%f \t%f \t%f\n", MIX_3x3[1][0], MIX_3x3[1][1], MIX_3x3[1][2]);
4539 fprintf(stdout, "%f \t%f \t%f\n", MIX_3x3[2][0], MIX_3x3[2][1], MIX_3x3[2][2]);
4540 */
4541
4542 // Invert the matrix
4543 float MIX_INV_3x3[9];
4544 matrice_pseudoinverse((float (*)[3])MIX_3x3, (float (*)[3])MIX_INV_3x3, 3);
4545
4546 // Transpose and repack the inverse to SSE matrix because the inversion transposes too
4547 dt_colormatrix_t MIX_INV;
4548 transpose_3x3_to_3xSSE(MIX_INV_3x3, MIX_INV);
4549
4550 /* DEBUG
4551 fprintf(stdout, "Repacked inverted channel mixer matrix :\n");
4552 fprintf(stdout, "%f \t%f \t%f\n", MIX_INV[0][0], MIX_INV[0][1], MIX_INV[0][2]);
4553 fprintf(stdout, "%f \t%f \t%f\n", MIX_INV[1][0], MIX_INV[1][1], MIX_INV[1][2]);
4554 fprintf(stdout, "%f \t%f \t%f\n", MIX_INV[2][0], MIX_INV[2][1], MIX_INV[2][2]);
4555 */
4556
4557 // Undo the channel mixing on the reference color
4558 // So we get the expected target color after the CAT
4559 dt_aligned_pixel_t temp;
4560 dot_product(LMS_target, MIX_INV, temp);
4561
4562 //fprintf(stdout, "LMS before channel mixer inversion : \t%f \t%f \t%f\n", LMS_target[0], LMS_target[1], LMS_target[2]);
4563 //fprintf(stdout, "LMS after channel mixer inversion : \t%f \t%f \t%f\n", temp[0], temp[1], temp[2]);
4564
4565 // convert back to XYZ to normalize luminance again
4566 // in case the matrix is not normalized
4567 dt_store_simd_aligned(XYZ_target, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), p->adaptation));
4568 const float Y_mix = XYZ_target[1];
4569 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_mix;
4570 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4571
4572 //fprintf(stdout, "LMS target after everything : %f \t%f \t%f\n", LMS_target[0], LMS_target[1], LMS_target[2]);
4573
4574 // So now we got the target color after CAT and before mixing
4575 // in LMS space
4576 }
4577
4578 // Get the input color in LMS space
4579 dt_aligned_pixel_t LMS = { 0.f };
4580 const float Y = XYZ[1];
4581 for(int c = 0; c < 3; c++) XYZ[c] /= Y;
4582 dt_store_simd_aligned(LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), p->adaptation));
4583
4584 // Find the illuminant
4586 convert_D50_to_LMS(p->adaptation, D50);
4587
4588 dt_aligned_pixel_t illuminant_LMS = { 0.f };
4589 dt_aligned_pixel_t illuminant_XYZ = { 0.f };
4590
4591 // We solve the equation : target color / input color = D50 / illuminant, for illuminant
4592 for(int c = 0; c < 3; c++) illuminant_LMS[c] = D50[c] * LMS[c] / LMS_target[c];
4593 dt_store_simd_aligned(illuminant_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(illuminant_LMS), p->adaptation));
4594
4595 // Convert to xyY
4596 const float sum = fmaxf(illuminant_XYZ[0] + illuminant_XYZ[1] + illuminant_XYZ[2], NORM_MIN);
4597 illuminant_XYZ[0] /= sum; // x
4598 illuminant_XYZ[2] = illuminant_XYZ[1]; // Y
4599 illuminant_XYZ[1] /= sum; // y
4600
4601 p->x = illuminant_XYZ[0];
4602 p->y = illuminant_XYZ[1];
4603
4604 // Force illuminant to custom, the daylight/black body approximations are
4605 // not accurate enough for color matching
4606 p->illuminant = DT_ILLUMINANT_CUSTOM;
4607
4609
4610 check_if_close_to_daylight(p->x, p->y, &p->temperature, NULL, NULL);
4611
4612 dt_bauhaus_slider_set(g->temperature, p->temperature);
4613 dt_bauhaus_combobox_set(g->illuminant, p->illuminant);
4614 dt_bauhaus_combobox_set(g->adaptation, p->adaptation);
4615
4616 const dt_aligned_pixel_t xyY = { p->x, p->y, 1.f };
4617 dt_aligned_pixel_t Lch_illuminant = { 0 };
4618 dt_xyY_to_Lch(xyY, Lch_illuminant);
4619 dt_bauhaus_slider_set(g->illum_x, Lch_illuminant[2] / M_PI * 180.f);
4620 dt_bauhaus_slider_set(g->illum_y, Lch_illuminant[1]);
4621
4622 update_illuminants(self);
4623 update_approx_cct(self);
4625 paint_hue(self);
4627 gtk_widget_queue_draw(g->origin_spot);
4628
4630
4631 if(memcmp(&previous, p, sizeof(previous)) != 0)
4632 {
4633 /* Auto-illuminant is driven by live picker motion. Writing history synchronously for every
4634 sampled move can outpace the preview worker and keep it permanently in TOP_CHANGED.
4635 Queue the standard throttled history update instead so the picker remains live while
4636 the worker converges on the latest sampled state. */
4637 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] history commit source=auto_set_illuminant\n");
4639 }
4640 }
4641}
4642
4643
4645{
4646 if(dt_gui_widgets_suppressed()) return;
4647 dt_print(DT_DEBUG_DEV, "[picker/channelmixerrgb] apply picker=%p pipe=%p\n", (void *)picker, (void *)pipe);
4648 _auto_set_illuminant(self, pipe);
4649}
4650
4651
4652void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
4653 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
4654{
4655 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4657 if(piece->dsc_in.channels != 4) return;
4658
4659 const dt_iop_order_iccprofile_info_t *const current_profile
4661 if(IS_NULL_PTR(current_profile)) return;
4662
4663 dt_colormatrix_t MIX = { { 0.f } };
4664
4665 float norm_R = 1.0f;
4666 if(p->normalize_R) norm_R = p->red[0] + p->red[1] + p->red[2];
4667
4668 float norm_G = 1.0f;
4669 if(p->normalize_G) norm_G = p->green[0] + p->green[1] + p->green[2];
4670
4671 float norm_B = 1.0f;
4672 if(p->normalize_B) norm_B = p->blue[0] + p->blue[1] + p->blue[2];
4673
4674 for(int c = 0; c < 3; c++)
4675 {
4676 MIX[0][c] = p->red[c] / norm_R;
4677 MIX[1][c] = p->green[c] / norm_G;
4678 MIX[2][c] = p->blue[c] / norm_B;
4679 }
4680
4681 float lightness = 50.f;
4682 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/lightness"))
4683 lightness = dt_conf_get_float("darkroom/modules/channelmixerrgb/lightness");
4684
4685 float hue = 0.f;
4686 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/hue"))
4687 hue = dt_conf_get_float("darkroom/modules/channelmixerrgb/hue");
4688
4689 float chroma = 0.f;
4690 if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/chroma"))
4691 chroma = dt_conf_get_float("darkroom/modules/channelmixerrgb/chroma");
4692
4693 dt_aligned_pixel_t Lch_target = { lightness, chroma, hue / 360.f, 0.f };
4694 dt_aligned_pixel_t Lab_target = { 0.f };
4695 dt_aligned_pixel_t XYZ_target = { 0.f };
4696 dt_aligned_pixel_t LMS_target = { 0.f };
4697 dt_LCH_2_Lab(Lch_target, Lab_target);
4698 dt_Lab_to_XYZ(Lab_target, XYZ_target);
4699
4700 // Normalize for unit luminance (illuminant)
4701 const float Y_target = XYZ_target[1];
4702 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_target;
4703 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4704
4705 float MIX_3x3[9];
4706 pack_3xSSE_to_3x3(MIX, MIX_3x3);
4707
4708 float MIX_INV_3x3[9];
4709 matrice_pseudoinverse((float (*)[3])MIX_3x3, (float (*)[3])MIX_INV_3x3, 3);
4710
4711 dt_colormatrix_t MIX_INV;
4712 transpose_3x3_to_3xSSE(MIX_INV_3x3, MIX_INV);
4713
4714 dt_aligned_pixel_t temp = { 0.f };
4715 dot_product(LMS_target, MIX_INV, temp);
4716
4717 dt_store_simd_aligned(XYZ_target, convert_any_LMS_to_XYZ(dt_load_simd_aligned(temp), p->adaptation));
4718 const float Y_mix = XYZ_target[1];
4719 if(Y_mix <= NORM_MIN) return;
4720 for(int c = 0; c < 3; c++) XYZ_target[c] /= Y_mix;
4721 dt_store_simd_aligned(LMS_target, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ_target), p->adaptation));
4722
4723 const float *const restrict in = (float *)i;
4724 float average_L = 0.f;
4725 float average_M = 0.f;
4726 float average_S = 0.f;
4727 size_t valid_pixels = 0;
4728
4729 // Compute average LMS in the image
4730 __OMP_PARALLEL_FOR__(reduction(+:average_L, average_M, average_S, valid_pixels))
4731 for(size_t k = 0; k < roi_out->width * roi_out->height * 4; k += 4)
4732 {
4733 // Convert each input pixel to the module CAT space first, then average the chromaticity there.
4734 dt_aligned_pixel_t XYZ = { 0.f };
4735 dt_aligned_pixel_t LMS = { 0.f };
4736 dt_ioppr_rgb_matrix_to_xyz(in + k, XYZ, current_profile->matrix_in_transposed, current_profile->lut_in,
4737 current_profile->unbounded_coeffs_in, current_profile->lutsize,
4738 current_profile->nonlinearlut);
4739
4740 dt_store_simd_aligned(LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(XYZ), p->adaptation));
4741 if(!isfinite(LMS[0]) || !isfinite(LMS[1]) || !isfinite(LMS[2])) continue;
4742
4743 average_L += LMS[0];
4744 average_M += LMS[1];
4745 average_S += LMS[2];
4746 valid_pixels++;
4747 }
4748
4749 if(valid_pixels == 0) return;
4750
4751 const float norm = 1.f / (float)valid_pixels;
4752 dt_aligned_pixel_t average_LMS = { average_L * norm, average_M * norm, average_S * norm, 0.f };
4753
4754 // Normalize for unit luminance
4755 dt_aligned_pixel_t average_XYZ = { 0.f };
4756 dt_store_simd_aligned(average_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(average_LMS), p->adaptation));
4757 const float Y_average_lms = average_XYZ[1];
4758 for(int c = 0; c < 3; c++) average_XYZ[c] /= Y_average_lms;
4759 dt_store_simd_aligned(average_LMS, convert_any_XYZ_to_LMS(dt_load_simd_aligned(average_XYZ), p->adaptation));
4760
4761 dt_aligned_pixel_t D50 = { 0.f };
4762 dt_aligned_pixel_t illuminant_LMS = { 0.f };
4763 dt_aligned_pixel_t illuminant_XYZ = { 0.f };
4764 convert_D50_to_LMS(p->adaptation, D50);
4765
4766 for(int c = 0; c < 3; c++)
4767 {
4768 const float target = copysignf(fmaxf(fabsf(LMS_target[c]), NORM_MIN), LMS_target[c]);
4769 illuminant_LMS[c] = D50[c] * average_LMS[c] / target;
4770 }
4771
4772 dt_store_simd_aligned(illuminant_XYZ, convert_any_LMS_to_XYZ(dt_load_simd_aligned(illuminant_LMS), p->adaptation));
4773
4774 const float sum = fmaxf(illuminant_XYZ[0] + illuminant_XYZ[1] + illuminant_XYZ[2], NORM_MIN);
4775 p->x = illuminant_XYZ[0] / sum;
4776 p->y = illuminant_XYZ[1] / sum;
4777 p->illuminant = DT_ILLUMINANT_CUSTOM;
4778 check_if_close_to_daylight(p->x, p->y, &p->temperature, NULL, NULL);
4779}
4780
4781
4782void gui_init(struct dt_iop_module_t *self)
4783{
4785
4786 // Init the color checker UI
4787 for(size_t k = 0; k < 4; k++)
4788 {
4789 g->box[k].x = g->box[k].y = -1.;
4790 g->active_node[k] = FALSE;
4791 }
4792 g->is_cursor_close = FALSE;
4793 g->drag_drop = FALSE;
4794 g->is_profiling_started = FALSE;
4795 g->run_profile = FALSE;
4796 g->run_validation = FALSE;
4797 g->profile_ready = FALSE;
4798 g->checker_ready = FALSE;
4799 g->delta_E_in = NULL;
4800 g->delta_E_label_text = NULL;
4801 g->colorcheckers = NULL;
4802
4803 g->XYZ[0] = NAN;
4804
4806 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
4808 G_CALLBACK(_preview_pipe_finished_callback), self);
4809
4810 // Init GTK notebook
4811 g->notebook = dt_ui_notebook_new();
4812 // The CAT page's illuminant picker is read at apply time; reset it if it is
4813 // still active once the user switches away from that page.
4814 dt_ui_notebook_set_picker_owner(g->notebook, self);
4815
4816 // Page CAT
4817 self->widget = dt_ui_notebook_page(g->notebook, N_("CAT"), _("chromatic adaptation transform"));
4818
4819 g->adaptation = dt_bauhaus_combobox_from_params(self, N_("adaptation"));
4820 gtk_widget_set_tooltip_text(GTK_WIDGET(g->adaptation),
4821 _("choose the method to adapt the illuminant\n"
4822 "and the colorspace in which the module works: \n"
4823 "- Linear Bradford (1985) is consistent with ICC v4 toolchain.\n"
4824 "- CAT16 (2016) is more robust and accurate.\n"
4825 "- Non-linear Bradford (1985) is the original Bradford,\n"
4826 " it can produce better results than the linear version, but is unreliable.\n"
4827 "- XYZ is a simple scaling in XYZ space. It is not recommended in general.\n"
4828 "- none disables any adaptation and uses pipeline working RGB."));
4829
4830 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4831
4832 g->approx_cct = dt_ui_label_new("CCT:");
4833 gtk_box_pack_start(GTK_BOX(hbox), g->approx_cct, FALSE, FALSE, 0);
4834
4835 g->illum_color = GTK_WIDGET(gtk_drawing_area_new());
4836 gtk_widget_set_size_request(g->illum_color, 2 * DT_PIXEL_APPLY_DPI(darktable.bauhaus->quad_width),
4838 gtk_widget_set_tooltip_text(GTK_WIDGET(g->illum_color),
4839 _("this is the color of the scene illuminant before chromatic adaptation\n"
4840 "this color will be turned into pure white by the adaptation."));
4841
4842 g_signal_connect(G_OBJECT(g->illum_color), "draw", G_CALLBACK(illuminant_color_draw), self);
4843 gtk_box_pack_start(GTK_BOX(hbox), g->illum_color, TRUE, TRUE, 0);
4844
4845 g->color_picker = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, hbox);
4846 gtk_widget_set_tooltip_text(g->color_picker, _("set white balance to detected from area"));
4847
4848 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), FALSE, FALSE, 0);
4849
4850 g->illuminant = dt_bauhaus_combobox_from_params(self, N_("illuminant"));
4851
4852 g->illum_fluo = dt_bauhaus_combobox_from_params(self, "illum_fluo");
4853
4854 g->illum_led = dt_bauhaus_combobox_from_params(self, "illum_led");
4855
4856 g->temperature = dt_bauhaus_slider_from_params(self, N_("temperature"));
4857
4859 dt_bauhaus_widget_set_label(g->illum_x, N_("hue"));
4860 dt_bauhaus_slider_set_format(g->illum_x, "\302\260");
4861 g_signal_connect(G_OBJECT(g->illum_x), "value-changed", G_CALLBACK(illum_xy_callback), self);
4862 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->illum_x), FALSE, FALSE, 0);
4863
4864 g->illum_y = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0., 100., 0, 0, 1);
4865 dt_bauhaus_widget_set_label(g->illum_y, N_("chroma"));
4866 dt_bauhaus_slider_set_format(g->illum_y, "%");
4868 g_signal_connect(G_OBJECT(g->illum_y), "value-changed", G_CALLBACK(illum_xy_callback), self);
4869 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->illum_y), FALSE, FALSE, 0);
4870
4871 dt_bauhaus_slider_set_soft_range(g->temperature, 3000., 7000.);
4872 dt_bauhaus_slider_set_digits(g->temperature, 0);
4873 dt_bauhaus_slider_set_format(g->temperature, " K");
4874
4875 g->gamut = dt_bauhaus_slider_from_params(self, "gamut");
4876 dt_bauhaus_slider_set_soft_max(g->gamut, 4.f);
4877
4878 g->clip = dt_bauhaus_toggle_from_params(self, "clip");
4879
4880 // Add the color mapping collapsible panel
4881
4883 (&g->csspot,
4884 "plugins/darkroom/channelmixerrgb/expand_picker_mapping",
4885 _("spot color mapping"),
4886 GTK_BOX(self->widget), GTK_PACK_END);
4887
4888 gtk_widget_set_tooltip_text(g->csspot.expander, _("use a color checker target to autoset CAT and channels"));
4889
4890 DT_BAUHAUS_COMBOBOX_NEW_FULL(darktable.bauhaus, g->spot_mode, DT_GUI_MODULE(self), N_("spot mode"),
4891 _("\"correction\" automatically adjust the illuminant\n"
4892 "such that the input color is mapped to the target.\n"
4893 "\"measure\" simply shows how an input color is mapped by the CAT\n"
4894 "and can be used to sample a target."),
4895 0, NULL, self,
4896 N_("correction"),
4897 N_("measure"));
4898 gtk_box_pack_start(GTK_BOX(g->csspot.container), GTK_WIDGET(g->spot_mode), TRUE, TRUE, 0);
4899 g_signal_connect(G_OBJECT(g->spot_mode), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4900
4901 gchar *label = N_("take channel mixing into account");
4902 g->use_mixing = gtk_check_button_new_with_label(_(label));
4903 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->use_mixing))), PANGO_ELLIPSIZE_END);
4904 gtk_widget_set_tooltip_text(g->use_mixing,
4905 _("compute the target by taking the channel mixing into account.\n"
4906 "if disabled, only the CAT is considered."));
4907 gtk_box_pack_start(GTK_BOX(g->csspot.container), GTK_WIDGET(g->use_mixing), TRUE, TRUE, 0);
4908 g_signal_connect(G_OBJECT(g->use_mixing), "toggled", G_CALLBACK(_spot_settings_changed_callback), self);
4909
4910 GtkWidget *hhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4911 GtkWidget *vvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4912
4913 gtk_box_pack_start(GTK_BOX(vvbox), dt_ui_section_label_new(_("input")), FALSE, FALSE, 0);
4914
4915 g->origin_spot = GTK_WIDGET(gtk_drawing_area_new());
4916 gtk_widget_set_size_request(g->origin_spot, 2 * DT_PIXEL_APPLY_DPI(darktable.bauhaus->quad_width),
4918 gtk_widget_set_tooltip_text(GTK_WIDGET(g->origin_spot),
4919 _("the input color that should be mapped to the target"));
4920
4921 g_signal_connect(G_OBJECT(g->origin_spot), "draw", G_CALLBACK(origin_color_draw), self);
4922 gtk_box_pack_start(GTK_BOX(vvbox), g->origin_spot, TRUE, TRUE, 0);
4923
4924 g->Lch_origin = gtk_label_new(_("L: \tN/A\nh: \tN/A\nc: \tN/A"));
4925 gtk_widget_set_tooltip_text(GTK_WIDGET(g->Lch_origin),
4926 _("these LCh coordinates are computed from CIE Lab 1976 coordinates"));
4927 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->Lch_origin), FALSE, FALSE, 0);
4928
4929 gtk_box_pack_start(GTK_BOX(hhbox), GTK_WIDGET(vvbox), FALSE, FALSE, DT_BAUHAUS_SPACE);
4930
4931 vvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4932
4933 gtk_box_pack_start(GTK_BOX(vvbox), dt_ui_section_label_new(_("target")), TRUE, TRUE, 0);
4934
4935 g->target_spot = GTK_WIDGET(gtk_drawing_area_new());
4936 gtk_widget_set_size_request(g->target_spot, 2 * DT_PIXEL_APPLY_DPI(darktable.bauhaus->quad_width),
4938 gtk_widget_set_tooltip_text(GTK_WIDGET(g->target_spot),
4939 _("the desired target color after mapping"));
4940
4941 g_signal_connect(G_OBJECT(g->target_spot), "draw", G_CALLBACK(target_color_draw), self);
4942 gtk_box_pack_start(GTK_BOX(vvbox), g->target_spot, TRUE, TRUE, 0);
4943
4944 g->lightness_spot = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0., LIGHTNESS_MAX, 0, 0, 1);
4945 dt_bauhaus_widget_set_label(g->lightness_spot, N_("lightness"));
4946 dt_bauhaus_slider_set_format(g->lightness_spot, "%");
4947 dt_bauhaus_slider_set_default(g->lightness_spot, 50.f);
4948 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->lightness_spot), TRUE, TRUE, 0);
4949 g_signal_connect(G_OBJECT(g->lightness_spot), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4950
4952 dt_bauhaus_widget_set_label(g->hue_spot, N_("hue"));
4953 dt_bauhaus_slider_set_format(g->hue_spot, "\302\260");
4954 dt_bauhaus_slider_set_default(g->hue_spot, 0.f);
4955 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->hue_spot), TRUE, TRUE, 0);
4956 g_signal_connect(G_OBJECT(g->hue_spot), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4957
4958 g->chroma_spot = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0., CHROMA_MAX, 0, 0, 1);
4959 dt_bauhaus_widget_set_label(g->chroma_spot, N_("chroma"));
4960 dt_bauhaus_slider_set_default(g->chroma_spot, 0.f);
4961 gtk_box_pack_start(GTK_BOX(vvbox), GTK_WIDGET(g->chroma_spot), TRUE, TRUE, 0);
4962 g_signal_connect(G_OBJECT(g->chroma_spot), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self);
4963
4964 gtk_box_pack_start(GTK_BOX(hhbox), GTK_WIDGET(vvbox), TRUE, TRUE, DT_BAUHAUS_SPACE);
4965
4966 gtk_box_pack_start(GTK_BOX(g->csspot.container), GTK_WIDGET(hhbox), FALSE, FALSE, 0);
4967
4968 GtkWidget *mixer_page = dt_ui_notebook_page(g->notebook, N_("Mixer"), _("channel mixing"));
4970 dt_bauhaus_widget_set_label(g->mixer_mode, N_("mode"));
4971 dt_bauhaus_combobox_add(g->mixer_mode, _("Complete"));
4972 dt_bauhaus_combobox_add(g->mixer_mode, _("Simple"));
4973 dt_bauhaus_combobox_add(g->mixer_mode, _("Primaries"));
4974 gtk_widget_set_tooltip_text(g->mixer_mode,
4975 _("complete exposes the original nine mixer coefficients.\n"
4976 "simple rebuilds the normalized mixer as an exact chroma-plane rotation,\n"
4977 "two signed stretches and two neutral couplings.\n"
4978 "primaries rebuilds the mixer as a generalized primaries, white tint and gain model."));
4979 gtk_box_pack_start(GTK_BOX(mixer_page), GTK_WIDGET(g->mixer_mode), FALSE, FALSE, 0);
4980 g_signal_connect(G_OBJECT(g->mixer_mode), "value-changed", G_CALLBACK(_channelmixerrgb_mixer_mode_callback), self);
4981
4982 g->mixer_stack = gtk_stack_new();
4983 gtk_stack_set_transition_type(GTK_STACK(g->mixer_stack), GTK_STACK_TRANSITION_TYPE_NONE);
4984 gtk_box_pack_start(GTK_BOX(mixer_page), GTK_WIDGET(g->mixer_stack), FALSE, FALSE, 0);
4985
4986 GtkWidget *mixer_complete = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4987 gtk_stack_add_named(GTK_STACK(g->mixer_stack), mixer_complete, "complete");
4988
4989 GtkWidget *first, *second, *third;
4990#define MIXER_ROW(var, short, section, swap) \
4991 gtk_box_pack_start(GTK_BOX(mixer_complete), dt_ui_section_label_new(section), FALSE, FALSE, 0); \
4992 self->widget = mixer_complete; \
4993 first = dt_bauhaus_slider_from_params(self, swap ? #var "[2]" : #var "[0]");\
4994 dt_bauhaus_slider_set_digits(first, 3); \
4995 dt_bauhaus_widget_set_label(first, N_("input R")); \
4996 second = dt_bauhaus_slider_from_params(self, #var "[1]"); \
4997 dt_bauhaus_slider_set_digits(second, 3); \
4998 dt_bauhaus_widget_set_label(second, N_("input G")); \
4999 third = dt_bauhaus_slider_from_params(self, swap ? #var "[0]" : #var "[2]");\
5000 dt_bauhaus_slider_set_digits(third, 3); \
5001 dt_bauhaus_widget_set_label(third, N_("input B")); \
5002 g->scale_##var##_R = swap ? third : first; \
5003 g->scale_##var##_G = second; \
5004 g->scale_##var##_B = swap ? first : third; \
5005 g->normalize_##short = dt_bauhaus_toggle_from_params(self, "normalize_" #short);
5006
5007 MIXER_ROW(red, R, _("output red"), FALSE)
5008 MIXER_ROW(green, G, _("output green"), FALSE)
5009 MIXER_ROW(blue, B, _("output blue"), FALSE)
5010
5011 GtkWidget *mixer_simple = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5012 gtk_stack_add_named(GTK_STACK(g->mixer_stack), mixer_simple, "simple");
5013
5014 g->simple_theta = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5015 dt_bauhaus_widget_set_label(g->simple_theta, N_("global hue rotation"));
5016 dt_bauhaus_slider_set_factor(g->simple_theta, 180.f);
5017 dt_bauhaus_slider_set_format(g->simple_theta, "\302\260");
5018 gtk_widget_set_tooltip_text(g->simple_theta, _("global rotation of the normalized chroma plane."));
5019 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_theta), FALSE, FALSE, 0);
5020 g_signal_connect(G_OBJECT(g->simple_theta), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5021
5022 gtk_box_pack_start(GTK_BOX(mixer_simple), dt_ui_section_label_new(_("chroma")), FALSE, FALSE, 0);
5023
5024 g->simple_psi = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5025 dt_bauhaus_widget_set_label(g->simple_psi, N_("chroma (u,v) axes orientation"));
5026 dt_bauhaus_slider_set_factor(g->simple_psi, 90.f);
5027 dt_bauhaus_slider_set_format(g->simple_psi, "\302\260");
5028 gtk_widget_set_tooltip_text(g->simple_psi, _("orientation of the principal stretch axes in the chroma plane."));
5029 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_psi), FALSE, FALSE, 0);
5030 g_signal_connect(G_OBJECT(g->simple_psi), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5031
5032 g->simple_stretch_1 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
5033 dt_bauhaus_widget_set_label(g->simple_stretch_1, N_("u stretch"));
5034 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."));
5035 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_stretch_1), FALSE, FALSE, 0);
5036 g_signal_connect(G_OBJECT(g->simple_stretch_1), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5037
5038 g->simple_stretch_2 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
5039 dt_bauhaus_widget_set_label(g->simple_stretch_2, N_("v stretch"));
5040 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."));
5041 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_stretch_2), FALSE, FALSE, 0);
5042 g_signal_connect(G_OBJECT(g->simple_stretch_2), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5043
5044 gtk_box_pack_start(GTK_BOX(mixer_simple), dt_ui_section_label_new(_("achromatic coupling")), FALSE, FALSE, 0);
5045
5046 g->simple_coupling_2 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5047 dt_bauhaus_widget_set_label(g->simple_coupling_2, N_("achromatic coupling hue"));
5048 dt_bauhaus_slider_set_factor(g->simple_coupling_2, 180.f);
5049 dt_bauhaus_slider_set_format(g->simple_coupling_2, "\302\260");
5050 gtk_widget_set_tooltip_text(g->simple_coupling_2, _("chroma direction, in the fixed chroma basis, that is coupled into the achromatic axis."));
5051 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_coupling_2), FALSE, FALSE, 0);
5052 g_signal_connect(G_OBJECT(g->simple_coupling_2), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5053
5054 g->simple_coupling_1 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 1.f, 0, 0, 3);
5055 dt_bauhaus_widget_set_label(g->simple_coupling_1, N_("achromatic coupling amount"));
5056 gtk_widget_set_tooltip_text(g->simple_coupling_1, _("strength of the chroma-to-achromatic coupling in the fixed chroma basis."));
5057 gtk_box_pack_start(GTK_BOX(mixer_simple), GTK_WIDGET(g->simple_coupling_1), FALSE, FALSE, 0);
5058 g_signal_connect(G_OBJECT(g->simple_coupling_1), "value-changed", G_CALLBACK(_channelmixerrgb_simple_slider_callback), self);
5059
5060 GtkWidget *mixer_primaries = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5061 gtk_stack_add_named(GTK_STACK(g->mixer_stack), mixer_primaries, "primaries");
5062
5063 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("achromatic axis")), FALSE, FALSE, 0);
5064
5065 g->primaries_achromatic_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -2.f, 2.f, 0, 0, 3);
5066 dt_bauhaus_widget_set_label(g->primaries_achromatic_hue, N_("white hue"));
5067 dt_bauhaus_slider_set_factor(g->primaries_achromatic_hue, 90.f);
5068 dt_bauhaus_slider_set_format(g->primaries_achromatic_hue, "\302\260");
5069 gtk_widget_set_tooltip_text(g->primaries_achromatic_hue,
5070 _("rotate the custom white vector around the D50 white of the current mixer basis."));
5071 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_achromatic_hue), FALSE, FALSE, 0);
5072 g_signal_connect(G_OBJECT(g->primaries_achromatic_hue), "value-changed",
5074
5075 g->primaries_achromatic_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 0, 3);
5076 dt_bauhaus_widget_set_label(g->primaries_achromatic_purity, N_("white purity"));
5077 gtk_widget_set_tooltip_text(g->primaries_achromatic_purity,
5078 _("distance of the custom white vector from the D50 white within the current mixer basis."));
5079 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_achromatic_purity), FALSE, FALSE, 0);
5080 g_signal_connect(G_OBJECT(g->primaries_achromatic_purity), "value-changed",
5082
5083 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("red primary")), FALSE, FALSE, 0);
5084
5085 g->primaries_red_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5086 dt_bauhaus_widget_set_label(g->primaries_red_hue, N_("red hue"));
5087 dt_bauhaus_slider_set_factor(g->primaries_red_hue, 90.f);
5088 dt_bauhaus_slider_set_format(g->primaries_red_hue, "\302\260");
5089 gtk_widget_set_tooltip_text(g->primaries_red_hue,
5090 _("rotate the first basis vector around the D50 white of the current mixer basis."));
5091 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_red_hue), FALSE, FALSE, 0);
5092 g_signal_connect(G_OBJECT(g->primaries_red_hue), "value-changed",
5094
5095 g->primaries_red_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
5096 dt_bauhaus_widget_set_label(g->primaries_red_purity, N_("red purity"));
5097 gtk_widget_set_tooltip_text(g->primaries_red_purity,
5098 _("radial scaling of the first basis vector inside the affine primaries footprint."));
5099 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_red_purity), FALSE, FALSE, 0);
5100 g_signal_connect(G_OBJECT(g->primaries_red_purity), "value-changed",
5102
5103 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("green primary")), FALSE, FALSE, 0);
5104
5105 g->primaries_green_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5106 dt_bauhaus_widget_set_label(g->primaries_green_hue, N_("green hue"));
5107 dt_bauhaus_slider_set_factor(g->primaries_green_hue, 90.f);
5108 dt_bauhaus_slider_set_format(g->primaries_green_hue, "\302\260");
5109 gtk_widget_set_tooltip_text(g->primaries_green_hue,
5110 _("rotate the second basis vector around the D50 white of the current mixer basis."));
5111 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_green_hue), FALSE, FALSE, 0);
5112 g_signal_connect(G_OBJECT(g->primaries_green_hue), "value-changed",
5114
5115 g->primaries_green_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
5116 dt_bauhaus_widget_set_label(g->primaries_green_purity, N_("green purity"));
5117 gtk_widget_set_tooltip_text(g->primaries_green_purity,
5118 _("radial scaling of the second basis vector inside the affine primaries footprint."));
5119 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_green_purity), FALSE, FALSE, 0);
5120 g_signal_connect(G_OBJECT(g->primaries_green_purity), "value-changed",
5122
5123 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("blue primary")), FALSE, FALSE, 0);
5124
5125 g->primaries_blue_hue = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0, 3);
5126 dt_bauhaus_widget_set_label(g->primaries_blue_hue, N_("blue hue"));
5127 dt_bauhaus_slider_set_factor(g->primaries_blue_hue, 90.f);
5128 dt_bauhaus_slider_set_format(g->primaries_blue_hue, "\302\260");
5129 gtk_widget_set_tooltip_text(g->primaries_blue_hue,
5130 _("rotate the third basis vector around the D50 white of the current mixer basis."));
5131 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_blue_hue), FALSE, FALSE, 0);
5132 g_signal_connect(G_OBJECT(g->primaries_blue_hue), "value-changed",
5134
5135 g->primaries_blue_purity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
5136 dt_bauhaus_widget_set_label(g->primaries_blue_purity, N_("blue purity"));
5137 gtk_widget_set_tooltip_text(g->primaries_blue_purity,
5138 _("radial scaling of the third basis vector inside the affine primaries footprint."));
5139 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_blue_purity), FALSE, FALSE, 0);
5140 g_signal_connect(G_OBJECT(g->primaries_blue_purity), "value-changed",
5142
5143 gtk_box_pack_start(GTK_BOX(mixer_primaries), dt_ui_section_label_new(_("gain correction")), FALSE, FALSE, 0);
5144
5145 g->primaries_gain = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -8.f, 8.f, 0, 1.f, 3);
5146 dt_bauhaus_widget_set_label(g->primaries_gain, N_("gain"));
5147 gtk_widget_set_tooltip_text(g->primaries_gain,
5148 _("global gain multiplying the custom white vector after the affine primaries transform."));
5149 gtk_box_pack_start(GTK_BOX(mixer_primaries), GTK_WIDGET(g->primaries_gain), FALSE, FALSE, 0);
5150 g_signal_connect(G_OBJECT(g->primaries_gain), "value-changed",
5152
5153 GtkWidget *outputs_page = dt_ui_notebook_page(g->notebook, N_("Outputs"),
5154 _("output colorfulness, brightness and B&W mixing"));
5155 self->widget = outputs_page;
5156
5157#define OUTPUT_SECTION(var, short, section, swap) \
5158 gtk_box_pack_start(GTK_BOX(outputs_page), dt_ui_section_label_new(section), FALSE, FALSE, 0); \
5159 \
5160 first = dt_bauhaus_slider_from_params(self, swap ? #var "[2]" : #var "[0]");\
5161 dt_bauhaus_slider_set_digits(first, 3); \
5162 dt_bauhaus_widget_set_label(first, N_("input R")); \
5163 \
5164 second = dt_bauhaus_slider_from_params(self, #var "[1]"); \
5165 dt_bauhaus_slider_set_digits(second, 3); \
5166 dt_bauhaus_widget_set_label(second, N_("input G")); \
5167 \
5168 third = dt_bauhaus_slider_from_params(self, swap ? #var "[0]" : #var "[2]");\
5169 dt_bauhaus_slider_set_digits(third, 3); \
5170 dt_bauhaus_widget_set_label(third, N_("input B")); \
5171 \
5172 g->scale_##var##_R = swap ? third : first; \
5173 g->scale_##var##_G = second; \
5174 g->scale_##var##_B = swap ? first : third; \
5175 \
5176 g->normalize_##short = dt_bauhaus_toggle_from_params(self, "normalize_" #short);
5177
5178 OUTPUT_SECTION(saturation, sat, _("colorfulness"), FALSE)
5179 g->saturation_version = dt_bauhaus_combobox_from_params(self, "version");
5180 OUTPUT_SECTION(lightness, light, _("brightness"), FALSE)
5181 OUTPUT_SECTION(grey, grey, _("B&W"), FALSE)
5182
5183 // start building top level widget
5184 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5185
5186 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
5187 const int saved_page = dt_conf_get_int("plugins/darkroom/channelmixerrgb/gui_page");
5188 const int active_page = saved_page > 2 ? 2 : CLAMP(saved_page, 0, 2);
5189 gtk_widget_show(gtk_notebook_get_nth_page(g->notebook, active_page));
5190 gtk_notebook_set_current_page(g->notebook, active_page);
5191
5192 // Add the color checker collapsible panel
5193
5195 (&g->cs,
5196 "plugins/darkroom/channelmixerrgb/expand_values",
5197 _("calibrate with a color checker"),
5198 GTK_BOX(self->widget), GTK_PACK_END);
5199
5200 gtk_widget_set_tooltip_text(g->cs.toggle,
5201 _("use a color checker target to autoset CAT and channels"));
5202 g_signal_connect(G_OBJECT(g->cs.toggle), "toggled", G_CALLBACK(start_profiling_callback), self);
5203
5204 GtkWidget *collapsible = GTK_WIDGET(g->cs.container);
5205
5206 gchar *tip_files_loc = NULL;
5207 {
5208 char confdir[PATH_MAX] = { 0 };
5209 dt_loc_get_user_config_dir(confdir, sizeof(confdir));
5210
5211 gchar *user_CGATS_dir = g_build_filename(confdir, "color", "checker", NULL);
5212 tip_files_loc = g_strdup_printf(_("'%s'"), user_CGATS_dir);
5213 dt_free(user_CGATS_dir);
5214 }
5215
5216 g->checkers_list = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
5217 dt_bauhaus_widget_set_label(g->checkers_list, N_("Chart"));
5218 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->checkers_list), TRUE, TRUE, 0);
5219 dt_bauhaus_combobox_set(g->checkers_list, 0);
5220 gchar *tooltip = g_strdup_printf(_("Choose the vendor and the type of your chart.\n"
5221 ".cht files in %s."), tip_files_loc);
5222 gtk_widget_set_tooltip_text(g->checkers_list, tooltip);
5224 g_signal_connect(G_OBJECT(g->checkers_list), "value-changed", G_CALLBACK(checker_changed_callback), (gpointer)self);
5225
5226 g->checkers_color_list = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
5227 dt_bauhaus_widget_set_label(g->checkers_color_list, N_("Chart color"));
5228 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->checkers_color_list), TRUE, TRUE, 0);
5229
5230 dt_bauhaus_combobox_set(g->checkers_color_list, 0);
5231 tooltip = g_strdup_printf(_("Choose the definition of your chart.\n"
5232 "CGATS.17 files in %s."), tip_files_loc);
5233 gtk_widget_set_tooltip_text(g->checkers_color_list, tooltip);
5235 g_signal_connect(G_OBJECT(g->checkers_color_list), "value-changed", G_CALLBACK(checker_color_changed_callback), (gpointer)self);
5236
5237 tooltip = g_markup_printf_escaped(_("<i>No CGATS.17 color file matches the selected chart.\n"
5238 "Add a compatible CGATS.17 file to <b>%s</b>.</i>"), tip_files_loc);
5239 g->checker_msg = gtk_label_new(NULL);
5240 gtk_label_set_markup(GTK_LABEL(g->checker_msg), tooltip);
5242 gtk_label_set_line_wrap(GTK_LABEL(g->checker_msg), TRUE);
5243 gtk_label_set_line_wrap_mode(GTK_LABEL(g->checker_msg), PANGO_WRAP_WORD);
5244 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->checker_msg), FALSE, TRUE, 0);
5245 gtk_widget_set_visible(g->checker_msg, FALSE);
5246 dt_free(tip_files_loc);
5247
5248
5249
5250 DT_BAUHAUS_COMBOBOX_NEW_FULL(darktable.bauhaus, g->optimize, DT_GUI_MODULE(self), N_("optimize for"),
5251 _("choose the colors that will be optimized with higher priority.\n"
5252 "neutral colors gives the lowest average delta E but a high maximum delta E\n"
5253 "saturated colors gives the lowest maximum delta E but a high average delta E\n"
5254 "none is a trade-off between both\n"
5255 "the others are special behaviours to protect some hues"),
5257 N_("none"),
5258 N_("neutral colors"),
5259 N_("saturated colors"),
5260 N_("skin and soil colors"),
5261 N_("foliage colors"),
5262 N_("sky and water colors"),
5263 N_("average delta E"),
5264 N_("maximum delta E"));
5265 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->optimize), TRUE, TRUE, 0);
5266
5268 dt_bauhaus_widget_set_label(g->safety, N_("patch scale"));
5269 gtk_widget_set_tooltip_text(g->safety, _("reduce the radius of the patches to select the more or less central part.\n"
5270 "useful when the perspective correction is sloppy or\n"
5271 "the patches frame cast a shadows on the edges of the patch." ));
5272 g_signal_connect(G_OBJECT(g->safety), "value-changed", G_CALLBACK(safety_changed_callback), self);
5273 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->safety), TRUE, TRUE, 0);
5274
5275 g->label_delta_E = dt_ui_label_new("");
5276 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(g->label_delta_E), TRUE, TRUE, 0);
5277 gtk_widget_set_tooltip_text(g->label_delta_E, _("the delta E is using the CIE 2000 formula"));
5278
5279 GtkWidget *toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
5280
5281 g->button_commit = dtgtk_button_new(dtgtk_cairo_paint_check_mark, 0, NULL);
5282 gtk_box_pack_end(GTK_BOX(toolbar), GTK_WIDGET(g->button_commit), FALSE, FALSE, 0);
5283 gtk_widget_set_tooltip_text(g->button_commit, _("accept the computed profile and set it in the module"));
5284 g_signal_connect(G_OBJECT(g->button_commit), "button-press-event", G_CALLBACK(commit_profile_callback), (gpointer)self);
5285
5286 g->button_profile = dtgtk_button_new(dtgtk_cairo_paint_refresh, 0, NULL);
5287 g_signal_connect(G_OBJECT(g->button_profile), "button-press-event", G_CALLBACK(run_profile_callback), (gpointer)self);
5288 gtk_widget_set_tooltip_text(g->button_profile, _("recompute the profile"));
5289 gtk_box_pack_end(GTK_BOX(toolbar), GTK_WIDGET(g->button_profile), FALSE, FALSE, 0);
5290
5291 g->button_validate = dtgtk_button_new(dtgtk_cairo_paint_softproof, 0, NULL);
5292 g_signal_connect(G_OBJECT(g->button_validate), "button-press-event", G_CALLBACK(run_validation_callback), (gpointer)self);
5293 gtk_widget_set_tooltip_text(g->button_validate, _("check the output delta E"));
5294 gtk_box_pack_end(GTK_BOX(toolbar), GTK_WIDGET(g->button_validate), FALSE, FALSE, 0);
5295
5296 gtk_box_pack_start(GTK_BOX(collapsible), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
5297}
5298
5300{
5303 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
5305
5307 dt_conf_set_int("plugins/darkroom/channelmixerrgb/gui_page", gtk_notebook_get_current_page (g->notebook));
5308
5309 if(g->delta_E_in)
5310 {
5311 dt_free_align(g->delta_E_in);
5312 g->delta_E_in = NULL;
5313 }
5314
5315 dt_free(g->delta_E_label_text);
5316
5317 dt_colorchecker_label_list_cleanup(&(g->colorcheckers));
5318 dt_colorchecker_label_list_cleanup(&(g->colorcheckers_all_color));
5319 g_list_free(g->colorcheckers_color); // data pointers are already freed by the cleanup function for colorcheckers_all_color
5320
5321 dt_colorchecker_cleanup(g->checker);
5322
5324}
5325
5326// clang-format off
5327// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
5328// vim: shiftwidth=2 expandtab tabstop=2 cindent
5329// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
5330// 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:1650
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3549
void dt_bauhaus_slider_set_hard_max(GtkWidget *widget, float val)
Definition bauhaus.c:1584
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:2192
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1643
const char * dt_bauhaus_combobox_get_entry(GtkWidget *widget, int pos)
Definition bauhaus.c:2202
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2375
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3486
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:1789
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2350
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1626
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:2158
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1552
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:2041
void dt_bauhaus_combobox_add_separator(GtkWidget *widget)
Definition bauhaus.c:2053
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3537
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2304
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1656
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:1783
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:2104
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1845
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3613
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2019
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3626
@ 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 __DT_CLONE_TARGETS__ int _custom_wb_from_coeffs(struct dt_iop_module_t *self, const dt_aligned_pixel_t wb_coeffs, dt_aligned_pixel_t custom_wb)
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:777
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:877
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:922
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:183
void * dt_alloc_align(size_t size)
Definition darktable.c:484
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define DT_ALIGNED_PIXEL
Definition darktable.h:401
#define dt_free_align(ptr)
Definition darktable.h:503
static void * dt_calloc_align(size_t size)
Definition darktable.h:510
@ DT_DEBUG_OPENCL
Definition darktable.h:744
@ DT_DEBUG_DEV
Definition darktable.h:739
#define dt_gui_freeze_begin()
Definition darktable.h:900
#define dt_gui_freeze_end()
Definition darktable.h:901
#define for_each_channel(_var,...)
Definition darktable.h:684
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:459
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:516
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:546
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __OMP_DECLARE_SIMD__(...)
Definition darktable.h:275
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
#define PATH_MAX
Definition darktable.h:1189
#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:293
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:42
#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:1783
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1197
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:1895
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:1056
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:3327
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:3334
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2470
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2465
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
Definition gtk.c:3312
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and relay every "switch_page" as DT_SIGNA...
Definition gtk.c:2500
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:322
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:469
#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:479
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
dt_iop_module_t * dt_iop_get_module_from_list(GList *iop_list, const char *op)
Definition imageop.c:3017
void dt_iop_throttled_history_update(gpointer data)
Definition imageop.c:3214
gboolean dt_iop_is_first_instance(GList *modules, dt_iop_module_t *module)
Definition imageop.c:3196
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:321
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2221
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:3220
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2989
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:226
#define dt_omploop_sfence()
Definition imageop.h:747
#define IOP_GUI_FREE
Definition imageop.h:641
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:450
#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:490
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:196
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:199
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:456
@ IOP_GROUP_COLOR
Definition imageop.h:169
#define IOP_GUI_ALLOC(module)
Definition imageop.h:638
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:2164
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2058
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2360
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2101
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:2155
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#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:387
@ 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:376
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
struct dt_develop_t * develop
Definition darktable.h:798
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:285
int32_t preview_height
Definition develop.h:213
dt_aligned_pixel_t wb_coeffs
Definition develop.h:448
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:444
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
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
struct dt_iop_buffer_dsc_t::@33 temperature
dt_aligned_pixel_t coeffs
Definition format.h:81
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:260
dt_iop_global_data_t * data
Definition imageop.h:263
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:294
GtkDarktableToggleButton * off
Definition imageop.h:376
dt_iop_params_t * default_params
Definition imageop.h:344
GtkWidget * widget
Definition imageop.h:374
struct dt_develop_t * dev
Definition imageop.h:333
dt_iop_gui_data_t * gui_data
Definition imageop.h:348
dt_iop_global_data_t * global_data
Definition imageop.h:351
gboolean enabled
Definition imageop.h:335
dt_aligned_pixel_t picked_color_min
Definition imageop.h:309
dt_aligned_pixel_t picked_color_max
Definition imageop.h:309
dt_aligned_pixel_t picked_color
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:344
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