Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
temperature.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2013, 2016 johannes hanika.
4 Copyright (C) 2010 Alexandre Prokoudine.
5 Copyright (C) 2010-2011 Bruce Guenter.
6 Copyright (C) 2010-2012 Henrik Andersson.
7 Copyright (C) 2010, 2012-2014 Pascal de Bruijn.
8 Copyright (C) 2010 Stuart Henderson.
9 Copyright (C) 2011 Antony Dovgal.
10 Copyright (C) 2011 Jérémy Rosen.
11 Copyright (C) 2011 Kanstantsin Shautsou.
12 Copyright (C) 2011 Olivier Tribout.
13 Copyright (C) 2011 Robert Bieber.
14 Copyright (C) 2011 Rostyslav Pidgornyi.
15 Copyright (C) 2011-2017, 2019 Tobias Ellinghaus.
16 Copyright (C) 2011-2012, 2014, 2016-2017 Ulrich Pegelow.
17 Copyright (C) 2012 Christian Tellefsen.
18 Copyright (C) 2012 Edouard Gomez.
19 Copyright (C) 2012 Richard Wonka.
20 Copyright (C) 2013, 2020, 2022 Aldric Renaudin.
21 Copyright (C) 2013, 2018-2022 Pascal Obry.
22 Copyright (C) 2014, 2018 Dan Torop.
23 Copyright (C) 2014-2016 Pedro Côrte-Real.
24 Copyright (C) 2014-2017, 2020 Roman Lebedev.
25 Copyright (C) 2017-2018 Matthieu Moy.
26 Copyright (C) 2018, 2020, 2022-2023, 2025-2026 Aurélien PIERRE.
27 Copyright (C) 2018-2019 Edgardo Hoszowski.
28 Copyright (C) 2018 Kelvie Wong.
29 Copyright (C) 2018 Maurizio Paglia.
30 Copyright (C) 2018 Peter Budai.
31 Copyright (C) 2018 rawfiner.
32 Copyright (C) 2019 Andreas Schneider.
33 Copyright (C) 2019-2022 Diederik Ter Rahe.
34 Copyright (C) 2019-2020, 2022 Hanno Schwalm.
35 Copyright (C) 2020-2021 Chris Elston.
36 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
37 Copyright (C) 2020-2021 Hubert Kowalski.
38 Copyright (C) 2020-2021 Ralf Brown.
39 Copyright (C) 2021 luzpaz.
40 Copyright (C) 2022 Martin Bařinka.
41 Copyright (C) 2022 Miloš Komarčević.
42 Copyright (C) 2022 Nicolas Auffray.
43 Copyright (C) 2022 Philipp Lutz.
44 Copyright (C) 2022 Victor Forsiuk.
45 Copyright (C) 2026 Guillaume Stutin.
46
47 darktable is free software: you can redistribute it and/or modify
48 it under the terms of the GNU General Public License as published by
49 the Free Software Foundation, either version 3 of the License, or
50 (at your option) any later version.
51
52 darktable is distributed in the hope that it will be useful,
53 but WITHOUT ANY WARRANTY; without even the implied warranty of
54 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55 GNU General Public License for more details.
56
57 You should have received a copy of the GNU General Public License
58 along with darktable. If not, see <http://www.gnu.org/licenses/>.
59*/
60#ifdef HAVE_CONFIG_H
61#include "config.h"
63#endif
64#include <assert.h>
65#include <lcms2.h>
66#include <math.h>
67#include <stdlib.h>
68#include <string.h>
69
70#include "widgets/bauhaus.h"
71#include "system/macros.h"
72#include "system/openmp.h"
74#include "system/mem_alloc.h"
75#include "system/simd.h"
76#include "common/logging.h"
78#include "common/opencl.h"
80#include "common/conf.h"
81#include "develop/develop.h"
82#include "develop/imageop_gui.h"
84
86#include "iop/iop_api.h"
87
88// for Kelvin temperature and bogus WB
90#include "external/cie_colorimetric_tables.c"
92#include "widgets/label.h"
95
97
98#define INITIALBLACKBODYTEMPERATURE 4000
99
100#define DT_IOP_LOWEST_TEMPERATURE 1901
101#define DT_IOP_HIGHEST_TEMPERATURE 25000
102
103#define DT_IOP_LOWEST_TINT 0.135
104#define DT_IOP_HIGHEST_TINT 2.326
105
106#define DT_IOP_NUM_OF_STD_TEMP_PRESETS 4
107
108// If you reorder presets combo, change this consts
109#define DT_IOP_TEMP_AS_SHOT 0
110#define DT_IOP_TEMP_SPOT 1
111#define DT_IOP_TEMP_USER 2
112#define DT_IOP_TEMP_D65 3
113
114static void gui_sliders_update(struct dt_iop_module_t *self);
115static int calculate_bogus_daylight_wb(dt_iop_module_t *module, double bwb[4]);
116static void prepare_matrices(dt_iop_module_t *module);
117static void find_coeffs(dt_iop_module_t *module, double coeffs[4]);
118
120{
121 float red; // $MIN: 0.0 $MAX: 8.0
122 float green; // $MIN: 0.0 $MAX: 8.0
123 float blue; // $MIN: 0.0 $MAX: 8.0
124 float g2; // $MIN: 0.0 $MAX: 8.0 $DESCRIPTION: "emerald"
126
150
155
162
169
170int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
171 void *new_params, const int new_version)
172{
173 if(old_version == 2 && new_version == 3)
174 {
175 typedef struct dt_iop_temperature_params_v2_t
176 {
177 float temp_out;
178 float coeffs[3];
179 } dt_iop_temperature_params_v2_t;
180
181 dt_iop_temperature_params_v2_t *o = (dt_iop_temperature_params_v2_t *)old_params;
183
184 n->red = o->coeffs[0];
185 n->green = o->coeffs[1];
186 n->blue = o->coeffs[2];
187 n->g2 = NAN;
188
189 return 0;
190 }
191 return 1;
192}
193
194static inline void _temp_params_from_array(dt_iop_temperature_params_t *p, const double a[4])
195{
196 p->red = a[0]; p->green = a[1]; p->blue = a[2]; p->g2 = a[3];
197}
198
199static inline void _temp_array_from_params(double a[4], const dt_iop_temperature_params_t *p)
200{
201 a[0] = p->red; a[1] = p->green; a[2] = p->blue; a[3] = p->g2;
202}
203
205{
206 // Ignore files that end with "-hdr.dng" since these are broken files we
207 // generated without any proper WB tagged
208 if(g_str_has_suffix(img->filename,"-hdr.dng"))
209 return TRUE;
210
211 static const char *const ignored_cameras[] = {
212 "Canon PowerShot A610",
213 "Canon PowerShot S3 IS",
214 "Canon PowerShot A620",
215 "Canon PowerShot A720 IS",
216 "Canon PowerShot A630",
217 "Canon PowerShot A640",
218 "Canon PowerShot A650",
219 "Canon PowerShot SX110 IS",
220 "Mamiya ZD",
221 "Canon EOS D2000C",
222 "Kodak EOS DCS 1",
223 "Kodak DCS560C",
224 "Kodak DCS460D",
225 "Nikon E5700",
226 "Sony DSC-F828",
227 "GITUP GIT2",
228 };
229
230 for(int i=0; i < sizeof(ignored_cameras)/sizeof(ignored_cameras[1]); i++)
231 if(!strcmp(img->camera_makermodel, ignored_cameras[i]))
232 return TRUE;
233
234 return FALSE;
235}
236
237
238const char *name()
239{
240 return C_("modulename", "white balance");
241}
242
243const char **description(struct dt_iop_module_t *self)
244{
245 return dt_iop_set_description(self, _("scale raw RGB channels to balance white and help demosaicing"),
246 _("corrective"),
247 _("linear, raw, scene-referred"),
248 _("linear, raw"),
249 _("linear, raw, scene-referred"));
250}
251
253{
254 return IOP_GROUP_TECHNICAL;
255}
256
261
263{
264 // This module may work in RAW or RGB (e.g. for TIFF files) depending on the input
265 // The module does not change the color space between the input and output, therefore implement it here
266 if(piece && piece->dsc_in.cst != IOP_CS_RAW)
267 return IOP_CS_RGB;
268 return IOP_CS_RAW;
269}
270
276
277/*
278 * Spectral power distribution functions
279 * https://en.wikipedia.org/wiki/Spectral_power_distribution
280 */
281typedef double((*spd)(unsigned long int wavelength, double TempK));
282
283/*
284 * Bruce Lindbloom, "Spectral Power Distribution of a Blackbody Radiator"
285 * http://www.brucelindbloom.com/Eqn_Blackbody.html
286 */
287static double spd_blackbody(unsigned long int wavelength, double TempK)
288{
289 // convert wavelength from nm to m
290 const long double lambda = (double)wavelength * 1e-9;
291
292/*
293 * these 2 constants were computed using following Sage code:
294 *
295 * (from http://physics.nist.gov/cgi-bin/cuu/Value?h)
296 * h = 6.62606957 * 10^-34 # Planck
297 * c= 299792458 # speed of light in vacuum
298 * k = 1.3806488 * 10^-23 # Boltzmann
299 *
300 * c_1 = 2 * pi * h * c^2
301 * c_2 = h * c / k
302 *
303 * print 'c_1 = ', c_1, ' ~= ', RealField(128)(c_1)
304 * print 'c_2 = ', c_2, ' ~= ', RealField(128)(c_2)
305 */
306
307#define c1 3.7417715246641281639549488324352159753e-16L
308#define c2 0.014387769599838156481252937624049081933L
309
310 return (double)(c1 / (powl(lambda, 5) * (expl(c2 / (lambda * TempK)) - 1.0L)));
311
312#undef c2
313#undef c1
314}
315
316/*
317 * Bruce Lindbloom, "Spectral Power Distribution of a CIE D-Illuminant"
318 * http://www.brucelindbloom.com/Eqn_DIlluminant.html
319 * and https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
320 */
321static double spd_daylight(unsigned long int wavelength, double TempK)
322{
323 cmsCIExyY WhitePoint = { 0.3127, 0.3290, 1.0 };
324
325 /*
326 * Bruce Lindbloom, "TempK to xy"
327 * http://www.brucelindbloom.com/Eqn_T_to_xy.html
328 */
329 cmsWhitePointFromTemp(&WhitePoint, TempK);
330
331 const double M = (0.0241 + 0.2562 * WhitePoint.x - 0.7341 * WhitePoint.y),
332 m1 = (-1.3515 - 1.7703 * WhitePoint.x + 5.9114 * WhitePoint.y) / M,
333 m2 = (0.0300 - 31.4424 * WhitePoint.x + 30.0717 * WhitePoint.y) / M;
334
335 const unsigned long int j
336 = ((wavelength - cie_daylight_components[0].wavelength)
337 / (cie_daylight_components[1].wavelength - cie_daylight_components[0].wavelength));
338
339 return (cie_daylight_components[j].S[0] + m1 * cie_daylight_components[j].S[1]
340 + m2 * cie_daylight_components[j].S[2]);
341}
342
343/*
344 * Bruce Lindbloom, "Computing XYZ From Spectral Data (Emissive Case)"
345 * http://www.brucelindbloom.com/Eqn_Spect_to_XYZ.html
346 */
347static cmsCIEXYZ spectrum_to_XYZ(double TempK, spd I)
348{
349 cmsCIEXYZ Source = {.X = 0.0, .Y = 0.0, .Z = 0.0 };
350
351 /*
352 * Color matching functions
353 * https://en.wikipedia.org/wiki/CIE_1931_color_space#Color_matching_functions
354 */
355 for(size_t i = 0; i < cie_1931_std_colorimetric_observer_count; i++)
356 {
357 const unsigned long int lambda = cie_1931_std_colorimetric_observer[0].wavelength
358 + (cie_1931_std_colorimetric_observer[1].wavelength
359 - cie_1931_std_colorimetric_observer[0].wavelength) * i;
360 const double P = I(lambda, TempK);
361 Source.X += P * cie_1931_std_colorimetric_observer[i].xyz.X;
362 Source.Y += P * cie_1931_std_colorimetric_observer[i].xyz.Y;
363 Source.Z += P * cie_1931_std_colorimetric_observer[i].xyz.Z;
364 }
365
366 // normalize so that each component is in [0.0, 1.0] range
367 const double _max = fmax(fmax(Source.X, Source.Y), Source.Z);
368 Source.X /= _max;
369 Source.Y /= _max;
370 Source.Z /= _max;
371
372 return Source;
373}
374
375// TODO: temperature and tint cannot be disjoined! (here it assumes no tint)
376static cmsCIEXYZ temperature_to_XYZ(double TempK)
377{
380
382 {
383 // if temperature is less than 4000K we use blackbody,
384 // because there will be no Daylight reference below 4000K...
385 return spectrum_to_XYZ(TempK, spd_blackbody);
386 }
387 else
388 {
389 return spectrum_to_XYZ(TempK, spd_daylight);
390 }
391}
392
393static cmsCIEXYZ temperature_tint_to_XYZ(double TempK, double tint)
394{
395 cmsCIEXYZ xyz = temperature_to_XYZ(TempK);
396
397 xyz.Y /= tint; // TODO: This is baaad!
398
399 return xyz;
400}
401
402// binary search inversion
403static void XYZ_to_temperature(cmsCIEXYZ XYZ, float *TempK, float *tint)
404{
405 double maxtemp = DT_IOP_HIGHEST_TEMPERATURE, mintemp = DT_IOP_LOWEST_TEMPERATURE;
406 cmsCIEXYZ _xyz;
407
408 for(*TempK = (maxtemp + mintemp) / 2.0; (maxtemp - mintemp) > 1.0; *TempK = (maxtemp + mintemp) / 2.0)
409 {
410 _xyz = temperature_to_XYZ(*TempK);
411 if(_xyz.Z / _xyz.X > XYZ.Z / XYZ.X)
412 maxtemp = *TempK;
413 else
414 mintemp = *TempK;
415 }
416
417 *tint = (_xyz.Y / _xyz.X) / (XYZ.Y / XYZ.X); // TODO: Fix this to move orthogonally to planckian locus
418
419
422 if(*tint < DT_IOP_LOWEST_TINT) *tint = DT_IOP_LOWEST_TINT;
423 if(*tint > DT_IOP_HIGHEST_TINT) *tint = DT_IOP_HIGHEST_TINT;
424}
425
426static void xyz2mul(dt_iop_module_t *self, cmsCIEXYZ xyz, double mul[4])
427{
429
430 double XYZ[3] = { xyz.X, xyz.Y, xyz.Z };
431
432 double CAM[4];
433 for(int k = 0; k < 4; k++)
434 {
435 CAM[k] = 0.0;
436 for(int i = 0; i < 3; i++)
437 {
438 CAM[k] += g->XYZ_to_CAM[k][i] * XYZ[i];
439 }
440 }
441
442 for(int k = 0; k < 4; k++) mul[k] = 1.0 / CAM[k];
443}
444
445static void temp2mul(dt_iop_module_t *self, double TempK, double tint, double mul[4])
446{
447 cmsCIEXYZ xyz = temperature_to_XYZ(TempK);
448
449 xyz.Y /= tint; // TODO: This is baaad!
458 xyz2mul(self, xyz, mul);
459}
460
462{
464
465 double CAM[4];
467 for(int k = 0; k < 4; k++) CAM[k] = CAM[k] > 0.0f ? 1.0 / CAM[k] : 0.0f;
468
469 double XYZ[3];
470 for(int k = 0; k < 3; k++)
471 {
472 XYZ[k] = 0.0;
473 for(int i = 0; i < 4; i++)
474 {
475 XYZ[k] += g->CAM_to_XYZ[k][i] * CAM[i];
476 }
477 }
478
479 return (cmsCIEXYZ){ XYZ[0], XYZ[1], XYZ[2] };
480}
481
482static void mul2temp(dt_iop_module_t *self, dt_iop_temperature_params_t *p, float *TempK, float *tint)
483{
484 XYZ_to_temperature(mul2xyz(self, p), TempK, tint);
485}
486
488int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
489 void *const ovoid)
490{
491 const dt_iop_roi_t *const roi_out = &piece->roi_out;
492 const uint32_t filters = piece->dsc_in.filters;
493 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
495 const int width = roi_out->width;
496 const int height = roi_out->height;
497
498 const float *const in = (const float *const)ivoid;
499 float *const out = (float *const)ovoid;
500 const float *const d_coeffs = d->coeffs;
501
502 if(filters == 9u)
503 { // xtrans float mosaiced
505 for(int j = 0; j < height; j++)
506 {
507 const size_t row_start = (size_t)j * width;
508 const float coeffs[12] =
509 {
510 d_coeffs[FCxtrans(j, 0, roi_out, xtrans)], d_coeffs[FCxtrans(j, 1, roi_out, xtrans)],
511 d_coeffs[FCxtrans(j, 2, roi_out, xtrans)], d_coeffs[FCxtrans(j, 3, roi_out, xtrans)],
512 d_coeffs[FCxtrans(j, 4, roi_out, xtrans)], d_coeffs[FCxtrans(j, 5, roi_out, xtrans)],
513 d_coeffs[FCxtrans(j, 6, roi_out, xtrans)], d_coeffs[FCxtrans(j, 7, roi_out, xtrans)],
514 d_coeffs[FCxtrans(j, 8, roi_out, xtrans)], d_coeffs[FCxtrans(j, 9, roi_out, xtrans)],
515 d_coeffs[FCxtrans(j, 10, roi_out, xtrans)], d_coeffs[FCxtrans(j, 11, roi_out, xtrans)],
516 };
517 /* Keep the 12-sensel X-Trans period explicit, but let the compiler pick
518 its own vectorization strategy on the scalar multiplies. */
519 int i = 0;
520 for(; i + 11 < width; i += 12)
521 {
522 const size_t p = row_start + i;
523 out[p + 0] = in[p + 0] * coeffs[0];
524 out[p + 1] = in[p + 1] * coeffs[1];
525 out[p + 2] = in[p + 2] * coeffs[2];
526 out[p + 3] = in[p + 3] * coeffs[3];
527 out[p + 4] = in[p + 4] * coeffs[4];
528 out[p + 5] = in[p + 5] * coeffs[5];
529 out[p + 6] = in[p + 6] * coeffs[6];
530 out[p + 7] = in[p + 7] * coeffs[7];
531 out[p + 8] = in[p + 8] * coeffs[8];
532 out[p + 9] = in[p + 9] * coeffs[9];
533 out[p + 10] = in[p + 10] * coeffs[10];
534 out[p + 11] = in[p + 11] * coeffs[11];
535 }
536 for(; i < width; i++)
537 {
538 const size_t p = row_start + i;
539 out[p] = in[p] * coeffs[i % 12];
540 }
541 }
542
543 }
544 else if(filters)
545 { // bayer float mosaiced
546 const int cfa_x = roi_out->x & 1;
548 for(int j = 0; j < height; j++)
549 {
550 const int offset_j = j + roi_out->y;
551 const size_t row_start = (size_t)j * width;
552 const int id0 = FC(offset_j, cfa_x + 0, filters);
553 const int id1 = FC(offset_j, cfa_x + 1, filters);
554 const float coeff0 = d_coeffs[id0];
555 const float coeff1 = d_coeffs[id1];
556 int i = 0;
557
558 for(; i + 1 < width; i += 2)
559 {
560 const size_t p = row_start + i;
561 out[p + 0] = in[p + 0] * coeff0;
562 out[p + 1] = in[p + 1] * coeff1;
563 }
564 for(; i < width; i++)
565 {
566 const size_t p = row_start + i;
567 out[p] = in[p] * d_coeffs[FC(offset_j, i + roi_out->x, filters)];
568 }
569 }
570
571 }
572 else
573 { // non-mosaiced
574 const size_t ch = piece->dsc_in.channels;
575 const size_t npixels = (size_t)roi_out->width * roi_out->height;
576
577 if(ch == 4)
578 {
580 for(size_t k = 0; k < npixels; k++)
581 {
582 const size_t p = 4 * k;
583 out[p + 0] = in[p + 0] * d->coeffs[0];
584 out[p + 1] = in[p + 1] * d->coeffs[1];
585 out[p + 2] = in[p + 2] * d->coeffs[2];
586 out[p + 3] = in[p + 3];
587 }
588
589 }
590 else
591 {
593 for(size_t k = 0; k < ch * npixels; k += ch)
594 {
595 for(ptrdiff_t c = 0; c < 3; c++)
596 {
597 const size_t p = k + c;
598 out[p] = in[p] * d->coeffs[c];
599 }
600 }
601
602 }
603
605 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
606 }
607
608 return 0;
609}
610
611#ifdef HAVE_OPENCL
612int 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)
613{
614 const dt_iop_roi_t *const roi_in = &piece->roi_in;
615 const dt_iop_roi_t *const roi_out = &piece->roi_out;
618
619 const int devid = pipe->devid;
620 const uint32_t filters = piece->dsc_in.filters;
621 cl_mem dev_coeffs = NULL;
622 cl_mem dev_xtrans = NULL;
623 cl_int err = -999;
624 int kernel = -1;
625
626 if(filters == 9u)
627 {
629 }
630 else if(filters)
631 {
633 }
634 else
635 {
637 }
638
639 if(filters == 9u)
640 {
641 dev_xtrans = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->dsc_in.xtrans), (void *)piece->dsc_in.xtrans);
642 if(IS_NULL_PTR(dev_xtrans)) goto error;
643 }
644
645 dev_coeffs = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3, d->coeffs);
646 if(IS_NULL_PTR(dev_coeffs)) goto error;
647
648 const int width = roi_in->width;
649 const int height = roi_in->height;
650
651 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
652 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
653 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
654 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
655 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
656 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_coeffs);
657 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(uint32_t), (void *)&filters);
658 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(uint32_t), (void *)&roi_out->x);
659 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(uint32_t), (void *)&roi_out->y);
660 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), (void *)&dev_xtrans);
661 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
662 if(err != CL_SUCCESS) goto error;
663
666
667 return TRUE;
668
669error:
672 dt_print(DT_DEBUG_OPENCL, "[opencl_white_balance] couldn't enqueue kernel! %d\n", err);
673 return FALSE;
674}
675#endif
676
677gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
678{
679 // History sanitization: white balance has nothing to balance on a true monochrome sensor, so a
680 // WB entry pasted onto a monochrome image is forced off here, at history-read time. For every
681 // other image (including non-raw, where manual WB is allowed) the user's state is preserved.
682 // This mirrors reload_defaults(), which hides the on/off button for monochrome images.
683 const gboolean mono = dt_image_is_monochrome(&self->dev->image_storage);
684 const gboolean state = current_state && !mono;
685 dt_iop_fmt_log(self, "force_enable: class=%s mono=%d current=%d -> %d",
687 mono, current_state, state);
688 return state;
689}
690
691// Publish the given WB multipliers into dev->proxy.wb_coeffs, the dev-wide value consumed by
692// channelmixerrgb's chromatic adaptation (get_white_balance_coeff()). dev->proxy is a GUI/main-thread
693// channel for inter-module communication; it MUST NOT be accessed from the pixel pipeline (neither
694// read nor write -- the pipeline only needs module params). It is published purely on the main
695// thread: reload_defaults() seeds the per-image default, commit_proxy() refreshes it once
696// history/presets are applied, and every GUI history commit re-publishes it (see
697// dt_dev_add_history_item_real / dt_dev_pop_history_items_ext).
698// (NOTE: channelmixerrgb still reads this from its process()/commit_params() for the live
699// DT_ILLUMINANT_CAMERA tracking -- that is a remaining violation of the rule, to be removed by
700// resolving the camera illuminant on the main thread into its params.)
702{
703 if(IS_NULL_PTR(self->dev)) return;
704 // Mirror the legacy commit_params() values exactly: g2 falls back to the first green when it is
705 // not a usable number on a non-4-colour sensor (NaN/0 would poison the G2 CFA sites downstream).
706 const gboolean g2_usable = isnormal(p->g2) || (self->dev->image_storage.flags & DT_IMAGE_4BAYER);
707 self->dev->proxy.wb_coeffs[0] = p->red;
708 self->dev->proxy.wb_coeffs[1] = p->green;
709 self->dev->proxy.wb_coeffs[2] = p->blue;
710 self->dev->proxy.wb_coeffs[3] = g2_usable ? p->g2 : p->green;
711}
712
713// Main-thread proxy commit, invoked by the history layer after params are applied (and before any
714// pipeline runs). Publishes the module's EFFECTIVE (post-history/preset) WB to dev->proxy.
719
722{
726
727 d->coeffs[0] = p->red;
728 d->coeffs[1] = p->green;
729 d->coeffs[2] = p->blue;
730 // Legacy-history / non-4-colour safety net: older edits may have stored g2 = NaN (the historical
731 // "usually NAN for RGB" second-green value, see find_coeffs). A NaN/0 multiplier would poison
732 // half the green CFA sites, so fall back to the first green when g2 isn't a usable number.
733 const gboolean g2_usable = isnormal(p->g2) || (self->dev->image_storage.flags & DT_IMAGE_4BAYER);
734 d->coeffs[3] = g2_usable ? p->g2 : p->green;
735 dt_iop_fmt_log(self, "commit: class=%s matrix_supported=%d coeffs=[%.4f %.4f %.4f %.4f] g2_in=%.4f g2_usable=%d -> enabled=%d",
738 d->coeffs[0], d->coeffs[1], d->coeffs[2], d->coeffs[3], p->g2, g2_usable, piece->enabled);
739 // NOTE: dev->proxy.wb_coeffs is intentionally NOT written here. dev->proxy is a GUI/main-thread
740 // inter-module channel and must not be touched from the pipeline; writing it from this
741 // pipeline-thread callback was a data race. It is now published on the main thread via
742 // commit_proxy() / reload_defaults().
743 piece->dsc_out.temperature.enabled = 1;
744 for(int k = 0; k < 4; k++)
745 {
746 piece->dsc_out.temperature.coeffs[k] = d->coeffs[k];
747 piece->dsc_out.processed_maximum[k] = piece->dsc_in.processed_maximum[k] * d->coeffs[k];
748 }
749
750 // 4Bayer images not implemented in OpenCL yet
751 if(self->dev->image_storage.flags & DT_IMAGE_4BAYER) piece->process_cl_ready = 0;
752
753 if(!IS_NULL_PTR(g))
754 {
755 // advertise on the pipe if coeffs are D65 for validity check
756 gboolean is_D65 = TRUE;
757 for(int c = 0; c < 3; c++)
758 if(d->coeffs[c] != (float)g->daylight_wb[c]) is_D65 = FALSE;
759
760 self->dev->proxy.wb_is_D65 = is_D65;
761 }
762}
763
769
771{
772 dt_free_align(piece->data);
773 piece->data = NULL;
774}
775
777{
779
780 const gboolean color_rgb = g->colored_sliders &&
782
787 dt_bauhaus_slider_set_feedback(g->scale_r, !color_rgb);
788 dt_bauhaus_slider_set_feedback(g->scale_g, !color_rgb);
789 dt_bauhaus_slider_set_feedback(g->scale_b, !color_rgb);
790 dt_bauhaus_slider_set_feedback(g->scale_g2, !color_rgb);
791
792 if(!color_rgb) return;
793
794 // there are 3 ways to do colored sliders: naive (independent 0->1), smart(er) (dependent 0->1) and real (coeff)
795
796 if(FALSE)
797 {
798 //naive:
799 dt_bauhaus_slider_set_stop(g->scale_r, 0.0, 0.0, 0.0, 0.0);
800 dt_bauhaus_slider_set_stop(g->scale_r, 1.0, 1.0, 0.0, 0.0);
801
802 dt_bauhaus_slider_set_stop(g->scale_g, 0.0, 0.0, 0.0, 0.0);
803 dt_bauhaus_slider_set_stop(g->scale_g, 1.0, 0.0, 1.0, 0.0);
804
805 dt_bauhaus_slider_set_stop(g->scale_b, 0.0, 0.0, 0.0, 0.0);
806 dt_bauhaus_slider_set_stop(g->scale_b, 1.0, 0.0, 0.0, 1.0);
807
808 dt_bauhaus_slider_set_stop(g->scale_g2, 0.0, 0.0, 0.0, 0.0);
809 dt_bauhaus_slider_set_stop(g->scale_g2, 1.0, 0.0, 1.0, 0.0);
810 }
811 if(!g->blackbody_is_confusing)
812 {
813 //smart(er) than naive
814 const float rchan = dt_bauhaus_slider_get(g->scale_r) / dt_bauhaus_slider_get_hard_max(g->scale_r);
815 const float gchan = dt_bauhaus_slider_get(g->scale_g) / dt_bauhaus_slider_get_hard_max(g->scale_g);
816 const float bchan = dt_bauhaus_slider_get(g->scale_b) / dt_bauhaus_slider_get_hard_max(g->scale_b);
817
818 dt_bauhaus_slider_set_stop(g->scale_r, 0.0, 0.0, gchan, bchan);
819 dt_bauhaus_slider_set_stop(g->scale_r, 1.0, 1.0, gchan, bchan);
820
821 dt_bauhaus_slider_set_stop(g->scale_g, 0.0, rchan, 0.0, bchan);
822 dt_bauhaus_slider_set_stop(g->scale_g, 1.0, rchan, 1.0, bchan);
823
824 dt_bauhaus_slider_set_stop(g->scale_b, 0.0, rchan, gchan, 0.0);
825 dt_bauhaus_slider_set_stop(g->scale_b, 1.0, rchan, gchan, 1.0);
826 }
827 else
828 {
829 //real (ish)
830 //we consider daylight wb to be "reference white"
831 const double white[3] = {
832 1.0/g->daylight_wb[0],
833 1.0/g->daylight_wb[1],
834 1.0/g->daylight_wb[2],
835 };
836
837 const float rchanmul = dt_bauhaus_slider_get(g->scale_r);
838 const float rchanmulmax = dt_bauhaus_slider_get_hard_max(g->scale_r);
839 const float gchanmul = dt_bauhaus_slider_get(g->scale_g);
840 const float gchanmulmax = dt_bauhaus_slider_get_hard_max(g->scale_g);
841 const float bchanmul = dt_bauhaus_slider_get(g->scale_b);
842 const float bchanmulmax = dt_bauhaus_slider_get_hard_max(g->scale_g);
843
844 dt_bauhaus_slider_set_stop(g->scale_r, 0.0, white[0]*0.0, white[1]*gchanmul, white[2]*bchanmul);
845 dt_bauhaus_slider_set_stop(g->scale_r, g->daylight_wb[0]/rchanmulmax, white[0]*g->daylight_wb[0], white[1]*gchanmul, white[2]*bchanmul);
846 dt_bauhaus_slider_set_stop(g->scale_r, 1.0, white[0]*1.0, white[1]*(gchanmul/gchanmulmax), white[2]*(bchanmul/bchanmulmax));
847
848 dt_bauhaus_slider_set_stop(g->scale_g, 0.0, white[0]*rchanmul, white[1]*0.0, white[2]*bchanmul);
849 dt_bauhaus_slider_set_stop(g->scale_g, g->daylight_wb[1]/bchanmulmax, white[0]*rchanmul, white[1]*g->daylight_wb[1], white[2]*bchanmul);
850 dt_bauhaus_slider_set_stop(g->scale_g, 1.0, white[0]*(rchanmul/rchanmulmax), white[1]*1.0, white[2]*(bchanmul/bchanmulmax));
851
852 dt_bauhaus_slider_set_stop(g->scale_b, 0.0, white[0]*rchanmul, white[1]*gchanmul, white[2]*0.0);
853 dt_bauhaus_slider_set_stop(g->scale_b, g->daylight_wb[2]/bchanmulmax, white[0]*rchanmul, white[1]*gchanmul, white[2]*g->daylight_wb[2]);
854 dt_bauhaus_slider_set_stop(g->scale_b, 1.0, white[0]*(rchanmul/rchanmulmax), white[1]*(gchanmul/gchanmulmax), white[2]*1.0);
855 }
856
857 if(gtk_widget_get_visible(GTK_WIDGET(g->scale_r)))
858 {
859 gtk_widget_queue_draw(GTK_WIDGET(g->scale_r));
860 gtk_widget_queue_draw(GTK_WIDGET(g->scale_g));
861 gtk_widget_queue_draw(GTK_WIDGET(g->scale_b));
862 }
863}
864
866{
868
870 dt_bauhaus_slider_clear_stops(g->scale_tint);
871 dt_bauhaus_slider_set_feedback(g->scale_k, !g->colored_sliders);
872 dt_bauhaus_slider_set_feedback(g->scale_tint, !g->colored_sliders);
873
874 if(!g->colored_sliders) return;
875
877 const double tint_step = (double)(DT_IOP_HIGHEST_TINT - DT_IOP_LOWEST_TINT) / (DT_BAUHAUS_SLIDER_MAX_STOPS - 1.0);
878 const int blackbody_is_confusing = g->blackbody_is_confusing;
879
880 const float cur_temp = dt_bauhaus_slider_get(g->scale_k);
881 const float cur_tint = dt_bauhaus_slider_get(g->scale_tint);
882
883 //we consider daylight wb to be "reference white"
884 const double dayligh_white[3] = {
885 1.0/g->daylight_wb[0],
886 1.0/g->daylight_wb[1],
887 1.0/g->daylight_wb[2],
888 };
889
890 double cur_coeffs[4] = {0.0};
891 temp2mul(self, cur_temp, 1.0, cur_coeffs);
892 const double cur_white[3] = {
893 1.0/cur_coeffs[0],
894 1.0/cur_coeffs[1],
895 1.0/cur_coeffs[2],
896 };
897
898 if(blackbody_is_confusing)
899 {
900 // show effect of adjustment on temp/tint sliders
901 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
902 {
903 const float stop = i / (DT_BAUHAUS_SLIDER_MAX_STOPS - 1.0);
904 const double K = DT_IOP_LOWEST_TEMPERATURE + i * temp_step;
905 const double tint = DT_IOP_LOWEST_TINT + i * tint_step;
906
907 double coeffs_K[4];
908 double coeffs_tint[4];
909 temp2mul(self, K, cur_tint, coeffs_K);
910 temp2mul(self, cur_temp, tint, coeffs_tint);
911 coeffs_K[0] /= coeffs_K[1];
912 coeffs_K[2] /= coeffs_K[1];
913 coeffs_K[3] /= coeffs_K[1];
914 coeffs_K[1] = 1.0;
915 coeffs_tint[0] /= coeffs_tint[1];
916 coeffs_tint[2] /= coeffs_tint[1];
917 coeffs_tint[3] /= coeffs_tint[1];
918 coeffs_tint[1] = 1.0;
919
920 dt_aligned_pixel_t sRGB_K = { dayligh_white[0]*coeffs_K[0], dayligh_white[1]*coeffs_K[1],
921 dayligh_white[2]*coeffs_K[2] };
922 dt_aligned_pixel_t sRGB_tint = {cur_white[0]*coeffs_tint[0], cur_white[1]*coeffs_tint[1],
923 cur_white[2]*coeffs_tint[2]};
924 const float maxsRGB_K = fmaxf(fmaxf(sRGB_K[0], sRGB_K[1]), sRGB_K[2]);
925 const float maxsRGB_tint = fmaxf(fmaxf(sRGB_tint[0], sRGB_tint[1]),sRGB_tint[2]);
926
927 if(maxsRGB_K > 1.f)
928 {
929 for(int ch=0; ch<3; ch++)
930 {
931 sRGB_K[ch] = fmaxf(sRGB_K[ch] / maxsRGB_K, 0.f);
932 }
933 }
934 if(maxsRGB_tint > 1.f)
935 {
936 for(int ch=0; ch<3; ch++)
937 {
938 sRGB_tint[ch] = fmaxf(sRGB_tint[ch] / maxsRGB_tint, 0.f);
939 }
940 }
941 dt_bauhaus_slider_set_stop(g->scale_k, stop, sRGB_K[0], sRGB_K[1], sRGB_K[2]);
942 dt_bauhaus_slider_set_stop(g->scale_tint, stop, sRGB_tint[0], sRGB_tint[1], sRGB_tint[2]);
943 }
944 }
945 else
946 {
947 // reflect actual black body colors for the temperature slider
948 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
949 {
950 const float stop = i / (DT_BAUHAUS_SLIDER_MAX_STOPS - 1.0);
951 const double K = DT_IOP_LOWEST_TEMPERATURE + i * temp_step;
952 const double tint = DT_IOP_LOWEST_TINT + i * tint_step;
953
954 const cmsCIEXYZ cmsXYZ_temp = temperature_tint_to_XYZ(K,cur_tint);
955 const cmsCIEXYZ cmsXYZ_tint = temperature_tint_to_XYZ(cur_temp, tint);
956 dt_aligned_pixel_t XYZ_temp = {cmsXYZ_temp.X, cmsXYZ_temp.Y, cmsXYZ_temp.Z};
957 dt_aligned_pixel_t XYZ_tint = {cmsXYZ_tint.X, cmsXYZ_tint.Y, cmsXYZ_tint.Z};
958 dt_aligned_pixel_t sRGB_temp;
959 dt_aligned_pixel_t sRGB_tint;
960
961 dt_XYZ_to_Rec709_D65(XYZ_temp, sRGB_temp);
962 dt_XYZ_to_Rec709_D65(XYZ_tint, sRGB_tint);
963
964 const float maxsRGB_temp = fmaxf(fmaxf(sRGB_temp[0], sRGB_temp[1]), sRGB_temp[2]);
965 const float maxsRGB_tint = fmaxf(fmaxf(sRGB_tint[0], sRGB_tint[1]), sRGB_tint[2]);
966
967 if(maxsRGB_temp > 1.f)
968 {
969 for(int ch=0; ch<3; ch++)
970 {
971 sRGB_temp[ch] = fmaxf(sRGB_temp[ch] / maxsRGB_temp, 0.f);
972 }
973 }
974
975 if(maxsRGB_tint > 1.f)
976 {
977 for(int ch=0; ch<3; ch++)
978 {
979 sRGB_tint[ch] = fmaxf(sRGB_tint[ch] / maxsRGB_tint, 0.f);
980 }
981 }
982
983 dt_bauhaus_slider_set_stop(g->scale_k, stop, sRGB_temp[0], sRGB_temp[1], sRGB_temp[2]);
984 dt_bauhaus_slider_set_stop(g->scale_tint, stop, sRGB_tint[0], sRGB_tint[1], sRGB_tint[2]);
985 }
986 }
987
988 if(gtk_widget_get_visible(GTK_WIDGET(g->scale_k)))
989 {
990 gtk_widget_queue_draw(GTK_WIDGET(g->scale_k));
991 gtk_widget_queue_draw(GTK_WIDGET(g->scale_tint));
992 }
993}
994
995
996void gui_update(struct dt_iop_module_t *self)
997{
1000
1001 const gboolean monochrome = dt_image_is_monochrome(&self->dev->image_storage);
1002 const gboolean is_raw = dt_image_is_matrix_correction_supported(&self->dev->image_storage);
1003 self->hide_enable_button = monochrome;
1004 self->default_enabled = is_raw;
1005 gtk_stack_set_visible_child_name(GTK_STACK(self->gui->widget), self->hide_enable_button ? "disabled" : "enabled");
1006
1007// if(self->hide_enable_button) return;
1008
1009 // Per-image WB references + widget defaults, formerly computed in reload_defaults(). These need
1010 // live widgets (bauhaus slider defaults, the presets combobox) and the GUI conversion matrices,
1011 // so they belong here on the GUI thread. Read the already-computed default_params; never recompute.
1013
1014 prepare_matrices(self);
1015
1016 dt_bauhaus_slider_set_default(g->scale_r, d->red);
1017 dt_bauhaus_slider_set_default(g->scale_g, d->green);
1018 dt_bauhaus_slider_set_default(g->scale_b, d->blue);
1019 dt_bauhaus_slider_set_default(g->scale_g2, d->g2);
1020
1021 // remember daylight wb used for temperature/tint conversion, assuming it corresponds to CIE D65
1022 _temp_array_from_params(g->daylight_wb, d);
1023 calculate_bogus_daylight_wb(self, g->daylight_wb);
1024
1025 // Store EXIF WB coeffs
1026 if(is_raw)
1027 find_coeffs(self, g->as_shot_wb);
1028 else
1029 g->as_shot_wb[0] = g->as_shot_wb[1] = g->as_shot_wb[2] = g->as_shot_wb[3] = 1.f;
1030
1031 g->as_shot_wb[0] /= g->as_shot_wb[1];
1032 g->as_shot_wb[2] /= g->as_shot_wb[1];
1033 g->as_shot_wb[3] /= g->as_shot_wb[1];
1034 g->as_shot_wb[1] = 1.0;
1035
1036 {
1037 float TempK_def, tint_def;
1038 mul2temp(self, d, &TempK_def, &tint_def);
1039 dt_bauhaus_slider_set_default(g->scale_k, TempK_def);
1040 dt_bauhaus_slider_set_default(g->scale_tint, tint_def);
1041 }
1042
1043 dt_bauhaus_combobox_clear(g->presets);
1044 dt_bauhaus_combobox_add(g->presets, C_("white balance", "as shot")); // old "camera". reason for change: all other RAW development tools use "As Shot" or "shot"
1045 dt_bauhaus_combobox_add(g->presets, C_("white balance", "from image area")); // old "spot", reason: describes exactly what'll happen
1046 dt_bauhaus_combobox_add(g->presets, C_("white balance", "user modified"));
1047 dt_bauhaus_combobox_add(g->presets, C_("white balance", "camera reference")); // old "camera neutral", reason: better matches intent
1048 g->preset_cnt = DT_IOP_NUM_OF_STD_TEMP_PRESETS;
1049 memset(g->preset_num, 0, sizeof(g->preset_num));
1050
1051 gui_sliders_update(self);
1052
1054
1055 float tempK, tint;
1056 mul2temp(self, p, &tempK, &tint);
1057
1058 dt_bauhaus_slider_set(g->scale_k, tempK);
1059 dt_bauhaus_slider_set(g->scale_tint, tint);
1060 dt_bauhaus_slider_set(g->scale_r, p->red);
1061 dt_bauhaus_slider_set(g->scale_g, p->green);
1062 dt_bauhaus_slider_set(g->scale_b, p->blue);
1063 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1064
1065 dt_bauhaus_combobox_set(g->presets, -1);
1066
1067 gboolean found = FALSE;
1068
1069 // is this a "as shot" white balance?
1070 if(p->red == g->as_shot_wb[0] && p->green == g->as_shot_wb[1] && p->blue == g->as_shot_wb[2])
1071 {
1073 found = TRUE;
1074 }
1075 else
1076 {
1077 // is this a "D65 white balance"?
1078 if((p->red == (float)g->daylight_wb[0]) &&
1079 (p->green == (float)g->daylight_wb[1]) &&
1080 (p->blue == (float)g->daylight_wb[2]))
1081 {
1083 found = TRUE;
1084 }
1085 }
1086
1087 if(!found)
1089
1090 if (!found || isnan(g->mod_temp)) // reset or initialize user-defined
1091 {
1092 g->mod_temp = tempK;
1093 g->mod_tint = tint;
1094 _temp_array_from_params(g->mod_coeff, p);
1095 }
1096
1097 gtk_widget_set_visible(g->buttonbar, g->button_bar_visible);
1098
1099 const int preset = dt_bauhaus_combobox_get(g->presets);
1100
1101 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_asshot), preset == DT_IOP_TEMP_AS_SHOT);
1102 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_user), preset == DT_IOP_TEMP_USER);
1103 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_d65), preset == DT_IOP_TEMP_D65);
1104
1106 color_rgb_sliders(self);
1107
1109
1110 gtk_widget_queue_draw(self->gui->widget);
1111}
1112
1113static int calculate_bogus_daylight_wb(dt_iop_module_t *module, double bwb[4])
1114{
1116 {
1117 bwb[0] = 1.0;
1118 bwb[2] = 1.0;
1119 bwb[1] = 1.0;
1120 bwb[3] = 1.0;
1121
1122 return 0;
1123 }
1124
1125 double mul[4];
1127 NULL, NULL,
1128 module->dev->image_storage.d65_color_matrix, mul))
1129 {
1130 // normalize green:
1131 bwb[0] = mul[0] / mul[1];
1132 bwb[2] = mul[2] / mul[1];
1133 bwb[1] = 1.0;
1134 bwb[3] = mul[3] / mul[1];
1135
1136 return 0;
1137 }
1138
1139 return 1;
1140}
1141
1143{
1144 // called exclusively from gui_update(): the gui struct exists by construction here,
1145 // and the NULL-tolerant accessor would only teach the analyzer a NULL that cannot be
1147
1148 // sRGB D65
1149 const double RGB_to_XYZ[3][4] = { { 0.4124564, 0.3575761, 0.1804375, 0 },
1150 { 0.2126729, 0.7151522, 0.0721750, 0 },
1151 { 0.0193339, 0.1191920, 0.9503041, 0 } };
1152
1153 // sRGB D65
1154 const double XYZ_to_RGB[4][3] = { { 3.2404542, -1.5371385, -0.4985314 },
1155 { -0.9692660, 1.8760108, 0.0415560 },
1156 { 0.0556434, -0.2040259, 1.0572252 },
1157 { 0, 0, 0 } };
1158
1160 {
1161 // let's just assume for now(TM) that if it has no raw colorimetry, it is sRGB.
1162 // Gate on the colorimetry axis (raw or sRAW), not on the mosaic-only "raw" flag, otherwise
1163 // an already-demosaiced raw (sRAW / linear DNG) would skip its camera matrix here (issue #729).
1164 memcpy(g->XYZ_to_CAM, XYZ_to_RGB, sizeof(g->XYZ_to_CAM));
1165 memcpy(g->CAM_to_XYZ, RGB_to_XYZ, sizeof(g->CAM_to_XYZ));
1166 return;
1167 }
1168
1171 g->XYZ_to_CAM, g->CAM_to_XYZ))
1172 {
1173 const char *camera = module->dev->image_storage.camera_makermodel;
1174 dt_control_alert(_("Camera data missing"), _("color matrix not found for image"), camera);
1175 }
1176}
1177
1178static void find_coeffs(dt_iop_module_t *module, double coeffs[4])
1179{
1180 const dt_image_t *img = &module->dev->image_storage;
1181
1182 // the raw should provide wb coeffs:
1183 int ok = 1;
1184 // Only check the first three values, the fourth is usually NAN for RGB
1185 const int num_coeffs = (img->flags & DT_IMAGE_4BAYER) ? 4 : 3;
1186 for(int k = 0; ok && k < num_coeffs; k++)
1187 {
1188 if(!isnormal(img->wb_coeffs[k]) || img->wb_coeffs[k] == 0.0f) ok = 0;
1189 }
1190 if(ok)
1191 {
1192 for(int k = 0; k < 4; k++) coeffs[k] = img->wb_coeffs[k];
1193 // wb_coeffs[3] is the SECOND green multiplier. For non-4-colour sensors (standard RGGB Bayer,
1194 // X-Trans) both green sites physically share the first green's multiplier, and many decoders
1195 // legitimately leave wb_coeffs[3] as NaN or 0 (see the "usually NAN for RGB" note above, and
1196 // the rawspeed DngDecoder which only fills 3 planes). Mirror coeffs[1] when the fourth value
1197 // is not a usable number, so we never propagate NaN into the G2 sites of the CFA.
1198 if(!(img->flags & DT_IMAGE_4BAYER) && !isnormal(coeffs[3])) coeffs[3] = coeffs[1];
1199 return;
1200 }
1201
1202 if(!ignore_missing_wb(&(module->dev->image_storage)))
1203 {
1204 // only display this if we have a sample, otherwise it is better to keep
1205 // on screen the more important message about missing sample and the way
1206 // to contribute.
1207 if(!img->camera_missing_sample)
1208 dt_control_alert(_("Camera data missing"), _("failed to read camera white balance information!"),
1209 img->filename);
1210 else
1211 fprintf(stderr, "[temperature] failed to read camera white balance information from `%s'!\n",
1212 img->filename);
1213 }
1214
1215 double bwb[4];
1216 if(!calculate_bogus_daylight_wb(module, bwb))
1217 {
1218 // found camera matrix and used it to calculate bogus daylight wb
1219 for(int c = 0; c < 4; c++) coeffs[c] = bwb[c];
1220 return;
1221 }
1222
1223 // did not find preset either?
1224 // final security net: hardcoded default that fits most cams.
1225 coeffs[0] = 2.0;
1226 coeffs[1] = 1.0;
1227 coeffs[2] = 1.5;
1228 coeffs[3] = 1.0;
1229}
1230
1232{
1233 dt_iop_temperature_params_t *d = module->default_params;
1234 *d = (dt_iop_temperature_params_t){ 1.0, 1.0, 1.0, 1.0 };
1235
1236 // we might be called from presets update infrastructure => there is no image
1237 if(!module->dev || module->dev->image_storage.id == -1) return;
1238
1239 const gboolean is_raw = dt_image_is_matrix_correction_supported(&module->dev->image_storage);
1240 const gboolean monochrome = dt_image_is_monochrome(&module->dev->image_storage);
1241
1242 // Note : this is not an user param anymore, and is set to modern by default
1243 // except for old histories using temperature without having an history entry for it.
1244 // see develop/develop.c/_dev_auto_apply_presets()
1245 const gboolean is_modern =
1246 dt_conf_is_equal("plugins/darkroom/chromatic-adaptation", "modern");
1247
1248 module->default_enabled = 0;
1249 module->hide_enable_button = 0;
1250
1251 // White balance module doesn't need to be enabled for monochrome raws (like
1252 // for leica monochrom cameras). prepare_matrices is a noop as well, as there
1253 // isn't a color matrix, so we can skip that as well.
1254 if(monochrome)
1255 {
1256 module->hide_enable_button = 1;
1257 }
1258 else
1259 {
1260 /* check if file is raw / hdr */
1261 if(is_raw)
1262 {
1263 // raw images need wb:
1264 module->default_enabled = 1;
1265
1266 // if workflow = modern, only set WB coeffs equivalent to D65 illuminant
1267 // full chromatic adaptation is deferred to channelmixerrgb
1268 double coeffs[4] = { 0 };
1269 if(is_modern && !calculate_bogus_daylight_wb(module, coeffs))
1270 {
1271 d->red = coeffs[0]/coeffs[1];
1272 d->blue = coeffs[2]/coeffs[1];
1273 d->g2 = coeffs[3]/coeffs[1];
1274 d->green = 1.0f;
1275 }
1276 else
1277 {
1278 // do best to find starting coeffs
1279 find_coeffs(module, coeffs);
1280 d->red = coeffs[0]/coeffs[1];
1281 d->blue = coeffs[2]/coeffs[1];
1282 d->g2 = coeffs[3]/coeffs[1];
1283 d->green = 1.0f;
1284 }
1285 }
1286 }
1287
1288 dt_iop_fmt_log(module, "reload_defaults: class=%s matrix_supported=%d mono=%d modern=%d -> default_enabled=%d coeffs=[%.4f %.4f %.4f %.4f]",
1290 is_raw, monochrome, is_modern, module->default_enabled, d->red, d->green, d->blue, d->g2);
1291
1292 // Seed the dev WB proxy with the per-image DEFAULT coeffs (main thread). channelmixerrgb derives
1293 // its own per-image defaults from dev->proxy.wb_coeffs, and reload_defaults() runs in iop order
1294 // (temperature before channelmixerrgb) at history init, so this baseline is visible to it. The
1295 // EFFECTIVE (history/preset) coeffs are published later by commit_proxy() once history is applied.
1296 // KNOWN GAP (no fix landed yet): on a user WB auto-preset, channelmixerrgb's default is derived
1297 // here against this pre-preset baseline, so a fresh init does not match a later manual reset.
1298 _publish_wb_coeffs(module, d);
1299
1300 // NOTE: widget state (slider/combo defaults, the daylight/as-shot WB references, the presets
1301 // combobox, the per-CFA slider labels) is intentionally NOT set here. reload_defaults() runs on
1302 // export/thumbnail devs with no widgets and off the GUI thread; touching bauhaus here re-entered
1303 // callbacks against half-built widgets and crashed. That work now lives in gui_update(), reading
1304 // these same default_params.
1305}
1306
1308{
1309 const int program = 2; // basic.cl, from programs.conf
1312 module->data = gd;
1313 gd->kernel_whitebalance_4f = dt_opencl_create_kernel(program, "whitebalance_4f");
1314 gd->kernel_whitebalance_1f = dt_opencl_create_kernel(program, "whitebalance_1f");
1315 gd->kernel_whitebalance_1f_xtrans = dt_opencl_create_kernel(program, "whitebalance_1f_xtrans");
1316}
1317
1326
1328{
1329 if(dt_gui_widgets_suppressed()) return;
1330
1333
1335
1336 g->mod_temp = dt_bauhaus_slider_get(g->scale_k);
1337 g->mod_tint = dt_bauhaus_slider_get(g->scale_tint);
1338
1339 temp2mul(self, g->mod_temp, g->mod_tint, g->mod_coeff);
1340
1341 // normalize
1342 g->mod_coeff[0] /= g->mod_coeff[1];
1343 g->mod_coeff[2] /= g->mod_coeff[1];
1344 g->mod_coeff[3] /= g->mod_coeff[1];
1345 g->mod_coeff[1] = 1.0;
1346
1347 _temp_params_from_array(p, g->mod_coeff);
1348
1350 dt_bauhaus_slider_set(g->scale_r, p->red);
1351 dt_bauhaus_slider_set(g->scale_g, p->green);
1352 dt_bauhaus_slider_set(g->scale_b, p->blue);
1353 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1356
1357 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1358}
1359
1360void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1361{
1364
1365 _temp_array_from_params(g->mod_coeff, p);
1366
1367 mul2temp(self, p, &g->mod_temp, &g->mod_tint);
1368
1370}
1371
1372static gboolean btn_toggled(GtkWidget *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1373{
1374 if(dt_gui_widgets_suppressed()) return TRUE;
1375
1377
1378 int preset = togglebutton == g->btn_asshot ? DT_IOP_TEMP_AS_SHOT :
1379 togglebutton == g->btn_d65 ? DT_IOP_TEMP_D65 :
1380 togglebutton == g->btn_user ? DT_IOP_TEMP_USER : 0;
1381
1382 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton)))
1383 {
1384 if(dt_bauhaus_combobox_get(g->presets) != preset)
1385 dt_bauhaus_combobox_set(g->presets, preset);
1386 }
1387 else if(dt_bauhaus_combobox_get(g->presets) == preset)
1388 {
1389 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton), TRUE);
1390 }
1391
1392 return TRUE;
1393}
1394
1396{
1397 if(dt_gui_widgets_suppressed()) return;
1398
1401
1402 const int pos = dt_bauhaus_combobox_get(g->presets);
1403
1404 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_asshot), pos == DT_IOP_TEMP_AS_SHOT);
1406 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_user), pos == DT_IOP_TEMP_USER);
1407 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_d65), pos == DT_IOP_TEMP_D65);
1408
1409 switch(pos)
1410 {
1411 case -1: // just un-setting.
1412 return;
1413 case DT_IOP_TEMP_AS_SHOT: // as shot wb
1414 _temp_params_from_array(p, g->as_shot_wb);
1415 break;
1416 case DT_IOP_TEMP_SPOT: // from image area wb, expose callback will set p->rgbg2.
1417 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->colorpicker)))
1418 {
1419 gboolean ret_val;
1420 g_signal_emit_by_name(G_OBJECT(g->colorpicker), "button-press-event", NULL, &ret_val);
1421 }
1422 break;
1423 case DT_IOP_TEMP_USER: // directly changing one of the coeff sliders also changes the mod_coeff so it can be read here
1424 _temp_params_from_array(p, g->mod_coeff);
1425 break;
1426 case DT_IOP_TEMP_D65: // camera reference d65
1427 _temp_params_from_array(p, g->daylight_wb);
1428 break;
1429 default: // camera WB presets
1430 break;
1431 }
1432
1433 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1434
1435 float TempK, tint;
1436
1437 if(pos == DT_IOP_TEMP_USER)
1438 {
1439 TempK = g->mod_temp;
1440 tint = g->mod_tint;
1441 }
1442 else
1443 {
1444 mul2temp(self, p, &TempK, &tint);
1445 }
1446
1448 dt_bauhaus_slider_set(g->scale_k, TempK);
1449 dt_bauhaus_slider_set(g->scale_tint, tint);
1450 dt_bauhaus_slider_set(g->scale_r, p->red);
1451 dt_bauhaus_slider_set(g->scale_g, p->green);
1452 dt_bauhaus_slider_set(g->scale_b, p->blue);
1453 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1455
1457 color_rgb_sliders(self);
1458
1459 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1460}
1461
1463{
1464 (void)picker;
1465 (void)pipe;
1466 (void)piece;
1467 if(dt_gui_widgets_suppressed()) return;
1468
1471
1472 // capture gui color picked event.
1473 if(self->picked_color_max[0] < self->picked_color_min[0]) return;
1474 const float *grayrgb = self->picked_color;
1475
1476 // normalize green:
1477 p->green = grayrgb[1] > 0.001f ? 1.0f / grayrgb[1] : 1.0f;
1478 p->red = fmaxf(0.0f, fminf(8.0f, (grayrgb[0] > 0.001f ? 1.0f / grayrgb[0] : 1.0f) / p->green));
1479 p->blue = fmaxf(0.0f, fminf(8.0f, (grayrgb[2] > 0.001f ? 1.0f / grayrgb[2] : 1.0f) / p->green));
1480 p->g2 = fmaxf(0.0f, fminf(8.0f, (grayrgb[3] > 0.001f ? 1.0f / grayrgb[3] : 1.0f) / p->green));
1481 p->green = 1.0;
1482
1485
1486 float tempK, tint;
1487 mul2temp(self, p, &tempK, &tint);
1488
1489 dt_bauhaus_slider_set(g->scale_k, tempK);
1490 dt_bauhaus_slider_set(g->scale_tint, tint);
1491 dt_bauhaus_slider_set(g->scale_r, p->red);
1492 dt_bauhaus_slider_set(g->scale_g, p->green);
1493 dt_bauhaus_slider_set(g->scale_b, p->blue);
1494 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1495
1496 dt_bauhaus_combobox_set(g->presets, -1);
1498
1499 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1500}
1501
1502
1503static void gui_sliders_update(struct dt_iop_module_t *self)
1504{
1505 const dt_image_t *img = &self->dev->image_storage;
1507
1508 if(FILTERS_ARE_CYGM(img->dsc.filters))
1509 {
1510 dt_bauhaus_widget_set_label(g->scale_r, N_("green"));
1511 gtk_widget_set_tooltip_text(g->scale_r, _("green channel coefficient"));
1512 dt_bauhaus_widget_set_label(g->scale_g, N_("magenta"));
1513 gtk_widget_set_tooltip_text(g->scale_g, _("magenta channel coefficient"));
1514 dt_bauhaus_widget_set_label(g->scale_b, N_("cyan"));
1515 gtk_widget_set_tooltip_text(g->scale_b, _("cyan channel coefficient"));
1516 dt_bauhaus_widget_set_label(g->scale_g2, N_("yellow"));
1517 gtk_widget_set_tooltip_text(g->scale_g2, _("yellow channel coefficient"));
1518
1519 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_b, 0);
1520 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g2, 1);
1521 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g, 2);
1522 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_r, 3);
1523 }
1524 else
1525 {
1526 dt_bauhaus_widget_set_label(g->scale_r, N_("red"));
1527 gtk_widget_set_tooltip_text(g->scale_r, _("red channel coefficient"));
1528 dt_bauhaus_widget_set_label(g->scale_g, N_("green"));
1529 gtk_widget_set_tooltip_text(g->scale_g, _("green channel coefficient"));
1530 dt_bauhaus_widget_set_label(g->scale_b, N_("blue"));
1531 gtk_widget_set_tooltip_text(g->scale_b, _("blue channel coefficient"));
1532 dt_bauhaus_widget_set_label(g->scale_g2, N_("emerald"));
1533 gtk_widget_set_tooltip_text(g->scale_g2, _("emerald channel coefficient"));
1534
1535 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_r, 0);
1536 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g, 1);
1537 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_b, 2);
1538 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g2, 3);
1539 }
1540
1541 gtk_widget_set_visible(GTK_WIDGET(g->scale_g2), (img->flags & DT_IMAGE_4BAYER));
1542}
1543
1544void gui_init(struct dt_iop_module_t *self)
1545{
1547
1548 g->colored_sliders = TRUE; // true if config != "no color"
1549 g->blackbody_is_confusing = FALSE; // true if config != "illuminant color"
1550
1551 const int feedback = TRUE;
1552 g->button_bar_visible = TRUE;
1553
1554 GtkBox *box_enabled = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
1555
1556 g->btn_asshot = dt_iop_togglebutton_new(self, N_("settings"), N_("as shot"), NULL,
1557 G_CALLBACK(btn_toggled), FALSE, 0, 0,
1559 gtk_widget_set_tooltip_text(g->btn_asshot, _("set white balance to as shot"));
1560
1561 // create color picker to be able to send its signal when spot selected,
1562 // this module may expect data in RAW or RGB, setting the color picker CST to IOP_CS_NONE will make the color
1563 // picker to depend on the number of color channels of the pixels. It is done like this as we may not know the
1564 // actual kind of data we are using in the GUI (it is part of the pipeline).
1567
1568 gtk_widget_set_tooltip_text(g->colorpicker, _("set white balance to detected from area"));
1569
1570 g->btn_user = dt_iop_togglebutton_new(self, N_("settings"), N_("user modified"), NULL,
1571 G_CALLBACK(btn_toggled), FALSE, 0, 0,
1573 gtk_widget_set_tooltip_text(g->btn_user, _("set white balance to user modified"));
1574
1575
1576 g->btn_d65 = dt_iop_togglebutton_new(self, N_("settings"), N_("camera reference"), NULL,
1577 G_CALLBACK(btn_toggled), FALSE, 0, 0,
1579 gtk_widget_set_tooltip_text(g->btn_d65, _("set white balance to camera reference point\nin most cases it should be D65"));
1580
1581 g->buttonbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING); // put buttons at top. fill later.
1582 dt_gui_add_class(g->buttonbar, "dt_iop_toggle");
1583 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->btn_d65, TRUE, TRUE, 0);
1584 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->btn_user, TRUE, TRUE, 0);
1585 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->colorpicker, TRUE, TRUE, 0);
1586 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->btn_asshot, TRUE, TRUE, 0);
1587 gtk_box_pack_start(box_enabled, g->buttonbar, TRUE, TRUE, 0);
1588
1590 dt_bauhaus_widget_set_label(g->presets, N_("settings")); // relabel to settings to remove confusion between module presets and white balance settings
1591 gtk_widget_set_tooltip_text(g->presets, _("choose white balance setting"));
1592 gtk_box_pack_start(box_enabled, g->presets, TRUE, TRUE, 0);
1593
1594 g->mod_temp = NAN;
1595 for(int k = 0; k < 4; k++)
1596 {
1597 g->daylight_wb[k] = 1.0;
1598 g->as_shot_wb[k] = 1.f;
1599 }
1600
1601 GtkWidget *temp_label_box = gtk_event_box_new();
1602 g->temp_label = dt_ui_section_label_new(_("scene illuminant temp"));
1603 gtk_widget_set_tooltip_text(g->temp_label, _("click to cycle color mode on sliders"));
1604 gtk_container_add(GTK_CONTAINER(temp_label_box), g->temp_label);
1605
1606 gtk_box_pack_start(box_enabled, temp_label_box, TRUE, TRUE, 0);
1607
1608 //Match UI order: temp first, then tint (like every other app ever)
1610 0, 5000.0, 0, feedback);
1611 dt_bauhaus_slider_set_format(g->scale_k, " K");
1612 dt_bauhaus_widget_set_label(g->scale_k, N_("temperature"));
1613 gtk_widget_set_tooltip_text(g->scale_k, _("color temperature (in Kelvin)"));
1614 gtk_box_pack_start(box_enabled, g->scale_k, TRUE, TRUE, 0);
1615
1617 0, 1.0, 3, feedback);
1618 dt_bauhaus_widget_set_label(g->scale_tint, N_("tint"));
1619 gtk_widget_set_tooltip_text(g->scale_tint, _("color tint of the image, from magenta (value < 1) to green (value > 1)"));
1620 gtk_box_pack_start(box_enabled, g->scale_tint, TRUE, TRUE, 0);
1621
1623 (&g->cs,
1624 "plugins/darkroom/temperature/expand_coefficients",
1625 _("channel coefficients"),
1626 GTK_BOX(box_enabled), GTK_PACK_END);
1627
1628 self->gui->widget = GTK_WIDGET(g->cs.container);
1629
1630 g->scale_r = dt_bauhaus_slider_from_params(self, N_("red"));
1631 g->scale_g = dt_bauhaus_slider_from_params(self, N_("green"));
1632 g->scale_b = dt_bauhaus_slider_from_params(self, N_("blue"));
1633 g->scale_g2 = dt_bauhaus_slider_from_params(self, "g2");
1634 dt_bauhaus_slider_set_digits(g->scale_r, 3);
1635 dt_bauhaus_slider_set_digits(g->scale_g, 3);
1636 dt_bauhaus_slider_set_digits(g->scale_b, 3);
1637 dt_bauhaus_slider_set_digits(g->scale_g2, 3);
1638
1639 gtk_widget_set_no_show_all(g->scale_g2, TRUE);
1640
1641 g_signal_connect(G_OBJECT(g->scale_k), "value-changed", G_CALLBACK(temp_tint_callback), self);
1642 g_signal_connect(G_OBJECT(g->scale_tint), "value-changed", G_CALLBACK(temp_tint_callback), self);
1643
1644 g_signal_connect(G_OBJECT(g->presets), "value-changed", G_CALLBACK(preset_tune_callback), self);
1645
1646 // start building top level widget
1647 self->gui->widget = gtk_stack_new();
1648 gtk_stack_set_homogeneous(GTK_STACK(self->gui->widget), FALSE);
1649
1650 GtkWidget *label_disabled = gtk_label_new(_("white balance disabled for camera"));
1651 gtk_widget_set_halign(label_disabled, GTK_ALIGN_START);
1652 gtk_label_set_ellipsize(GTK_LABEL(label_disabled), PANGO_ELLIPSIZE_END);
1653
1654 gtk_stack_add_named(GTK_STACK(self->gui->widget), GTK_WIDGET(box_enabled), "enabled");
1655 gtk_stack_add_named(GTK_STACK(self->gui->widget), label_disabled, "disabled");
1656}
1657
1659{
1661
1663}
1664
1665void gui_reset(struct dt_iop_module_t *self)
1666{
1668
1669 const int preset = dt_bauhaus_combobox_get(g->presets);
1671
1672 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_asshot), preset == DT_IOP_TEMP_AS_SHOT);
1673 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_user), preset == DT_IOP_TEMP_USER);
1674 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_d65), preset == DT_IOP_TEMP_D65);
1675
1676 color_rgb_sliders(self);
1678}
1679
1680// clang-format off
1681// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1682// vim: shiftwidth=2 expandtab tabstop=2 cindent
1683// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1684// 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_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3346
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:2000
void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
Definition bauhaus.c:2156
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1491
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2164
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3283
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:1641
void dt_bauhaus_slider_set_feedback(GtkWidget *widget, int feedback)
Definition bauhaus.c:3392
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2139
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1701
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
float dt_bauhaus_slider_get_hard_max(GtkWidget *widget)
Definition bauhaus.c:1450
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1827
#define DT_BAUHAUS_SLIDER_MAX_STOPS
Definition bauhaus.h:73
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
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.
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
@ IOP_CS_RAW
@ IOP_CS_RGB
@ IOP_CS_NONE
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new_with_cst(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w, const dt_iop_colorspace_type_t cst)
@ DT_COLOR_PICKER_AREA
__DT_CLONE_TARGETS__ int dt_colorspaces_conversion_matrices_rgb(const float *adobe_XYZ_to_CAM, double(*out_RGB_to_CAM)[3], double(*out_CAM_to_RGB)[4], const float *embedded_matrix, double *mul)
Compute the sRGB->camera and camera->sRGB matrices, and the default white balance multipliers.
int dt_colorspaces_conversion_matrices_xyz(const float adobe_XYZ_to_CAM[4][3], float in_XYZ_to_CAM[9], double XYZ_to_CAM[4][3], double CAM_to_XYZ[3][4])
Compute the XYZ->camera and camera->XYZ matrices for an image.
The colour-profile module's API: which profiles exist, and how to apply one.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t XYZ
const dt_colormatrix_t dt_aligned_pixel_t out
static const dt_colormatrix_t M
gboolean dt_conf_is_equal(const char *name, const char *value)
#define P(V, params)
#define S(V, params)
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)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
void dt_control_alert(const char *title, const char *message, const char *item)
Tell the user something that must not go unseen: what it says stays true after a toast would have fad...
Definition control.c:883
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
static int FCxtrans(const int row, const int col, global const unsigned char(*const xtrans)[6])
static int FC(const int row, const int col, const unsigned int filters)
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
void default_output_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
#define DT_GUI_MODULE(x)
@ DT_IMAGE_4BAYER
Definition image.h:149
#define FILTERS_ARE_CYGM(filters)
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:1909
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:215
#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:535
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_GROUP_TECHNICAL
Definition imageop.h:162
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
static float kernel(const float *x, const float *y)
struct dt_iop_tonecurve_params_t preset
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define o
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2808
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
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:2603
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
dt_image_t image_storage
Definition develop.h:225
gboolean wb_is_D65
Definition develop.h:427
dt_aligned_pixel_t wb_coeffs
Definition develop.h:428
struct dt_develop_t::@14 proxy
gboolean camera_missing_sample
Definition image.h:393
char camera_makermodel[128]
Definition image.h:391
int32_t flags
Definition image.h:410
float d65_color_matrix[9]
Definition image.h:430
dt_iop_buffer_dsc_t dsc
Definition image.h:428
float adobe_XYZ_to_CAM[4][3]
Definition image.h:458
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
int32_t id
Definition image.h:410
dt_aligned_pixel_t wb_coeffs
Definition image.h:455
uint32_t filters
Definition format.h:89
dt_iop_gui_data_t * gui_data
Definition imageop_gui.h:42
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:272
int32_t hide_enable_button
Definition imageop.h:270
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
gboolean default_enabled
Definition imageop.h:318
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
dt_gui_collapsible_section_t cs
#define __DT_CLONE_TARGETS__
#define DT_IOP_HIGHEST_TINT
static cmsCIEXYZ temperature_tint_to_XYZ(double TempK, double tint)
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)
static double spd_blackbody(unsigned long int wavelength, double TempK)
const char ** description(struct dt_iop_module_t *self)
int default_group()
static cmsCIEXYZ spectrum_to_XYZ(double TempK, spd I)
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
static cmsCIEXYZ mul2xyz(dt_iop_module_t *self, const dt_iop_temperature_params_t *p)
static void _publish_wb_coeffs(dt_iop_module_t *self, const dt_iop_temperature_params_t *p)
void reload_defaults(dt_iop_module_t *module)
static double spd_daylight(unsigned long int wavelength, double TempK)
#define INITIALBLACKBODYTEMPERATURE
Definition temperature.c:98
static void temp2mul(dt_iop_module_t *self, double TempK, double tint, double mul[4])
#define DT_IOP_LOWEST_TINT
void color_rgb_sliders(struct dt_iop_module_t *self)
static void gui_sliders_update(struct dt_iop_module_t *self)
static void XYZ_to_temperature(cmsCIEXYZ XYZ, float *TempK, float *tint)
#define DT_IOP_LOWEST_TEMPERATURE
static int ignore_missing_wb(dt_image_t *img)
static int calculate_bogus_daylight_wb(dt_iop_module_t *module, double bwb[4])
static cmsCIEXYZ temperature_to_XYZ(double TempK)
static void _temp_array_from_params(double a[4], const dt_iop_temperature_params_t *p)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
const char * name()
void output_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
void gui_reset(struct dt_iop_module_t *self)
#define DT_IOP_HIGHEST_TEMPERATURE
static gboolean btn_toggled(GtkWidget *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
void gui_init(struct dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
#define DT_IOP_TEMP_D65
#define DT_IOP_TEMP_AS_SHOT
void cleanup_global(dt_iop_module_so_t *module)
void commit_proxy(dt_iop_module_t *self)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
static void _temp_params_from_array(dt_iop_temperature_params_t *p, const double a[4])
static void mul2temp(dt_iop_module_t *self, dt_iop_temperature_params_t *p, float *TempK, float *tint)
static void temp_tint_callback(GtkWidget *slider, dt_iop_module_t *self)
void gui_cleanup(struct dt_iop_module_t *self)
typedef double((*spd)(unsigned long int wavelength, double TempK))
gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
static void find_coeffs(dt_iop_module_t *module, double coeffs[4])
#define DT_IOP_NUM_OF_STD_TEMP_PRESETS
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void prepare_matrices(dt_iop_module_t *module)
void init_global(dt_iop_module_so_t *module)
#define DT_IOP_TEMP_SPOT
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)
static void xyz2mul(dt_iop_module_t *self, cmsCIEXYZ xyz, double mul[4])
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void color_temptint_sliders(struct dt_iop_module_t *self)
static void preset_tune_callback(GtkWidget *widget, dt_iop_module_t *self)
#define DT_IOP_TEMP_USER
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 int _max(int a, int b)
Definition tiling.c:88
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)
Telling the user something happened.
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_bulb(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_camera(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_drawn(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)