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
46 darktable is free software: you can redistribute it and/or modify
47 it under the terms of the GNU General Public License as published by
48 the Free Software Foundation, either version 3 of the License, or
49 (at your option) any later version.
50
51 darktable is distributed in the hope that it will be useful,
52 but WITHOUT ANY WARRANTY; without even the implied warranty of
53 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54 GNU General Public License for more details.
55
56 You should have received a copy of the GNU General Public License
57 along with darktable. If not, see <http://www.gnu.org/licenses/>.
58*/
59#ifdef HAVE_CONFIG_H
60#include "config.h"
62#endif
63#include <assert.h>
64#include <lcms2.h>
65#include <math.h>
66#include <stdlib.h>
67#include <string.h>
68
69#include "widgets/bauhaus.h"
70#include "system/macros.h"
71#include "system/openmp.h"
73#include "system/mem_alloc.h"
74#include "system/simd.h"
75#include "common/logging.h"
77#include "common/opencl.h"
79#include "common/conf.h"
80#include "develop/develop.h"
81#include "develop/imageop_gui.h"
83
85#include "iop/iop_api.h"
86
87// for Kelvin temperature and bogus WB
89#include "external/cie_colorimetric_tables.c"
91#include "widgets/label.h"
94
96
97#define INITIALBLACKBODYTEMPERATURE 4000
98
99#define DT_IOP_LOWEST_TEMPERATURE 1901
100#define DT_IOP_HIGHEST_TEMPERATURE 25000
101
102#define DT_IOP_LOWEST_TINT 0.135
103#define DT_IOP_HIGHEST_TINT 2.326
104
105#define DT_IOP_NUM_OF_STD_TEMP_PRESETS 4
106
107// If you reorder presets combo, change this consts
108#define DT_IOP_TEMP_AS_SHOT 0
109#define DT_IOP_TEMP_SPOT 1
110#define DT_IOP_TEMP_USER 2
111#define DT_IOP_TEMP_D65 3
112
113static void gui_sliders_update(struct dt_iop_module_t *self);
114static int calculate_bogus_daylight_wb(dt_iop_module_t *module, double bwb[4]);
115static void prepare_matrices(dt_iop_module_t *module);
116static void find_coeffs(dt_iop_module_t *module, double coeffs[4]);
117
119{
120 float red; // $MIN: 0.0 $MAX: 8.0
121 float green; // $MIN: 0.0 $MAX: 8.0
122 float blue; // $MIN: 0.0 $MAX: 8.0
123 float g2; // $MIN: 0.0 $MAX: 8.0 $DESCRIPTION: "emerald"
125
149
154
161
168
169int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
170 void *new_params, const int new_version)
171{
172 if(old_version == 2 && new_version == 3)
173 {
174 typedef struct dt_iop_temperature_params_v2_t
175 {
176 float temp_out;
177 float coeffs[3];
178 } dt_iop_temperature_params_v2_t;
179
180 dt_iop_temperature_params_v2_t *o = (dt_iop_temperature_params_v2_t *)old_params;
182
183 n->red = o->coeffs[0];
184 n->green = o->coeffs[1];
185 n->blue = o->coeffs[2];
186 n->g2 = NAN;
187
188 return 0;
189 }
190 return 1;
191}
192
193static inline void _temp_params_from_array(dt_iop_temperature_params_t *p, const double a[4])
194{
195 p->red = a[0]; p->green = a[1]; p->blue = a[2]; p->g2 = a[3];
196}
197
198static inline void _temp_array_from_params(double a[4], const dt_iop_temperature_params_t *p)
199{
200 a[0] = p->red; a[1] = p->green; a[2] = p->blue; a[3] = p->g2;
201}
202
204{
205 // Ignore files that end with "-hdr.dng" since these are broken files we
206 // generated without any proper WB tagged
207 if(g_str_has_suffix(img->filename,"-hdr.dng"))
208 return TRUE;
209
210 static const char *const ignored_cameras[] = {
211 "Canon PowerShot A610",
212 "Canon PowerShot S3 IS",
213 "Canon PowerShot A620",
214 "Canon PowerShot A720 IS",
215 "Canon PowerShot A630",
216 "Canon PowerShot A640",
217 "Canon PowerShot A650",
218 "Canon PowerShot SX110 IS",
219 "Mamiya ZD",
220 "Canon EOS D2000C",
221 "Kodak EOS DCS 1",
222 "Kodak DCS560C",
223 "Kodak DCS460D",
224 "Nikon E5700",
225 "Sony DSC-F828",
226 "GITUP GIT2",
227 };
228
229 for(int i=0; i < sizeof(ignored_cameras)/sizeof(ignored_cameras[1]); i++)
230 if(!strcmp(img->camera_makermodel, ignored_cameras[i]))
231 return TRUE;
232
233 return FALSE;
234}
235
236
237const char *name()
238{
239 return C_("modulename", "white balance");
240}
241
242const char **description(struct dt_iop_module_t *self)
243{
244 return dt_iop_set_description(self, _("scale raw RGB channels to balance white and help demosaicing"),
245 _("corrective"),
246 _("linear, raw, scene-referred"),
247 _("linear, raw"),
248 _("linear, raw, scene-referred"));
249}
250
252{
253 return IOP_GROUP_TECHNICAL;
254}
255
260
262{
263 // This module may work in RAW or RGB (e.g. for TIFF files) depending on the input
264 // The module does not change the color space between the input and output, therefore implement it here
265 if(piece && piece->dsc_in.cst != IOP_CS_RAW)
266 return IOP_CS_RGB;
267 return IOP_CS_RAW;
268}
269
272{
273 default_output_format(self, pipe, piece, dsc);
274}
275
276/*
277 * Spectral power distribution functions
278 * https://en.wikipedia.org/wiki/Spectral_power_distribution
279 */
280typedef double((*spd)(unsigned long int wavelength, double TempK));
281
282/*
283 * Bruce Lindbloom, "Spectral Power Distribution of a Blackbody Radiator"
284 * http://www.brucelindbloom.com/Eqn_Blackbody.html
285 */
286static double spd_blackbody(unsigned long int wavelength, double TempK)
287{
288 // convert wavelength from nm to m
289 const long double lambda = (double)wavelength * 1e-9;
290
291/*
292 * these 2 constants were computed using following Sage code:
293 *
294 * (from http://physics.nist.gov/cgi-bin/cuu/Value?h)
295 * h = 6.62606957 * 10^-34 # Planck
296 * c= 299792458 # speed of light in vacuum
297 * k = 1.3806488 * 10^-23 # Boltzmann
298 *
299 * c_1 = 2 * pi * h * c^2
300 * c_2 = h * c / k
301 *
302 * print 'c_1 = ', c_1, ' ~= ', RealField(128)(c_1)
303 * print 'c_2 = ', c_2, ' ~= ', RealField(128)(c_2)
304 */
305
306#define c1 3.7417715246641281639549488324352159753e-16L
307#define c2 0.014387769599838156481252937624049081933L
308
309 return (double)(c1 / (powl(lambda, 5) * (expl(c2 / (lambda * TempK)) - 1.0L)));
310
311#undef c2
312#undef c1
313}
314
315/*
316 * Bruce Lindbloom, "Spectral Power Distribution of a CIE D-Illuminant"
317 * http://www.brucelindbloom.com/Eqn_DIlluminant.html
318 * and https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
319 */
320static double spd_daylight(unsigned long int wavelength, double TempK)
321{
322 cmsCIExyY WhitePoint = { 0.3127, 0.3290, 1.0 };
323
324 /*
325 * Bruce Lindbloom, "TempK to xy"
326 * http://www.brucelindbloom.com/Eqn_T_to_xy.html
327 */
328 cmsWhitePointFromTemp(&WhitePoint, TempK);
329
330 const double M = (0.0241 + 0.2562 * WhitePoint.x - 0.7341 * WhitePoint.y),
331 m1 = (-1.3515 - 1.7703 * WhitePoint.x + 5.9114 * WhitePoint.y) / M,
332 m2 = (0.0300 - 31.4424 * WhitePoint.x + 30.0717 * WhitePoint.y) / M;
333
334 const unsigned long int j
335 = ((wavelength - cie_daylight_components[0].wavelength)
336 / (cie_daylight_components[1].wavelength - cie_daylight_components[0].wavelength));
337
338 return (cie_daylight_components[j].S[0] + m1 * cie_daylight_components[j].S[1]
339 + m2 * cie_daylight_components[j].S[2]);
340}
341
342/*
343 * Bruce Lindbloom, "Computing XYZ From Spectral Data (Emissive Case)"
344 * http://www.brucelindbloom.com/Eqn_Spect_to_XYZ.html
345 */
346static cmsCIEXYZ spectrum_to_XYZ(double TempK, spd I)
347{
348 cmsCIEXYZ Source = {.X = 0.0, .Y = 0.0, .Z = 0.0 };
349
350 /*
351 * Color matching functions
352 * https://en.wikipedia.org/wiki/CIE_1931_color_space#Color_matching_functions
353 */
354 for(size_t i = 0; i < cie_1931_std_colorimetric_observer_count; i++)
355 {
356 const unsigned long int lambda = cie_1931_std_colorimetric_observer[0].wavelength
357 + (cie_1931_std_colorimetric_observer[1].wavelength
358 - cie_1931_std_colorimetric_observer[0].wavelength) * i;
359 const double P = I(lambda, TempK);
360 Source.X += P * cie_1931_std_colorimetric_observer[i].xyz.X;
361 Source.Y += P * cie_1931_std_colorimetric_observer[i].xyz.Y;
362 Source.Z += P * cie_1931_std_colorimetric_observer[i].xyz.Z;
363 }
364
365 // normalize so that each component is in [0.0, 1.0] range
366 const double _max = fmax(fmax(Source.X, Source.Y), Source.Z);
367 Source.X /= _max;
368 Source.Y /= _max;
369 Source.Z /= _max;
370
371 return Source;
372}
373
374// TODO: temperature and tint cannot be disjoined! (here it assumes no tint)
375static cmsCIEXYZ temperature_to_XYZ(double TempK)
376{
379
381 {
382 // if temperature is less than 4000K we use blackbody,
383 // because there will be no Daylight reference below 4000K...
384 return spectrum_to_XYZ(TempK, spd_blackbody);
385 }
386 else
387 {
388 return spectrum_to_XYZ(TempK, spd_daylight);
389 }
390}
391
392static cmsCIEXYZ temperature_tint_to_XYZ(double TempK, double tint)
393{
394 cmsCIEXYZ xyz = temperature_to_XYZ(TempK);
395
396 xyz.Y /= tint; // TODO: This is baaad!
397
398 return xyz;
399}
400
401// binary search inversion
402static void XYZ_to_temperature(cmsCIEXYZ XYZ, float *TempK, float *tint)
403{
404 double maxtemp = DT_IOP_HIGHEST_TEMPERATURE, mintemp = DT_IOP_LOWEST_TEMPERATURE;
405 cmsCIEXYZ _xyz;
406
407 for(*TempK = (maxtemp + mintemp) / 2.0; (maxtemp - mintemp) > 1.0; *TempK = (maxtemp + mintemp) / 2.0)
408 {
409 _xyz = temperature_to_XYZ(*TempK);
410 if(_xyz.Z / _xyz.X > XYZ.Z / XYZ.X)
411 maxtemp = *TempK;
412 else
413 mintemp = *TempK;
414 }
415
416 *tint = (_xyz.Y / _xyz.X) / (XYZ.Y / XYZ.X); // TODO: Fix this to move orthogonally to planckian locus
417
418
421 if(*tint < DT_IOP_LOWEST_TINT) *tint = DT_IOP_LOWEST_TINT;
422 if(*tint > DT_IOP_HIGHEST_TINT) *tint = DT_IOP_HIGHEST_TINT;
423}
424
425static void xyz2mul(dt_iop_module_t *self, cmsCIEXYZ xyz, double mul[4])
426{
428
429 double XYZ[3] = { xyz.X, xyz.Y, xyz.Z };
430
431 double CAM[4];
432 for(int k = 0; k < 4; k++)
433 {
434 CAM[k] = 0.0;
435 for(int i = 0; i < 3; i++)
436 {
437 CAM[k] += g->XYZ_to_CAM[k][i] * XYZ[i];
438 }
439 }
440
441 for(int k = 0; k < 4; k++) mul[k] = 1.0 / CAM[k];
442}
443
444static void temp2mul(dt_iop_module_t *self, double TempK, double tint, double mul[4])
445{
446 cmsCIEXYZ xyz = temperature_to_XYZ(TempK);
447
448 xyz.Y /= tint; // TODO: This is baaad!
457 xyz2mul(self, xyz, mul);
458}
459
461{
463
464 double CAM[4];
466 for(int k = 0; k < 4; k++) CAM[k] = CAM[k] > 0.0f ? 1.0 / CAM[k] : 0.0f;
467
468 double XYZ[3];
469 for(int k = 0; k < 3; k++)
470 {
471 XYZ[k] = 0.0;
472 for(int i = 0; i < 4; i++)
473 {
474 XYZ[k] += g->CAM_to_XYZ[k][i] * CAM[i];
475 }
476 }
477
478 return (cmsCIEXYZ){ XYZ[0], XYZ[1], XYZ[2] };
479}
480
481static void mul2temp(dt_iop_module_t *self, dt_iop_temperature_params_t *p, float *TempK, float *tint)
482{
483 XYZ_to_temperature(mul2xyz(self, p), TempK, tint);
484}
485
487int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
488 void *const ovoid)
489{
490 const dt_iop_roi_t *const roi_out = &piece->roi_out;
491 const uint32_t filters = piece->dsc_in.filters;
492 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
494 const int width = roi_out->width;
495 const int height = roi_out->height;
496
497 const float *const in = (const float *const)ivoid;
498 float *const out = (float *const)ovoid;
499 const float *const d_coeffs = d->coeffs;
500
501 if(filters == 9u)
502 { // xtrans float mosaiced
504 for(int j = 0; j < height; j++)
505 {
506 const size_t row_start = (size_t)j * width;
507 const float coeffs[12] =
508 {
509 d_coeffs[FCxtrans(j, 0, roi_out, xtrans)], d_coeffs[FCxtrans(j, 1, roi_out, xtrans)],
510 d_coeffs[FCxtrans(j, 2, roi_out, xtrans)], d_coeffs[FCxtrans(j, 3, roi_out, xtrans)],
511 d_coeffs[FCxtrans(j, 4, roi_out, xtrans)], d_coeffs[FCxtrans(j, 5, roi_out, xtrans)],
512 d_coeffs[FCxtrans(j, 6, roi_out, xtrans)], d_coeffs[FCxtrans(j, 7, roi_out, xtrans)],
513 d_coeffs[FCxtrans(j, 8, roi_out, xtrans)], d_coeffs[FCxtrans(j, 9, roi_out, xtrans)],
514 d_coeffs[FCxtrans(j, 10, roi_out, xtrans)], d_coeffs[FCxtrans(j, 11, roi_out, xtrans)],
515 };
516 /* Keep the 12-sensel X-Trans period explicit, but let the compiler pick
517 its own vectorization strategy on the scalar multiplies. */
518 int i = 0;
519 for(; i + 11 < width; i += 12)
520 {
521 const size_t p = row_start + i;
522 out[p + 0] = in[p + 0] * coeffs[0];
523 out[p + 1] = in[p + 1] * coeffs[1];
524 out[p + 2] = in[p + 2] * coeffs[2];
525 out[p + 3] = in[p + 3] * coeffs[3];
526 out[p + 4] = in[p + 4] * coeffs[4];
527 out[p + 5] = in[p + 5] * coeffs[5];
528 out[p + 6] = in[p + 6] * coeffs[6];
529 out[p + 7] = in[p + 7] * coeffs[7];
530 out[p + 8] = in[p + 8] * coeffs[8];
531 out[p + 9] = in[p + 9] * coeffs[9];
532 out[p + 10] = in[p + 10] * coeffs[10];
533 out[p + 11] = in[p + 11] * coeffs[11];
534 }
535 for(; i < width; i++)
536 {
537 const size_t p = row_start + i;
538 out[p] = in[p] * coeffs[i % 12];
539 }
540 }
541
542 }
543 else if(filters)
544 { // bayer float mosaiced
545 const int cfa_x = roi_out->x & 1;
547 for(int j = 0; j < height; j++)
548 {
549 const int offset_j = j + roi_out->y;
550 const size_t row_start = (size_t)j * width;
551 const int id0 = FC(offset_j, cfa_x + 0, filters);
552 const int id1 = FC(offset_j, cfa_x + 1, filters);
553 const float coeff0 = d_coeffs[id0];
554 const float coeff1 = d_coeffs[id1];
555 int i = 0;
556
557 for(; i + 1 < width; i += 2)
558 {
559 const size_t p = row_start + i;
560 out[p + 0] = in[p + 0] * coeff0;
561 out[p + 1] = in[p + 1] * coeff1;
562 }
563 for(; i < width; i++)
564 {
565 const size_t p = row_start + i;
566 out[p] = in[p] * d_coeffs[FC(offset_j, i + roi_out->x, filters)];
567 }
568 }
569
570 }
571 else
572 { // non-mosaiced
573 const size_t ch = piece->dsc_in.channels;
574 const size_t npixels = (size_t)roi_out->width * roi_out->height;
575
576 if(ch == 4)
577 {
579 for(size_t k = 0; k < npixels; k++)
580 {
581 const size_t p = 4 * k;
582 out[p + 0] = in[p + 0] * d->coeffs[0];
583 out[p + 1] = in[p + 1] * d->coeffs[1];
584 out[p + 2] = in[p + 2] * d->coeffs[2];
585 out[p + 3] = in[p + 3];
586 }
587
588 }
589 else
590 {
592 for(size_t k = 0; k < ch * npixels; k += ch)
593 {
594 for(ptrdiff_t c = 0; c < 3; c++)
595 {
596 const size_t p = k + c;
597 out[p] = in[p] * d->coeffs[c];
598 }
599 }
600
601 }
602
604 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
605 }
606
607 return 0;
608}
609
610#ifdef HAVE_OPENCL
611int 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)
612{
613 const dt_iop_roi_t *const roi_in = &piece->roi_in;
614 const dt_iop_roi_t *const roi_out = &piece->roi_out;
617
618 const int devid = pipe->devid;
619 const uint32_t filters = piece->dsc_in.filters;
620 cl_mem dev_coeffs = NULL;
621 cl_mem dev_xtrans = NULL;
622 cl_int err = -999;
623 int kernel = -1;
624
625 if(filters == 9u)
626 {
628 }
629 else if(filters)
630 {
632 }
633 else
634 {
636 }
637
638 if(filters == 9u)
639 {
640 dev_xtrans = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->dsc_in.xtrans), (void *)piece->dsc_in.xtrans);
641 if(IS_NULL_PTR(dev_xtrans)) goto error;
642 }
643
644 dev_coeffs = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3, d->coeffs);
645 if(IS_NULL_PTR(dev_coeffs)) goto error;
646
647 const int width = roi_in->width;
648 const int height = roi_in->height;
649
650 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
651 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
652 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
653 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
654 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
655 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_coeffs);
656 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(uint32_t), (void *)&filters);
657 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(uint32_t), (void *)&roi_out->x);
658 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(uint32_t), (void *)&roi_out->y);
659 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), (void *)&dev_xtrans);
660 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
661 if(err != CL_SUCCESS) goto error;
662
665
666 return TRUE;
667
668error:
671 dt_print(DT_DEBUG_OPENCL, "[opencl_white_balance] couldn't enqueue kernel! %d\n", err);
672 return FALSE;
673}
674#endif
675
676gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
677{
678 // History sanitization: white balance has nothing to balance on a true monochrome sensor, so a
679 // WB entry pasted onto a monochrome image is forced off here, at history-read time. For every
680 // other image (including non-raw, where manual WB is allowed) the user's state is preserved.
681 // This mirrors reload_defaults(), which hides the on/off button for monochrome images.
682 const gboolean mono = dt_image_is_monochrome(&self->dev->image_storage);
683 const gboolean state = current_state && !mono;
684 dt_iop_fmt_log(self, "force_enable: class=%s mono=%d current=%d -> %d",
686 mono, current_state, state);
687 return state;
688}
689
690// Publish the given WB multipliers into dev->proxy.wb_coeffs, the dev-wide value consumed by
691// channelmixerrgb's chromatic adaptation (get_white_balance_coeff()). dev->proxy is a GUI/main-thread
692// channel for inter-module communication; it MUST NOT be accessed from the pixel pipeline (neither
693// read nor write -- the pipeline only needs module params). It is published purely on the main
694// thread: reload_defaults() seeds the per-image default, commit_proxy() refreshes it once
695// history/presets are applied, and every GUI history commit re-publishes it (see
696// dt_dev_add_history_item_real / dt_dev_pop_history_items_ext).
697// (NOTE: channelmixerrgb still reads this from its process()/commit_params() for the live
698// DT_ILLUMINANT_CAMERA tracking -- that is a remaining violation of the rule, to be removed by
699// resolving the camera illuminant on the main thread into its params.)
701{
702 if(IS_NULL_PTR(self->dev)) return;
703 // Mirror the legacy commit_params() values exactly: g2 falls back to the first green when it is
704 // not a usable number on a non-4-colour sensor (NaN/0 would poison the G2 CFA sites downstream).
705 const gboolean g2_usable = isnormal(p->g2) || (self->dev->image_storage.flags & DT_IMAGE_4BAYER);
706 self->dev->proxy.wb_coeffs[0] = p->red;
707 self->dev->proxy.wb_coeffs[1] = p->green;
708 self->dev->proxy.wb_coeffs[2] = p->blue;
709 self->dev->proxy.wb_coeffs[3] = g2_usable ? p->g2 : p->green;
710}
711
712// Main-thread proxy commit, invoked by the history layer after params are applied (and before any
713// pipeline runs). Publishes the module's EFFECTIVE (post-history/preset) WB to dev->proxy.
718
721{
725
726 d->coeffs[0] = p->red;
727 d->coeffs[1] = p->green;
728 d->coeffs[2] = p->blue;
729 // Legacy-history / non-4-colour safety net: older edits may have stored g2 = NaN (the historical
730 // "usually NAN for RGB" second-green value, see find_coeffs). A NaN/0 multiplier would poison
731 // half the green CFA sites, so fall back to the first green when g2 isn't a usable number.
732 const gboolean g2_usable = isnormal(p->g2) || (self->dev->image_storage.flags & DT_IMAGE_4BAYER);
733 d->coeffs[3] = g2_usable ? p->g2 : p->green;
734 dt_iop_fmt_log(self, "commit: class=%s matrix_supported=%d coeffs=[%.4f %.4f %.4f %.4f] g2_in=%.4f g2_usable=%d -> enabled=%d",
737 d->coeffs[0], d->coeffs[1], d->coeffs[2], d->coeffs[3], p->g2, g2_usable, piece->enabled);
738 // NOTE: dev->proxy.wb_coeffs is intentionally NOT written here. dev->proxy is a GUI/main-thread
739 // inter-module channel and must not be touched from the pipeline; writing it from this
740 // pipeline-thread callback was a data race. It is now published on the main thread via
741 // commit_proxy() / reload_defaults().
742 piece->dsc_out.temperature.enabled = 1;
743 for(int k = 0; k < 4; k++)
744 {
745 piece->dsc_out.temperature.coeffs[k] = d->coeffs[k];
746 piece->dsc_out.processed_maximum[k] = piece->dsc_in.processed_maximum[k] * d->coeffs[k];
747 }
748
749 // 4Bayer images not implemented in OpenCL yet
751
752 if(!IS_NULL_PTR(g))
753 {
754 // advertise on the pipe if coeffs are D65 for validity check
755 gboolean is_D65 = TRUE;
756 for(int c = 0; c < 3; c++)
757 if(d->coeffs[c] != (float)g->daylight_wb[c]) is_D65 = FALSE;
758
759 self->dev->proxy.wb_is_D65 = is_D65;
760 }
761}
762
764{
766 piece->data_size = sizeof(dt_iop_temperature_data_t);
767}
768
770{
771 dt_free_align(piece->data);
772 piece->data = NULL;
773}
774
776{
778
779 const gboolean color_rgb = g->colored_sliders &&
781
786 dt_bauhaus_slider_set_feedback(g->scale_r, !color_rgb);
787 dt_bauhaus_slider_set_feedback(g->scale_g, !color_rgb);
788 dt_bauhaus_slider_set_feedback(g->scale_b, !color_rgb);
789 dt_bauhaus_slider_set_feedback(g->scale_g2, !color_rgb);
790
791 if(!color_rgb) return;
792
793 // there are 3 ways to do colored sliders: naive (independent 0->1), smart(er) (dependent 0->1) and real (coeff)
794
795 if(FALSE)
796 {
797 //naive:
798 dt_bauhaus_slider_set_stop(g->scale_r, 0.0, 0.0, 0.0, 0.0);
799 dt_bauhaus_slider_set_stop(g->scale_r, 1.0, 1.0, 0.0, 0.0);
800
801 dt_bauhaus_slider_set_stop(g->scale_g, 0.0, 0.0, 0.0, 0.0);
802 dt_bauhaus_slider_set_stop(g->scale_g, 1.0, 0.0, 1.0, 0.0);
803
804 dt_bauhaus_slider_set_stop(g->scale_b, 0.0, 0.0, 0.0, 0.0);
805 dt_bauhaus_slider_set_stop(g->scale_b, 1.0, 0.0, 0.0, 1.0);
806
807 dt_bauhaus_slider_set_stop(g->scale_g2, 0.0, 0.0, 0.0, 0.0);
808 dt_bauhaus_slider_set_stop(g->scale_g2, 1.0, 0.0, 1.0, 0.0);
809 }
810 if(!g->blackbody_is_confusing)
811 {
812 //smart(er) than naive
813 const float rchan = dt_bauhaus_slider_get(g->scale_r) / dt_bauhaus_slider_get_hard_max(g->scale_r);
814 const float gchan = dt_bauhaus_slider_get(g->scale_g) / dt_bauhaus_slider_get_hard_max(g->scale_g);
815 const float bchan = dt_bauhaus_slider_get(g->scale_b) / dt_bauhaus_slider_get_hard_max(g->scale_b);
816
817 dt_bauhaus_slider_set_stop(g->scale_r, 0.0, 0.0, gchan, bchan);
818 dt_bauhaus_slider_set_stop(g->scale_r, 1.0, 1.0, gchan, bchan);
819
820 dt_bauhaus_slider_set_stop(g->scale_g, 0.0, rchan, 0.0, bchan);
821 dt_bauhaus_slider_set_stop(g->scale_g, 1.0, rchan, 1.0, bchan);
822
823 dt_bauhaus_slider_set_stop(g->scale_b, 0.0, rchan, gchan, 0.0);
824 dt_bauhaus_slider_set_stop(g->scale_b, 1.0, rchan, gchan, 1.0);
825 }
826 else
827 {
828 //real (ish)
829 //we consider daylight wb to be "reference white"
830 const double white[3] = {
831 1.0/g->daylight_wb[0],
832 1.0/g->daylight_wb[1],
833 1.0/g->daylight_wb[2],
834 };
835
836 const float rchanmul = dt_bauhaus_slider_get(g->scale_r);
837 const float rchanmulmax = dt_bauhaus_slider_get_hard_max(g->scale_r);
838 const float gchanmul = dt_bauhaus_slider_get(g->scale_g);
839 const float gchanmulmax = dt_bauhaus_slider_get_hard_max(g->scale_g);
840 const float bchanmul = dt_bauhaus_slider_get(g->scale_b);
841 const float bchanmulmax = dt_bauhaus_slider_get_hard_max(g->scale_g);
842
843 dt_bauhaus_slider_set_stop(g->scale_r, 0.0, white[0]*0.0, white[1]*gchanmul, white[2]*bchanmul);
844 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);
845 dt_bauhaus_slider_set_stop(g->scale_r, 1.0, white[0]*1.0, white[1]*(gchanmul/gchanmulmax), white[2]*(bchanmul/bchanmulmax));
846
847 dt_bauhaus_slider_set_stop(g->scale_g, 0.0, white[0]*rchanmul, white[1]*0.0, white[2]*bchanmul);
848 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);
849 dt_bauhaus_slider_set_stop(g->scale_g, 1.0, white[0]*(rchanmul/rchanmulmax), white[1]*1.0, white[2]*(bchanmul/bchanmulmax));
850
851 dt_bauhaus_slider_set_stop(g->scale_b, 0.0, white[0]*rchanmul, white[1]*gchanmul, white[2]*0.0);
852 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]);
853 dt_bauhaus_slider_set_stop(g->scale_b, 1.0, white[0]*(rchanmul/rchanmulmax), white[1]*(gchanmul/gchanmulmax), white[2]*1.0);
854 }
855
856 if(gtk_widget_get_visible(GTK_WIDGET(g->scale_r)))
857 {
858 gtk_widget_queue_draw(GTK_WIDGET(g->scale_r));
859 gtk_widget_queue_draw(GTK_WIDGET(g->scale_g));
860 gtk_widget_queue_draw(GTK_WIDGET(g->scale_b));
861 }
862}
863
865{
867
869 dt_bauhaus_slider_clear_stops(g->scale_tint);
870 dt_bauhaus_slider_set_feedback(g->scale_k, !g->colored_sliders);
871 dt_bauhaus_slider_set_feedback(g->scale_tint, !g->colored_sliders);
872
873 if(!g->colored_sliders) return;
874
876 const double tint_step = (double)(DT_IOP_HIGHEST_TINT - DT_IOP_LOWEST_TINT) / (DT_BAUHAUS_SLIDER_MAX_STOPS - 1.0);
877 const int blackbody_is_confusing = g->blackbody_is_confusing;
878
879 const float cur_temp = dt_bauhaus_slider_get(g->scale_k);
880 const float cur_tint = dt_bauhaus_slider_get(g->scale_tint);
881
882 //we consider daylight wb to be "reference white"
883 const double dayligh_white[3] = {
884 1.0/g->daylight_wb[0],
885 1.0/g->daylight_wb[1],
886 1.0/g->daylight_wb[2],
887 };
888
889 double cur_coeffs[4] = {0.0};
890 temp2mul(self, cur_temp, 1.0, cur_coeffs);
891 const double cur_white[3] = {
892 1.0/cur_coeffs[0],
893 1.0/cur_coeffs[1],
894 1.0/cur_coeffs[2],
895 };
896
897 if(blackbody_is_confusing)
898 {
899 // show effect of adjustment on temp/tint sliders
900 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
901 {
902 const float stop = i / (DT_BAUHAUS_SLIDER_MAX_STOPS - 1.0);
903 const double K = DT_IOP_LOWEST_TEMPERATURE + i * temp_step;
904 const double tint = DT_IOP_LOWEST_TINT + i * tint_step;
905
906 double coeffs_K[4];
907 double coeffs_tint[4];
908 temp2mul(self, K, cur_tint, coeffs_K);
909 temp2mul(self, cur_temp, tint, coeffs_tint);
910 coeffs_K[0] /= coeffs_K[1];
911 coeffs_K[2] /= coeffs_K[1];
912 coeffs_K[3] /= coeffs_K[1];
913 coeffs_K[1] = 1.0;
914 coeffs_tint[0] /= coeffs_tint[1];
915 coeffs_tint[2] /= coeffs_tint[1];
916 coeffs_tint[3] /= coeffs_tint[1];
917 coeffs_tint[1] = 1.0;
918
919 dt_aligned_pixel_t sRGB_K = { dayligh_white[0]*coeffs_K[0], dayligh_white[1]*coeffs_K[1],
920 dayligh_white[2]*coeffs_K[2] };
921 dt_aligned_pixel_t sRGB_tint = {cur_white[0]*coeffs_tint[0], cur_white[1]*coeffs_tint[1],
922 cur_white[2]*coeffs_tint[2]};
923 const float maxsRGB_K = fmaxf(fmaxf(sRGB_K[0], sRGB_K[1]), sRGB_K[2]);
924 const float maxsRGB_tint = fmaxf(fmaxf(sRGB_tint[0], sRGB_tint[1]),sRGB_tint[2]);
925
926 if(maxsRGB_K > 1.f)
927 {
928 for(int ch=0; ch<3; ch++)
929 {
930 sRGB_K[ch] = fmaxf(sRGB_K[ch] / maxsRGB_K, 0.f);
931 }
932 }
933 if(maxsRGB_tint > 1.f)
934 {
935 for(int ch=0; ch<3; ch++)
936 {
937 sRGB_tint[ch] = fmaxf(sRGB_tint[ch] / maxsRGB_tint, 0.f);
938 }
939 }
940 dt_bauhaus_slider_set_stop(g->scale_k, stop, sRGB_K[0], sRGB_K[1], sRGB_K[2]);
941 dt_bauhaus_slider_set_stop(g->scale_tint, stop, sRGB_tint[0], sRGB_tint[1], sRGB_tint[2]);
942 }
943 }
944 else
945 {
946 // reflect actual black body colors for the temperature slider
947 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
948 {
949 const float stop = i / (DT_BAUHAUS_SLIDER_MAX_STOPS - 1.0);
950 const double K = DT_IOP_LOWEST_TEMPERATURE + i * temp_step;
951 const double tint = DT_IOP_LOWEST_TINT + i * tint_step;
952
953 const cmsCIEXYZ cmsXYZ_temp = temperature_tint_to_XYZ(K,cur_tint);
954 const cmsCIEXYZ cmsXYZ_tint = temperature_tint_to_XYZ(cur_temp, tint);
955 dt_aligned_pixel_t XYZ_temp = {cmsXYZ_temp.X, cmsXYZ_temp.Y, cmsXYZ_temp.Z};
956 dt_aligned_pixel_t XYZ_tint = {cmsXYZ_tint.X, cmsXYZ_tint.Y, cmsXYZ_tint.Z};
957 dt_aligned_pixel_t sRGB_temp;
958 dt_aligned_pixel_t sRGB_tint;
959
960 dt_XYZ_to_Rec709_D65(XYZ_temp, sRGB_temp);
961 dt_XYZ_to_Rec709_D65(XYZ_tint, sRGB_tint);
962
963 const float maxsRGB_temp = fmaxf(fmaxf(sRGB_temp[0], sRGB_temp[1]), sRGB_temp[2]);
964 const float maxsRGB_tint = fmaxf(fmaxf(sRGB_tint[0], sRGB_tint[1]), sRGB_tint[2]);
965
966 if(maxsRGB_temp > 1.f)
967 {
968 for(int ch=0; ch<3; ch++)
969 {
970 sRGB_temp[ch] = fmaxf(sRGB_temp[ch] / maxsRGB_temp, 0.f);
971 }
972 }
973
974 if(maxsRGB_tint > 1.f)
975 {
976 for(int ch=0; ch<3; ch++)
977 {
978 sRGB_tint[ch] = fmaxf(sRGB_tint[ch] / maxsRGB_tint, 0.f);
979 }
980 }
981
982 dt_bauhaus_slider_set_stop(g->scale_k, stop, sRGB_temp[0], sRGB_temp[1], sRGB_temp[2]);
983 dt_bauhaus_slider_set_stop(g->scale_tint, stop, sRGB_tint[0], sRGB_tint[1], sRGB_tint[2]);
984 }
985 }
986
987 if(gtk_widget_get_visible(GTK_WIDGET(g->scale_k)))
988 {
989 gtk_widget_queue_draw(GTK_WIDGET(g->scale_k));
990 gtk_widget_queue_draw(GTK_WIDGET(g->scale_tint));
991 }
992}
993
994
995void gui_update(struct dt_iop_module_t *self)
996{
999
1000 const gboolean monochrome = dt_image_is_monochrome(&self->dev->image_storage);
1001 const gboolean is_raw = dt_image_is_matrix_correction_supported(&self->dev->image_storage);
1002 self->hide_enable_button = monochrome;
1003 self->default_enabled = is_raw;
1004 gtk_stack_set_visible_child_name(GTK_STACK(self->gui->widget), self->hide_enable_button ? "disabled" : "enabled");
1005
1006// if(self->hide_enable_button) return;
1007
1008 // Per-image WB references + widget defaults, formerly computed in reload_defaults(). These need
1009 // live widgets (bauhaus slider defaults, the presets combobox) and the GUI conversion matrices,
1010 // so they belong here on the GUI thread. Read the already-computed default_params; never recompute.
1012
1013 prepare_matrices(self);
1014
1015 dt_bauhaus_slider_set_default(g->scale_r, d->red);
1016 dt_bauhaus_slider_set_default(g->scale_g, d->green);
1017 dt_bauhaus_slider_set_default(g->scale_b, d->blue);
1018 dt_bauhaus_slider_set_default(g->scale_g2, d->g2);
1019
1020 // remember daylight wb used for temperature/tint conversion, assuming it corresponds to CIE D65
1021 _temp_array_from_params(g->daylight_wb, d);
1022 calculate_bogus_daylight_wb(self, g->daylight_wb);
1023
1024 // Store EXIF WB coeffs
1025 if(is_raw)
1026 find_coeffs(self, g->as_shot_wb);
1027 else
1028 g->as_shot_wb[0] = g->as_shot_wb[1] = g->as_shot_wb[2] = g->as_shot_wb[3] = 1.f;
1029
1030 g->as_shot_wb[0] /= g->as_shot_wb[1];
1031 g->as_shot_wb[2] /= g->as_shot_wb[1];
1032 g->as_shot_wb[3] /= g->as_shot_wb[1];
1033 g->as_shot_wb[1] = 1.0;
1034
1035 {
1036 float TempK_def, tint_def;
1037 mul2temp(self, d, &TempK_def, &tint_def);
1038 dt_bauhaus_slider_set_default(g->scale_k, TempK_def);
1039 dt_bauhaus_slider_set_default(g->scale_tint, tint_def);
1040 }
1041
1042 dt_bauhaus_combobox_clear(g->presets);
1043 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"
1044 dt_bauhaus_combobox_add(g->presets, C_("white balance", "from image area")); // old "spot", reason: describes exactly what'll happen
1045 dt_bauhaus_combobox_add(g->presets, C_("white balance", "user modified"));
1046 dt_bauhaus_combobox_add(g->presets, C_("white balance", "camera reference")); // old "camera neutral", reason: better matches intent
1047 g->preset_cnt = DT_IOP_NUM_OF_STD_TEMP_PRESETS;
1048 memset(g->preset_num, 0, sizeof(g->preset_num));
1049
1050 gui_sliders_update(self);
1051
1053
1054 float tempK, tint;
1055 mul2temp(self, p, &tempK, &tint);
1056
1057 dt_bauhaus_slider_set(g->scale_k, tempK);
1058 dt_bauhaus_slider_set(g->scale_tint, tint);
1059 dt_bauhaus_slider_set(g->scale_r, p->red);
1060 dt_bauhaus_slider_set(g->scale_g, p->green);
1061 dt_bauhaus_slider_set(g->scale_b, p->blue);
1062 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1063
1064 dt_bauhaus_combobox_set(g->presets, -1);
1065
1066 gboolean found = FALSE;
1067
1068 // is this a "as shot" white balance?
1069 if(p->red == g->as_shot_wb[0] && p->green == g->as_shot_wb[1] && p->blue == g->as_shot_wb[2])
1070 {
1072 found = TRUE;
1073 }
1074 else
1075 {
1076 // is this a "D65 white balance"?
1077 if((p->red == (float)g->daylight_wb[0]) &&
1078 (p->green == (float)g->daylight_wb[1]) &&
1079 (p->blue == (float)g->daylight_wb[2]))
1080 {
1082 found = TRUE;
1083 }
1084 }
1085
1086 if(!found)
1088
1089 if (!found || isnan(g->mod_temp)) // reset or initialize user-defined
1090 {
1091 g->mod_temp = tempK;
1092 g->mod_tint = tint;
1093 _temp_array_from_params(g->mod_coeff, p);
1094 }
1095
1096 gtk_widget_set_visible(g->buttonbar, g->button_bar_visible);
1097
1098 const int preset = dt_bauhaus_combobox_get(g->presets);
1099
1100 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_asshot), preset == DT_IOP_TEMP_AS_SHOT);
1101 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_user), preset == DT_IOP_TEMP_USER);
1102 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_d65), preset == DT_IOP_TEMP_D65);
1103
1105 color_rgb_sliders(self);
1106
1108
1109 gtk_widget_queue_draw(self->gui->widget);
1110}
1111
1112static int calculate_bogus_daylight_wb(dt_iop_module_t *module, double bwb[4])
1113{
1115 {
1116 bwb[0] = 1.0;
1117 bwb[2] = 1.0;
1118 bwb[1] = 1.0;
1119 bwb[3] = 1.0;
1120
1121 return 0;
1122 }
1123
1124 double mul[4];
1126 NULL, NULL,
1127 module->dev->image_storage.d65_color_matrix, mul))
1128 {
1129 // normalize green:
1130 bwb[0] = mul[0] / mul[1];
1131 bwb[2] = mul[2] / mul[1];
1132 bwb[1] = 1.0;
1133 bwb[3] = mul[3] / mul[1];
1134
1135 return 0;
1136 }
1137
1138 return 1;
1139}
1140
1142{
1143 // called exclusively from gui_update(): the gui struct exists by construction here,
1144 // and the NULL-tolerant accessor would only teach the analyzer a NULL that cannot be
1146
1147 // sRGB D65
1148 const double RGB_to_XYZ[3][4] = { { 0.4124564, 0.3575761, 0.1804375, 0 },
1149 { 0.2126729, 0.7151522, 0.0721750, 0 },
1150 { 0.0193339, 0.1191920, 0.9503041, 0 } };
1151
1152 // sRGB D65
1153 const double XYZ_to_RGB[4][3] = { { 3.2404542, -1.5371385, -0.4985314 },
1154 { -0.9692660, 1.8760108, 0.0415560 },
1155 { 0.0556434, -0.2040259, 1.0572252 },
1156 { 0, 0, 0 } };
1157
1159 {
1160 // let's just assume for now(TM) that if it has no raw colorimetry, it is sRGB.
1161 // Gate on the colorimetry axis (raw or sRAW), not on the mosaic-only "raw" flag, otherwise
1162 // an already-demosaiced raw (sRAW / linear DNG) would skip its camera matrix here (issue #729).
1163 memcpy(g->XYZ_to_CAM, XYZ_to_RGB, sizeof(g->XYZ_to_CAM));
1164 memcpy(g->CAM_to_XYZ, RGB_to_XYZ, sizeof(g->CAM_to_XYZ));
1165 return;
1166 }
1167
1170 g->XYZ_to_CAM, g->CAM_to_XYZ))
1171 {
1172 char *camera = module->dev->image_storage.camera_makermodel;
1173 fprintf(stderr, "[temperature] `%s' color matrix not found for image\n", camera);
1174 dt_control_log(_("`%s' 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_log(_("failed to read camera white balance information from `%s'!"),
1209 img->filename);
1210 fprintf(stderr, "[temperature] failed to read camera white balance information from `%s'!\n",
1211 img->filename);
1212 }
1213
1214 double bwb[4];
1215 if(!calculate_bogus_daylight_wb(module, bwb))
1216 {
1217 // found camera matrix and used it to calculate bogus daylight wb
1218 for(int c = 0; c < 4; c++) coeffs[c] = bwb[c];
1219 return;
1220 }
1221
1222 // did not find preset either?
1223 // final security net: hardcoded default that fits most cams.
1224 coeffs[0] = 2.0;
1225 coeffs[1] = 1.0;
1226 coeffs[2] = 1.5;
1227 coeffs[3] = 1.0;
1228}
1229
1231{
1232 dt_iop_temperature_params_t *d = module->default_params;
1233 *d = (dt_iop_temperature_params_t){ 1.0, 1.0, 1.0, 1.0 };
1234
1235 // we might be called from presets update infrastructure => there is no image
1236 if(!module->dev || module->dev->image_storage.id == -1) return;
1237
1238 const gboolean is_raw = dt_image_is_matrix_correction_supported(&module->dev->image_storage);
1239 const gboolean monochrome = dt_image_is_monochrome(&module->dev->image_storage);
1240
1241 // Note : this is not an user param anymore, and is set to modern by default
1242 // except for old histories using temperature without having an history entry for it.
1243 // see develop/develop.c/_dev_auto_apply_presets()
1244 const gboolean is_modern =
1245 dt_conf_is_equal("plugins/darkroom/chromatic-adaptation", "modern");
1246
1247 module->default_enabled = 0;
1248 module->hide_enable_button = 0;
1249
1250 // White balance module doesn't need to be enabled for monochrome raws (like
1251 // for leica monochrom cameras). prepare_matrices is a noop as well, as there
1252 // isn't a color matrix, so we can skip that as well.
1253 if(monochrome)
1254 {
1255 module->hide_enable_button = 1;
1256 }
1257 else
1258 {
1259 /* check if file is raw / hdr */
1260 if(is_raw)
1261 {
1262 // raw images need wb:
1263 module->default_enabled = 1;
1264
1265 // if workflow = modern, only set WB coeffs equivalent to D65 illuminant
1266 // full chromatic adaptation is deferred to channelmixerrgb
1267 double coeffs[4] = { 0 };
1268 if(is_modern && !calculate_bogus_daylight_wb(module, coeffs))
1269 {
1270 d->red = coeffs[0]/coeffs[1];
1271 d->blue = coeffs[2]/coeffs[1];
1272 d->g2 = coeffs[3]/coeffs[1];
1273 d->green = 1.0f;
1274 }
1275 else
1276 {
1277 // do best to find starting coeffs
1278 find_coeffs(module, coeffs);
1279 d->red = coeffs[0]/coeffs[1];
1280 d->blue = coeffs[2]/coeffs[1];
1281 d->g2 = coeffs[3]/coeffs[1];
1282 d->green = 1.0f;
1283 }
1284 }
1285 }
1286
1287 dt_iop_fmt_log(module, "reload_defaults: class=%s matrix_supported=%d mono=%d modern=%d -> default_enabled=%d coeffs=[%.4f %.4f %.4f %.4f]",
1289 is_raw, monochrome, is_modern, module->default_enabled, d->red, d->green, d->blue, d->g2);
1290
1291 // Seed the dev WB proxy with the per-image DEFAULT coeffs (main thread). channelmixerrgb derives
1292 // its own per-image defaults from dev->proxy.wb_coeffs, and reload_defaults() runs in iop order
1293 // (temperature before channelmixerrgb) at history init, so this baseline is visible to it. The
1294 // EFFECTIVE (history/preset) coeffs are published later by commit_proxy() once history is applied.
1295 // KNOWN GAP (no fix landed yet): on a user WB auto-preset, channelmixerrgb's default is derived
1296 // here against this pre-preset baseline, so a fresh init does not match a later manual reset.
1297 _publish_wb_coeffs(module, d);
1298
1299 // NOTE: widget state (slider/combo defaults, the daylight/as-shot WB references, the presets
1300 // combobox, the per-CFA slider labels) is intentionally NOT set here. reload_defaults() runs on
1301 // export/thumbnail devs with no widgets and off the GUI thread; touching bauhaus here re-entered
1302 // callbacks against half-built widgets and crashed. That work now lives in gui_update(), reading
1303 // these same default_params.
1304}
1305
1307{
1308 const int program = 2; // basic.cl, from programs.conf
1311 module->data = gd;
1312 gd->kernel_whitebalance_4f = dt_opencl_create_kernel(program, "whitebalance_4f");
1313 gd->kernel_whitebalance_1f = dt_opencl_create_kernel(program, "whitebalance_1f");
1314 gd->kernel_whitebalance_1f_xtrans = dt_opencl_create_kernel(program, "whitebalance_1f_xtrans");
1315}
1316
1325
1327{
1328 if(dt_gui_widgets_suppressed()) return;
1329
1332
1334
1335 g->mod_temp = dt_bauhaus_slider_get(g->scale_k);
1336 g->mod_tint = dt_bauhaus_slider_get(g->scale_tint);
1337
1338 temp2mul(self, g->mod_temp, g->mod_tint, g->mod_coeff);
1339
1340 // normalize
1341 g->mod_coeff[0] /= g->mod_coeff[1];
1342 g->mod_coeff[2] /= g->mod_coeff[1];
1343 g->mod_coeff[3] /= g->mod_coeff[1];
1344 g->mod_coeff[1] = 1.0;
1345
1346 _temp_params_from_array(p, g->mod_coeff);
1347
1349 dt_bauhaus_slider_set(g->scale_r, p->red);
1350 dt_bauhaus_slider_set(g->scale_g, p->green);
1351 dt_bauhaus_slider_set(g->scale_b, p->blue);
1352 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1355
1356 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1357}
1358
1359void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1360{
1363
1364 _temp_array_from_params(g->mod_coeff, p);
1365
1366 mul2temp(self, p, &g->mod_temp, &g->mod_tint);
1367
1369}
1370
1371static gboolean btn_toggled(GtkWidget *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1372{
1373 if(dt_gui_widgets_suppressed()) return TRUE;
1374
1376
1377 int preset = togglebutton == g->btn_asshot ? DT_IOP_TEMP_AS_SHOT :
1378 togglebutton == g->btn_d65 ? DT_IOP_TEMP_D65 :
1379 togglebutton == g->btn_user ? DT_IOP_TEMP_USER : 0;
1380
1381 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton)))
1382 {
1383 if(dt_bauhaus_combobox_get(g->presets) != preset)
1384 dt_bauhaus_combobox_set(g->presets, preset);
1385 }
1386 else if(dt_bauhaus_combobox_get(g->presets) == preset)
1387 {
1388 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton), TRUE);
1389 }
1390
1391 return TRUE;
1392}
1393
1395{
1396 if(dt_gui_widgets_suppressed()) return;
1397
1400
1401 const int pos = dt_bauhaus_combobox_get(g->presets);
1402
1403 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_asshot), pos == DT_IOP_TEMP_AS_SHOT);
1405 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_user), pos == DT_IOP_TEMP_USER);
1406 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_d65), pos == DT_IOP_TEMP_D65);
1407
1408 switch(pos)
1409 {
1410 case -1: // just un-setting.
1411 return;
1412 case DT_IOP_TEMP_AS_SHOT: // as shot wb
1413 _temp_params_from_array(p, g->as_shot_wb);
1414 break;
1415 case DT_IOP_TEMP_SPOT: // from image area wb, expose callback will set p->rgbg2.
1416 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->colorpicker)))
1417 {
1418 gboolean ret_val;
1419 g_signal_emit_by_name(G_OBJECT(g->colorpicker), "button-press-event", NULL, &ret_val);
1420 }
1421 break;
1422 case DT_IOP_TEMP_USER: // directly changing one of the coeff sliders also changes the mod_coeff so it can be read here
1423 _temp_params_from_array(p, g->mod_coeff);
1424 break;
1425 case DT_IOP_TEMP_D65: // camera reference d65
1426 _temp_params_from_array(p, g->daylight_wb);
1427 break;
1428 default: // camera WB presets
1429 break;
1430 }
1431
1432 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1433
1434 float TempK, tint;
1435
1436 if(pos == DT_IOP_TEMP_USER)
1437 {
1438 TempK = g->mod_temp;
1439 tint = g->mod_tint;
1440 }
1441 else
1442 {
1443 mul2temp(self, p, &TempK, &tint);
1444 }
1445
1447 dt_bauhaus_slider_set(g->scale_k, TempK);
1448 dt_bauhaus_slider_set(g->scale_tint, tint);
1449 dt_bauhaus_slider_set(g->scale_r, p->red);
1450 dt_bauhaus_slider_set(g->scale_g, p->green);
1451 dt_bauhaus_slider_set(g->scale_b, p->blue);
1452 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1454
1456 color_rgb_sliders(self);
1457
1458 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1459}
1460
1462{
1463 (void)picker;
1464 (void)pipe;
1465 (void)piece;
1466 if(dt_gui_widgets_suppressed()) return;
1467
1470
1471 // capture gui color picked event.
1472 if(self->picked_color_max[0] < self->picked_color_min[0]) return;
1473 const float *grayrgb = self->picked_color;
1474
1475 // normalize green:
1476 p->green = grayrgb[1] > 0.001f ? 1.0f / grayrgb[1] : 1.0f;
1477 p->red = fmaxf(0.0f, fminf(8.0f, (grayrgb[0] > 0.001f ? 1.0f / grayrgb[0] : 1.0f) / p->green));
1478 p->blue = fmaxf(0.0f, fminf(8.0f, (grayrgb[2] > 0.001f ? 1.0f / grayrgb[2] : 1.0f) / p->green));
1479 p->g2 = fmaxf(0.0f, fminf(8.0f, (grayrgb[3] > 0.001f ? 1.0f / grayrgb[3] : 1.0f) / p->green));
1480 p->green = 1.0;
1481
1484
1485 float tempK, tint;
1486 mul2temp(self, p, &tempK, &tint);
1487
1488 dt_bauhaus_slider_set(g->scale_k, tempK);
1489 dt_bauhaus_slider_set(g->scale_tint, tint);
1490 dt_bauhaus_slider_set(g->scale_r, p->red);
1491 dt_bauhaus_slider_set(g->scale_g, p->green);
1492 dt_bauhaus_slider_set(g->scale_b, p->blue);
1493 dt_bauhaus_slider_set(g->scale_g2, p->g2);
1494
1495 dt_bauhaus_combobox_set(g->presets, -1);
1497
1498 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1499}
1500
1501
1502static void gui_sliders_update(struct dt_iop_module_t *self)
1503{
1504 const dt_image_t *img = &self->dev->image_storage;
1506
1507 if(FILTERS_ARE_CYGM(img->dsc.filters))
1508 {
1509 dt_bauhaus_widget_set_label(g->scale_r, N_("green"));
1510 gtk_widget_set_tooltip_text(g->scale_r, _("green channel coefficient"));
1511 dt_bauhaus_widget_set_label(g->scale_g, N_("magenta"));
1512 gtk_widget_set_tooltip_text(g->scale_g, _("magenta channel coefficient"));
1513 dt_bauhaus_widget_set_label(g->scale_b, N_("cyan"));
1514 gtk_widget_set_tooltip_text(g->scale_b, _("cyan channel coefficient"));
1515 dt_bauhaus_widget_set_label(g->scale_g2, N_("yellow"));
1516 gtk_widget_set_tooltip_text(g->scale_g2, _("yellow channel coefficient"));
1517
1518 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_b, 0);
1519 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g2, 1);
1520 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g, 2);
1521 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_r, 3);
1522 }
1523 else
1524 {
1525 dt_bauhaus_widget_set_label(g->scale_r, N_("red"));
1526 gtk_widget_set_tooltip_text(g->scale_r, _("red channel coefficient"));
1527 dt_bauhaus_widget_set_label(g->scale_g, N_("green"));
1528 gtk_widget_set_tooltip_text(g->scale_g, _("green channel coefficient"));
1529 dt_bauhaus_widget_set_label(g->scale_b, N_("blue"));
1530 gtk_widget_set_tooltip_text(g->scale_b, _("blue channel coefficient"));
1531 dt_bauhaus_widget_set_label(g->scale_g2, N_("emerald"));
1532 gtk_widget_set_tooltip_text(g->scale_g2, _("emerald channel coefficient"));
1533
1534 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_r, 0);
1535 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g, 1);
1536 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_b, 2);
1537 gtk_box_reorder_child(GTK_BOX(g->cs.container), g->scale_g2, 3);
1538 }
1539
1540 gtk_widget_set_visible(GTK_WIDGET(g->scale_g2), (img->flags & DT_IMAGE_4BAYER));
1541}
1542
1543void gui_init(struct dt_iop_module_t *self)
1544{
1546
1547 g->colored_sliders = TRUE; // true if config != "no color"
1548 g->blackbody_is_confusing = FALSE; // true if config != "illuminant color"
1549
1550 const int feedback = TRUE;
1551 g->button_bar_visible = TRUE;
1552
1553 GtkBox *box_enabled = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
1554
1555 g->btn_asshot = dt_iop_togglebutton_new(self, N_("settings"), N_("as shot"), NULL,
1556 G_CALLBACK(btn_toggled), FALSE, 0, 0,
1558 gtk_widget_set_tooltip_text(g->btn_asshot, _("set white balance to as shot"));
1559
1560 // create color picker to be able to send its signal when spot selected,
1561 // this module may expect data in RAW or RGB, setting the color picker CST to IOP_CS_NONE will make the color
1562 // picker to depend on the number of color channels of the pixels. It is done like this as we may not know the
1563 // actual kind of data we are using in the GUI (it is part of the pipeline).
1566
1567 gtk_widget_set_tooltip_text(g->colorpicker, _("set white balance to detected from area"));
1568
1569 g->btn_user = dt_iop_togglebutton_new(self, N_("settings"), N_("user modified"), NULL,
1570 G_CALLBACK(btn_toggled), FALSE, 0, 0,
1572 gtk_widget_set_tooltip_text(g->btn_user, _("set white balance to user modified"));
1573
1574
1575 g->btn_d65 = dt_iop_togglebutton_new(self, N_("settings"), N_("camera reference"), NULL,
1576 G_CALLBACK(btn_toggled), FALSE, 0, 0,
1578 gtk_widget_set_tooltip_text(g->btn_d65, _("set white balance to camera reference point\nin most cases it should be D65"));
1579
1580 g->buttonbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING); // put buttons at top. fill later.
1581 dt_gui_add_class(g->buttonbar, "dt_iop_toggle");
1582 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->btn_d65, TRUE, TRUE, 0);
1583 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->btn_user, TRUE, TRUE, 0);
1584 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->colorpicker, TRUE, TRUE, 0);
1585 gtk_box_pack_end(GTK_BOX(g->buttonbar), g->btn_asshot, TRUE, TRUE, 0);
1586 gtk_box_pack_start(box_enabled, g->buttonbar, TRUE, TRUE, 0);
1587
1589 dt_bauhaus_widget_set_label(g->presets, N_("settings")); // relabel to settings to remove confusion between module presets and white balance settings
1590 gtk_widget_set_tooltip_text(g->presets, _("choose white balance setting"));
1591 gtk_box_pack_start(box_enabled, g->presets, TRUE, TRUE, 0);
1592
1593 g->mod_temp = NAN;
1594 for(int k = 0; k < 4; k++)
1595 {
1596 g->daylight_wb[k] = 1.0;
1597 g->as_shot_wb[k] = 1.f;
1598 }
1599
1600 GtkWidget *temp_label_box = gtk_event_box_new();
1601 g->temp_label = dt_ui_section_label_new(_("scene illuminant temp"));
1602 gtk_widget_set_tooltip_text(g->temp_label, _("click to cycle color mode on sliders"));
1603 gtk_container_add(GTK_CONTAINER(temp_label_box), g->temp_label);
1604
1605 gtk_box_pack_start(box_enabled, temp_label_box, TRUE, TRUE, 0);
1606
1607 //Match UI order: temp first, then tint (like every other app ever)
1609 0, 5000.0, 0, feedback);
1610 dt_bauhaus_slider_set_format(g->scale_k, " K");
1611 dt_bauhaus_widget_set_label(g->scale_k, N_("temperature"));
1612 gtk_widget_set_tooltip_text(g->scale_k, _("color temperature (in Kelvin)"));
1613 gtk_box_pack_start(box_enabled, g->scale_k, TRUE, TRUE, 0);
1614
1616 0, 1.0, 3, feedback);
1617 dt_bauhaus_widget_set_label(g->scale_tint, N_("tint"));
1618 gtk_widget_set_tooltip_text(g->scale_tint, _("color tint of the image, from magenta (value < 1) to green (value > 1)"));
1619 gtk_box_pack_start(box_enabled, g->scale_tint, TRUE, TRUE, 0);
1620
1622 (&g->cs,
1623 "plugins/darkroom/temperature/expand_coefficients",
1624 _("channel coefficients"),
1625 GTK_BOX(box_enabled), GTK_PACK_END);
1626
1627 self->gui->widget = GTK_WIDGET(g->cs.container);
1628
1629 g->scale_r = dt_bauhaus_slider_from_params(self, N_("red"));
1630 g->scale_g = dt_bauhaus_slider_from_params(self, N_("green"));
1631 g->scale_b = dt_bauhaus_slider_from_params(self, N_("blue"));
1632 g->scale_g2 = dt_bauhaus_slider_from_params(self, "g2");
1633 dt_bauhaus_slider_set_digits(g->scale_r, 3);
1634 dt_bauhaus_slider_set_digits(g->scale_g, 3);
1635 dt_bauhaus_slider_set_digits(g->scale_b, 3);
1636 dt_bauhaus_slider_set_digits(g->scale_g2, 3);
1637
1638 gtk_widget_set_no_show_all(g->scale_g2, TRUE);
1639
1640 g_signal_connect(G_OBJECT(g->scale_k), "value-changed", G_CALLBACK(temp_tint_callback), self);
1641 g_signal_connect(G_OBJECT(g->scale_tint), "value-changed", G_CALLBACK(temp_tint_callback), self);
1642
1643 g_signal_connect(G_OBJECT(g->presets), "value-changed", G_CALLBACK(preset_tune_callback), self);
1644
1645 // start building top level widget
1646 self->gui->widget = gtk_stack_new();
1647 gtk_stack_set_homogeneous(GTK_STACK(self->gui->widget), FALSE);
1648
1649 GtkWidget *label_disabled = gtk_label_new(_("white balance disabled for camera"));
1650 gtk_widget_set_halign(label_disabled, GTK_ALIGN_START);
1651 gtk_label_set_ellipsize(GTK_LABEL(label_disabled), PANGO_ELLIPSIZE_END);
1652
1653 gtk_stack_add_named(GTK_STACK(self->gui->widget), GTK_WIDGET(box_enabled), "enabled");
1654 gtk_stack_add_named(GTK_STACK(self->gui->widget), label_disabled, "disabled");
1655}
1656
1658{
1660
1662}
1663
1664void gui_reset(struct dt_iop_module_t *self)
1665{
1667
1668 const int preset = dt_bauhaus_combobox_get(g->presets);
1670
1671 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_asshot), preset == DT_IOP_TEMP_AS_SHOT);
1672 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_user), preset == DT_IOP_TEMP_USER);
1673 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->btn_d65), preset == DT_IOP_TEMP_D65);
1674
1675 color_rgb_sliders(self);
1677}
1678
1679// clang-format off
1680// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1681// vim: shiftwidth=2 expandtab tabstop=2 cindent
1682// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1683// 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:3343
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:1997
void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
Definition bauhaus.c:2153
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:2161
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
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:1638
void dt_bauhaus_slider_set_feedback(GtkWidget *widget, int feedback)
Definition bauhaus.c:3389
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
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:1824
#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_log(const char *msg,...)
Definition control.c:824
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
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:140
#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:1893
@ 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
@ 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 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:2554
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
dt_iop_buffer_dsc_t dsc_out
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
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:384
char camera_makermodel[128]
Definition image.h:382
int32_t flags
Definition image.h:401
float d65_color_matrix[9]
Definition image.h:421
dt_iop_buffer_dsc_t dsc
Definition image.h:419
float adobe_XYZ_to_CAM[4][3]
Definition image.h:449
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
int32_t id
Definition image.h:401
dt_aligned_pixel_t wb_coeffs
Definition image.h:446
dt_aligned_pixel_t coeffs
Definition format.h:110
uint32_t filters
Definition format.h:89
unsigned int channels
Definition format.h:83
uint8_t xtrans[6][6]
Definition format.h:99
dt_aligned_pixel_t processed_maximum
Definition format.h:114
struct dt_iop_buffer_dsc_t::@56 temperature
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:97
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
Definition temperature.c:99
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:86
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)