Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorprimaries.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
21#include "develop/iop_profile.h"
22#endif
23
24#include <math.h>
25#include <stdlib.h>
26#include <string.h>
27
28#include "widgets/bauhaus.h"
30#include "system/macros.h"
31#include "system/openmp.h"
33#include "system/mem_alloc.h"
34#include "system/simd.h"
35#include "common/logging.h"
36#include "common/times.h"
38#include "common/imagebuf.h"
39#include "gui/lut_viewer.h"
40#include "develop/develop.h"
41#include "develop/imageop.h"
42#include "develop/imageop_gui.h"
44#include "iop/iop_api.h"
45#include "widgets/label.h"
46#include "widgets/notebook.h"
48
50
51#define DT_IOP_COLORPRIMARIES_NODE_COUNT 6
52#define DT_IOP_COLORPRIMARIES_EDGE_COUNT 6
53#define DT_IOP_COLORPRIMARIES_RADIAL_COUNT (DT_IOP_COLORPRIMARIES_NODE_COUNT + DT_IOP_COLORPRIMARIES_EDGE_COUNT)
54#define DT_IOP_COLORPRIMARIES_BLACK_WHITE_COUNT (2 * DT_IOP_COLORPRIMARIES_NODE_COUNT)
55#define DT_IOP_COLORPRIMARIES_AXIS_ANCHORS 64
56#define DT_IOP_COLORPRIMARIES_MAX_ANCHORS \
57 (DT_IOP_COLORPRIMARIES_NODE_COUNT + DT_IOP_COLORPRIMARIES_EDGE_COUNT + DT_IOP_COLORPRIMARIES_RADIAL_COUNT \
58 + DT_IOP_COLORPRIMARIES_BLACK_WHITE_COUNT + DT_IOP_COLORPRIMARIES_AXIS_ANCHORS)
59#define DT_IOP_COLORPRIMARIES_VIEWER_CONTROL_NODES \
60 (DT_IOP_COLORPRIMARIES_NODE_COUNT + DT_IOP_COLORPRIMARIES_EDGE_COUNT + DT_IOP_COLORPRIMARIES_RADIAL_COUNT \
61 + DT_IOP_COLORPRIMARIES_BLACK_WHITE_COUNT)
62#define DT_IOP_COLORPRIMARIES_SQRT3 1.7320508075688772f
63#define DT_IOP_COLORPRIMARIES_INV_SQRT2 0.7071067811865475f
64
66{
67 DT_IOP_COLORPRIMARIES_TETRAHEDRAL = 0, // $DESCRIPTION: "tetrahedral"
68 DT_IOP_COLORPRIMARIES_TRILINEAR = 1, // $DESCRIPTION: "trilinear"
69 DT_IOP_COLORPRIMARIES_PYRAMID = 2, // $DESCRIPTION: "pyramid"
71
81
87
89{
90 float white_level; // $MIN: -2.0 $MAX: 16.0 $DEFAULT: 1.0 $DESCRIPTION: "white level"
91 float gamut_coverage; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 67.0 $DESCRIPTION: "gamut coverage"
92 float sigma_L; // $MIN: 1.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "brightness smoothing"
93 float sigma_rho; // $MIN: 0.01 $MAX: 2.0 $DEFAULT: 0.70710678 $DESCRIPTION: "saturation smoothing"
94 float sigma_theta; // $MIN: 0.01 $MAX: 6.28318531 $DEFAULT: 0.70710678 $DESCRIPTION: "hue smoothing"
95 float neutral_protection; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "neutral protection"
96 dt_iop_colorprimaries_interpolation_t interpolation; // $DEFAULT: DT_IOP_COLORPRIMARIES_TETRAHEDRAL $DESCRIPTION: "interpolation"
97 float hue[DT_IOP_COLORPRIMARIES_NODE_COUNT]; // $MIN: -180.0 $MAX: 180.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
98 float saturation[DT_IOP_COLORPRIMARIES_NODE_COUNT]; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation"
99 float brightness[DT_IOP_COLORPRIMARIES_NODE_COUNT]; // $MIN: -0.25 $MAX: 0.25 $DEFAULT: 0.0 $DESCRIPTION: "brightness"
101
103{
104 float *clut;
105 uint16_t clut_level;
110 // Params that `clut` was last built from, for this piece alone. The LUT is expensive
111 // enough to be worth not rebuilding on every commit, but it must never be shared across
112 // pieces/instances: see the multi-instance cache-bleed writeup for this module.
114 gboolean clut_valid;
116
118{
119 // Truly global, read-only-after-init state only. Do NOT add a shared LUT/params cache
120 // here: this struct is the same instance for every module instance, every piece, and
121 // every pipe (full/preview/thumbnail/export) of colorprimaries in the
122 // whole application -- there is exactly one of it, not one per instance. A memoized LUT
123 // stored here can only ever hold one instance's content at a time, and gets silently
124 // overwritten by whichever piece/instance last committed -- including disabled instances,
125 // since commit_params runs for disabled pieces too.
131
155
156const char *name()
157{
158 return _("color primaries");
159}
160
161const char *aliases()
162{
163 return _("RGB primaries|primary colors");
164}
165
166const char **description(struct dt_iop_module_t *self)
167{
168 return dt_iop_set_description(self,
169 _("edit RGB/CYM primary control nodes in dt UCS and interpolate their RGB shifts "
170 "through a cylindrical local field"),
171 _("creative"), _("linear, RGB, display-referred"), _("linear, RGB"),
172 _("linear, RGB, display-referred"));
173}
174
176{
177 return IOP_GROUP_COLOR;
178}
179
184
186{
187 return IOP_CS_RGB;
188}
189
190static void _update_gui_lut_cache(dt_iop_module_t *self);
191
200
201static inline const char *_node_name(const dt_iop_colorprimaries_node_t node)
202{
203 switch(node)
204 {
206 return _("yellow");
208 return _("green");
210 return _("cyan");
212 return _("blue");
214 return _("magenta");
216 default:
217 return _("red");
218 }
219}
220
222{
223 memset(p, 0, sizeof(*p));
224 p->white_level = 1.f;
225 p->gamut_coverage = 67.f;
226 p->sigma_L = 100.f;
228 p->sigma_theta = DT_IOP_COLORPRIMARIES_INV_SQRT2;
229 p->neutral_protection = 0.f;
230 p->interpolation = DT_IOP_COLORPRIMARIES_TETRAHEDRAL;
231}
232
233static inline gboolean _lut_fields_equal(const dt_iop_colorprimaries_params_t *const a,
234 const dt_iop_colorprimaries_params_t *const b)
235{
236 return !memcmp(a, b, sizeof(*a));
237}
238
240{
241 switch(node)
242 {
244 RGB[0] = 1.f;
245 RGB[1] = 1.f;
246 RGB[2] = 0.f;
247 break;
249 RGB[0] = 0.f;
250 RGB[1] = 1.f;
251 RGB[2] = 0.f;
252 break;
254 RGB[0] = 0.f;
255 RGB[1] = 1.f;
256 RGB[2] = 1.f;
257 break;
259 RGB[0] = 0.f;
260 RGB[1] = 0.f;
261 RGB[2] = 1.f;
262 break;
264 RGB[0] = 1.f;
265 RGB[1] = 0.f;
266 RGB[2] = 1.f;
267 break;
269 default:
270 RGB[0] = 1.f;
271 RGB[1] = 0.f;
272 RGB[2] = 0.f;
273 break;
274 }
275 RGB[3] = 0.f;
276}
277
278static inline void _node_base_rgb(const dt_iop_colorprimaries_node_t node, const float gamut_coverage, dt_aligned_pixel_t RGB)
279{
280 dt_aligned_pixel_t corner = { 0.f };
281 _node_corner_rgb(node, corner);
282
283 const float mu = (corner[0] + corner[1] + corner[2]) / 3.f;
284 for_each_channel(c, aligned(RGB, corner)) RGB[c] = mu + gamut_coverage * (corner[c] - mu);
285 RGB[3] = 0.f;
286}
287
288static inline float _mix_hue_delta_weighted(const float hue_a, const float hue_b, const float weight_a,
289 const float weight_b)
290{
291 const float angle_a = hue_a * M_PI_F / 180.f;
292 const float angle_b = hue_b * M_PI_F / 180.f;
293 return atan2f(weight_a * sinf(angle_a) + weight_b * sinf(angle_b),
294 weight_a * cosf(angle_a) + weight_b * cosf(angle_b));
295}
296
297static inline float _hsb_distance(const dt_aligned_pixel_t a, const dt_aligned_pixel_t b)
298{
299 const float dh = dt_colorrings_wrap_pi(a[0] - b[0]);
300 const float ds = a[1] - b[1];
301 const float db = a[2] - b[2];
302 return sqrtf(dh * dh + ds * ds + db * db);
303}
304
305static inline void _black_white_rgb(const gboolean white, dt_aligned_pixel_t RGB)
306{
307 RGB[0] = white ? 1.f : 0.f;
308 RGB[1] = white ? 1.f : 0.f;
309 RGB[2] = white ? 1.f : 0.f;
310 RGB[3] = 0.f;
311}
312
313static void _halfway_to_axis_rgb(const dt_aligned_pixel_t source_rgb, dt_aligned_pixel_t halfway_rgb)
314{
315 float L = 0.f;
316 float rho = 0.f;
317 float theta = 0.f;
318 dt_colorrings_rgb_to_gray_cyl(source_rgb, &L, &rho, &theta);
319 dt_colorrings_gray_basis_to_rgb(L, 0.5f * rho * cosf(theta), 0.5f * rho * sinf(theta), halfway_rgb);
320 halfway_rgb[3] = 0.f;
321}
322
323static gboolean _build_anchor_from_source_rgb(const dt_aligned_pixel_t source_rgb, const float hue_delta,
324 const float saturation_delta, const float brightness_delta,
325 const dt_iop_order_iccprofile_info_t *const lut_profile,
326 dt_colorrings_sparse_anchor_t *const anchor)
327{
328 const float white = dt_colorrings_graph_white();
329 dt_aligned_pixel_t source_hsb = { 0.f };
330 dt_aligned_pixel_t source_axis = { 0.f };
331 dt_aligned_pixel_t target_axis = { 0.f };
332 float source_L = 0.f;
333 float source_rho = 0.f;
334 float source_theta = 0.f;
335 float source_axis_L = 0.f;
336 float target_axis_L = 0.f;
337 float unused_rho = 0.f;
338 float unused_theta = 0.f;
339 float source_brightness = 0.f;
340 float target_brightness = 0.f;
341 float requested_scale = 1.f;
342
343 dt_colorrings_profile_rgb_to_dt_ucs_hsb(source_rgb, white, lut_profile, source_hsb);
344 dt_colorrings_rgb_to_gray_cyl(source_rgb, &source_L, &source_rho, &source_theta);
345
346 if(source_rho <= 1e-6f) return FALSE;
347
359 source_brightness = CLAMP(source_hsb[2], 0.f, 1.f);
360 target_brightness = CLAMP(source_brightness + brightness_delta, 0.f, 1.f);
361 dt_colorrings_brightness_to_axis_rgb(source_brightness, white, lut_profile, source_axis);
362 dt_colorrings_rgb_to_gray_cyl(source_axis, &source_axis_L, &unused_rho, &unused_theta);
363 dt_colorrings_brightness_to_axis_rgb(target_brightness, white, lut_profile, target_axis);
364 dt_colorrings_rgb_to_gray_cyl(target_axis, &target_axis_L, &unused_rho, &unused_theta);
365
366 if(source_hsb[1] > 1e-6f)
367 requested_scale = fmaxf(source_hsb[1] + saturation_delta, 0.f) / source_hsb[1];
368
369 anchor->L = source_L;
370 anchor->rho = source_rho;
371 anchor->theta = source_theta;
372 anchor->delta_L = target_axis_L - source_axis_L;
373 anchor->chroma_scale = requested_scale;
374 anchor->delta_theta = dt_colorrings_wrap_pi(hue_delta);
375 anchor->weight = 1.f;
376 return TRUE;
377}
378
380 const float hue_delta, const float saturation_delta,
381 const float brightness_delta,
382 const dt_iop_order_iccprofile_info_t *const lut_profile,
383 dt_colorrings_sparse_anchor_t *const anchor,
384 dt_aligned_pixel_t halfway_rgb)
385{
386 dt_aligned_pixel_t source_hsb = { 0.f };
387 dt_aligned_pixel_t axis_rgb = { 0.f };
388 dt_aligned_pixel_t axis_hsb = { 0.f };
389 dt_aligned_pixel_t halfway_hsb = { 0.f };
390 float distance_source = 0.f;
391 float distance_axis = 0.f;
392 float weight_source = 0.5f;
393 float source_L = 0.f;
394 float source_rho = 0.f;
395 float source_theta = 0.f;
396
397 dt_colorrings_rgb_to_gray_cyl(source_rgb, &source_L, &source_rho, &source_theta);
398 if(source_rho <= 1e-6f) return FALSE;
399
400 _halfway_to_axis_rgb(source_rgb, halfway_rgb);
401 dt_colorrings_gray_axis_rgb_from_L(source_L, axis_rgb);
402
403 dt_colorrings_profile_rgb_to_dt_ucs_hsb(source_rgb, dt_colorrings_graph_white(), lut_profile, source_hsb);
404 dt_colorrings_profile_rgb_to_dt_ucs_hsb(axis_rgb, dt_colorrings_graph_white(), lut_profile, axis_hsb);
405 dt_colorrings_profile_rgb_to_dt_ucs_hsb(halfway_rgb, dt_colorrings_graph_white(), lut_profile, halfway_hsb);
406
413 axis_hsb[0] = source_hsb[0];
414 distance_source = _hsb_distance(halfway_hsb, source_hsb);
415 distance_axis = _hsb_distance(halfway_hsb, axis_hsb);
416 if(distance_source + distance_axis > 1e-6f)
417 weight_source = distance_axis / (distance_source + distance_axis);
418
419 return _build_anchor_from_source_rgb(halfway_rgb, weight_source * hue_delta, weight_source * saturation_delta,
420 weight_source * brightness_delta, lut_profile, anchor);
421}
422
424 const dt_aligned_pixel_t extreme_rgb,
425 const float hue_delta, const float saturation_delta,
426 const float brightness_delta,
427 const dt_iop_order_iccprofile_info_t *const lut_profile,
428 dt_colorrings_sparse_anchor_t *const anchor,
429 dt_aligned_pixel_t halfway_rgb)
430{
431 dt_aligned_pixel_t source_hsb = { 0.f };
432 dt_aligned_pixel_t extreme_hsb = { 0.f };
433 dt_aligned_pixel_t halfway_hsb = { 0.f };
434 float distance_source = 0.f;
435 float distance_extreme = 0.f;
436 float weight_source = 0.5f;
437
438 for(int c = 0; c < 3; c++) halfway_rgb[c] = 0.5f * (source_rgb[c] + extreme_rgb[c]);
439 halfway_rgb[3] = 0.f;
440
441 dt_colorrings_profile_rgb_to_dt_ucs_hsb(source_rgb, dt_colorrings_graph_white(), lut_profile, source_hsb);
442 dt_colorrings_profile_rgb_to_dt_ucs_hsb(extreme_rgb, dt_colorrings_graph_white(), lut_profile, extreme_hsb);
443 dt_colorrings_profile_rgb_to_dt_ucs_hsb(halfway_rgb, dt_colorrings_graph_white(), lut_profile, halfway_hsb);
444
445 extreme_hsb[0] = source_hsb[0];
446 distance_source = _hsb_distance(halfway_hsb, source_hsb);
447 distance_extreme = _hsb_distance(halfway_hsb, extreme_hsb);
448 if(distance_source + distance_extreme > 1e-6f)
449 weight_source = distance_extreme / (distance_source + distance_extreme);
450
451 return _build_anchor_from_source_rgb(halfway_rgb, weight_source * hue_delta, weight_source * saturation_delta,
452 weight_source * brightness_delta, lut_profile, anchor);
453}
454
455static void _apply_anchor_to_rgb(const dt_aligned_pixel_t source_rgb, const dt_colorrings_sparse_anchor_t *const anchor,
456 dt_aligned_pixel_t target_rgb)
457{
458 dt_aligned_pixel_t axis = { 0.f };
459 float source_L = 0.f;
460 float source_rho = 0.f;
461 float source_theta = 0.f;
462
463 dt_colorrings_rgb_to_gray_cyl(source_rgb, &source_L, &source_rho, &source_theta);
464 dt_colorrings_gray_basis_to_rgb(source_L + anchor->delta_L,
465 source_rho * anchor->chroma_scale * cosf(source_theta + anchor->delta_theta),
466 source_rho * anchor->chroma_scale * sinf(source_theta + anchor->delta_theta),
467 target_rgb);
468 dt_colorrings_gray_axis_rgb_from_L(source_L + anchor->delta_L, axis);
469 dt_colorrings_project_to_cube_shell(axis, target_rgb);
470 target_rgb[3] = 0.f;
471}
472
474 const dt_iop_order_iccprofile_info_t *const lut_profile,
475 dt_colorrings_sparse_anchor_t *const anchor)
476{
477 dt_aligned_pixel_t source_rgb = { 0.f };
478 _node_base_rgb(node, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), source_rgb);
479 return _build_anchor_from_source_rgb(source_rgb, p->hue[node] * M_PI_F / 180.f, p->saturation[node] * 0.01f,
480 p->brightness[node], lut_profile, anchor);
481}
482
484 const dt_iop_order_iccprofile_info_t *const lut_profile, dt_aligned_pixel_t source_rgb,
485 float *const hue_delta, float *const saturation_delta,
486 float *const brightness_delta)
487{
488 dt_aligned_pixel_t source_a = { 0.f };
489 dt_aligned_pixel_t source_b = { 0.f };
490 dt_aligned_pixel_t hsb_a = { 0.f };
491 dt_aligned_pixel_t hsb_b = { 0.f };
492 dt_aligned_pixel_t hsb_mid = { 0.f };
493 float distance_a = 0.f;
494 float distance_b = 0.f;
495 float weight_a = 0.5f;
496 float weight_b = 0.5f;
497
498 _node_base_rgb(edge.a, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), source_a);
499 _node_base_rgb(edge.b, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), source_b);
500
512 for_each_channel(c, aligned(source_a, source_b, source_rgb)) source_rgb[c] = 0.5f * (source_a[c] + source_b[c]);
513 source_rgb[3] = 0.f;
516 dt_colorrings_profile_rgb_to_dt_ucs_hsb(source_rgb, dt_colorrings_graph_white(), lut_profile, hsb_mid);
517
518 distance_a = _hsb_distance(hsb_mid, hsb_a);
519 distance_b = _hsb_distance(hsb_mid, hsb_b);
520 if(distance_a + distance_b > 1e-6f)
521 {
522 weight_a = distance_b / (distance_a + distance_b);
523 weight_b = distance_a / (distance_a + distance_b);
524 }
525
526 *hue_delta = _mix_hue_delta_weighted(p->hue[edge.a], p->hue[edge.b], weight_a, weight_b);
527 *saturation_delta = (weight_a * p->saturation[edge.a] + weight_b * p->saturation[edge.b]) * 0.01f;
528 *brightness_delta = weight_a * p->brightness[edge.a] + weight_b * p->brightness[edge.b];
529 return TRUE;
530}
531
532static inline void _store_viewer_control_node(dt_lut_viewer_control_node_t *const control_nodes, int *const count,
533 const dt_aligned_pixel_t input_rgb, const dt_aligned_pixel_t output_rgb)
534{
535 for(int c = 0; c < 3; c++)
536 {
537 control_nodes[*count].input_rgb[c] = input_rgb[c];
538 control_nodes[*count].output_rgb[c] = output_rgb[c];
539 }
540 (*count)++;
541}
542
543static inline void _append_anchor(dt_colorrings_sparse_anchor_t *const anchors, int *const anchor_count,
544 const dt_colorrings_sparse_anchor_t *const anchor)
545{
546 anchors[*anchor_count] = *anchor;
547 (*anchor_count)++;
548}
549
551 const dt_iop_order_iccprofile_info_t *const lut_profile,
552 dt_colorrings_sparse_anchor_t *const anchor)
553{
554 dt_aligned_pixel_t source_rgb = { 0.f };
555 float hue_delta = 0.f;
556 float saturation_delta = 0.f;
557 float brightness_delta = 0.f;
558
559 if(!_build_edge_edit(p, edge, lut_profile, source_rgb, &hue_delta, &saturation_delta, &brightness_delta))
560 return FALSE;
561
562 return _build_anchor_from_source_rgb(source_rgb, hue_delta, saturation_delta, brightness_delta, lut_profile, anchor);
563}
564
567 const dt_iop_order_iccprofile_info_t *const lut_profile,
568 dt_colorrings_sparse_anchor_t *const anchor,
569 dt_aligned_pixel_t midpoint_rgb)
570{
571 dt_aligned_pixel_t source_rgb = { 0.f };
572 _node_base_rgb(node, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), source_rgb);
573 return _build_halfway_radial_anchor_from_source_rgb(source_rgb, p->hue[node] * M_PI_F / 180.f,
574 p->saturation[node] * 0.01f, p->brightness[node],
575 lut_profile, anchor, midpoint_rgb);
576}
577
580 const gboolean toward_white,
581 const dt_iop_order_iccprofile_info_t *const lut_profile,
582 dt_colorrings_sparse_anchor_t *const anchor,
583 dt_aligned_pixel_t midpoint_rgb)
584{
585 dt_aligned_pixel_t source_rgb = { 0.f };
586 dt_aligned_pixel_t extreme_rgb = { 0.f };
587 _node_base_rgb(node, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), source_rgb);
588 _black_white_rgb(toward_white, extreme_rgb);
589 return _build_halfway_extreme_anchor_from_source_rgb(source_rgb, extreme_rgb, p->hue[node] * M_PI_F / 180.f,
590 p->saturation[node] * 0.01f, p->brightness[node],
591 lut_profile, anchor, midpoint_rgb);
592}
593
596 const dt_iop_order_iccprofile_info_t *const lut_profile,
597 dt_colorrings_sparse_anchor_t *const anchor,
598 dt_aligned_pixel_t midpoint_rgb)
599{
600 dt_aligned_pixel_t source_rgb = { 0.f };
601 float hue_delta = 0.f;
602 float saturation_delta = 0.f;
603 float brightness_delta = 0.f;
604
605 if(!_build_edge_edit(p, edge, lut_profile, source_rgb, &hue_delta, &saturation_delta, &brightness_delta))
606 return FALSE;
607
608 return _build_halfway_radial_anchor_from_source_rgb(source_rgb, hue_delta, saturation_delta, brightness_delta,
609 lut_profile, anchor, midpoint_rgb);
610}
611
613 const dt_iop_order_iccprofile_info_t *const lut_profile,
614 dt_lut_viewer_control_node_t *const control_nodes)
615{
616 int count = 0;
617
618 if(IS_NULL_PTR(control_nodes) || IS_NULL_PTR(lut_profile)) return 0;
619
620 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
621 {
622 dt_colorrings_sparse_anchor_t anchor = { 0.f };
623 dt_aligned_pixel_t source_rgb = { 0.f };
624 dt_aligned_pixel_t target_rgb = { 0.f };
625
626 _node_base_rgb((dt_iop_colorprimaries_node_t)node, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), source_rgb);
627 if(!_build_node_anchor(p, (dt_iop_colorprimaries_node_t)node, lut_profile, &anchor)) continue;
628 _apply_anchor_to_rgb(source_rgb, &anchor, target_rgb);
629 _store_viewer_control_node(control_nodes, &count, source_rgb, target_rgb);
630 }
631
632 for(int edge = 0; edge < DT_IOP_COLORPRIMARIES_EDGE_COUNT; edge++)
633 {
634 dt_colorrings_sparse_anchor_t anchor = { 0.f };
635 dt_aligned_pixel_t source_rgb = { 0.f };
636 dt_aligned_pixel_t target_rgb = { 0.f };
637 float hue_delta = 0.f;
638 float saturation_delta = 0.f;
639 float brightness_delta = 0.f;
640
641 if(!_build_edge_edit(p, _chroma_edges[edge], lut_profile, source_rgb, &hue_delta, &saturation_delta, &brightness_delta))
642 continue;
643 if(!_build_anchor_from_source_rgb(source_rgb, hue_delta, saturation_delta, brightness_delta, lut_profile, &anchor))
644 continue;
645 _apply_anchor_to_rgb(source_rgb, &anchor, target_rgb);
646 _store_viewer_control_node(control_nodes, &count, source_rgb, target_rgb);
647 }
648
649 for(int white = 0; white < 2; white++)
650 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
651 {
652 dt_colorrings_sparse_anchor_t anchor = { 0.f };
653 dt_aligned_pixel_t midway_rgb = { 0.f };
654 dt_aligned_pixel_t target_rgb = { 0.f };
655
656 if(!_build_node_black_white_midpoint_anchor(p, (dt_iop_colorprimaries_node_t)node, white != 0, lut_profile, &anchor,
657 midway_rgb))
658 continue;
659 _apply_anchor_to_rgb(midway_rgb, &anchor, target_rgb);
660 _store_viewer_control_node(control_nodes, &count, midway_rgb, target_rgb);
661 }
662
663 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
664 {
665 dt_colorrings_sparse_anchor_t anchor = { 0.f };
666 dt_aligned_pixel_t inner_source_rgb = { 0.f };
667 dt_aligned_pixel_t inner_target_rgb = { 0.f };
668
669 if(!_build_node_radial_midpoint_anchor(p, (dt_iop_colorprimaries_node_t)node, lut_profile, &anchor, inner_source_rgb))
670 continue;
671 _apply_anchor_to_rgb(inner_source_rgb, &anchor, inner_target_rgb);
672 _store_viewer_control_node(control_nodes, &count, inner_source_rgb, inner_target_rgb);
673 }
674
675 for(int edge = 0; edge < DT_IOP_COLORPRIMARIES_EDGE_COUNT; edge++)
676 {
677 dt_colorrings_sparse_anchor_t anchor = { 0.f };
678 dt_aligned_pixel_t inner_source_rgb = { 0.f };
679 dt_aligned_pixel_t inner_target_rgb = { 0.f };
680 if(!_build_edge_radial_midpoint_anchor(p, _chroma_edges[edge], lut_profile, &anchor, inner_source_rgb))
681 continue;
682 _apply_anchor_to_rgb(inner_source_rgb, &anchor, inner_target_rgb);
683 _store_viewer_control_node(control_nodes, &count, inner_source_rgb, inner_target_rgb);
684 }
685
686 return count;
687}
688
690 const dt_iop_order_iccprofile_info_t *const lut_profile, dt_aligned_pixel_t HSB)
691{
692 dt_aligned_pixel_t RGB = { 0.f };
693 _node_base_rgb(node, CLAMP(p->gamut_coverage * 0.01f, 0.f, 1.f), RGB);
695}
696
698 const dt_iop_order_iccprofile_info_t *const lut_profile, dt_aligned_pixel_t HSB)
699{
700 dt_aligned_pixel_t source_hsb = { 0.f };
701 _node_source_hsb(p, node, lut_profile, source_hsb);
702
703 HSB[0] = dt_colorrings_wrap_hue_pi(source_hsb[0] + p->hue[node] * M_PI_F / 180.f);
704 HSB[1] = CLAMP(source_hsb[1] + p->saturation[node] * 0.01f, 0.f, 1.f);
705 HSB[2] = CLAMP(source_hsb[2] + p->brightness[node], 0.f, 1.f);
706 HSB[3] = 0.f;
707}
708
710 const dt_iop_order_iccprofile_info_t *lut_profile)
711{
712 const gboolean log_perf = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
713 const double start = log_perf ? dt_get_wtime() : 0.0;
714 const size_t clut_size = (size_t)DT_COLORRINGS_CLUT_LEVEL * DT_COLORRINGS_CLUT_LEVEL * DT_COLORRINGS_CLUT_LEVEL * 3u;
715 const float inv_sigma_L = 1.f / fmaxf(p->sigma_L * 0.01f, 1e-6f);
716 const float inv_sigma_rho = 1.f / fmaxf(p->sigma_rho, 1e-6f);
717 const float inv_sigma_theta = 1.f / fmaxf(p->sigma_theta, 1e-6f);
719 int anchor_count = 0;
720
721 if(IS_NULL_PTR(d->clut)) d->clut = dt_alloc_align_float(clut_size);
722 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
723
732 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
733 {
734 dt_colorrings_sparse_anchor_t anchor = { 0.f };
735 if(_build_node_anchor(p, (dt_iop_colorprimaries_node_t)node, lut_profile, &anchor))
736 _append_anchor(anchors, &anchor_count, &anchor);
737 }
738
739 for(int edge = 0; edge < DT_IOP_COLORPRIMARIES_EDGE_COUNT; edge++)
740 {
741 dt_colorrings_sparse_anchor_t anchor = { 0.f };
742 if(_build_edge_anchor(p, _chroma_edges[edge], lut_profile, &anchor))
743 _append_anchor(anchors, &anchor_count, &anchor);
744 }
745
746 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
747 {
748 dt_colorrings_sparse_anchor_t inner_anchor = { 0.f };
749 dt_aligned_pixel_t inner_source_rgb = { 0.f };
750 if(_build_node_radial_midpoint_anchor(p, (dt_iop_colorprimaries_node_t)node, lut_profile, &inner_anchor,
751 inner_source_rgb))
752 _append_anchor(anchors, &anchor_count, &inner_anchor);
753 }
754
755 for(int edge = 0; edge < DT_IOP_COLORPRIMARIES_EDGE_COUNT; edge++)
756 {
757 dt_colorrings_sparse_anchor_t inner_anchor = { 0.f };
758 dt_aligned_pixel_t inner_source_rgb = { 0.f };
759 if(_build_edge_radial_midpoint_anchor(p, _chroma_edges[edge], lut_profile, &inner_anchor, inner_source_rgb))
760 _append_anchor(anchors, &anchor_count, &inner_anchor);
761 }
762
763 for(int white = 0; white < 2; white++)
764 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
765 {
766 dt_colorrings_sparse_anchor_t extreme_anchor = { 0.f };
767 dt_aligned_pixel_t midway_rgb = { 0.f };
769 &extreme_anchor, midway_rgb))
770 _append_anchor(anchors, &anchor_count, &extreme_anchor);
771 }
772
773 for(int sample = 0; sample < DT_IOP_COLORPRIMARIES_AXIS_ANCHORS; sample++)
774 {
775 const float value = (float)sample / (float)(DT_IOP_COLORPRIMARIES_AXIS_ANCHORS - 1);
776 anchors[anchor_count].L = value * DT_IOP_COLORPRIMARIES_SQRT3;
777 anchors[anchor_count].rho = 0.f;
778 anchors[anchor_count].theta = 0.f;
779 anchors[anchor_count].delta_L = 0.f;
780 anchors[anchor_count].chroma_scale = 1.f;
781 anchors[anchor_count].delta_theta = 0.f;
782 anchors[anchor_count].weight = 1.f / (float)DT_IOP_COLORPRIMARIES_AXIS_ANCHORS;
783 anchor_count++;
784 }
785
786 dt_colorrings_fill_lut_sparse_local_field(d->clut, DT_COLORRINGS_CLUT_LEVEL, anchors, anchor_count, inv_sigma_L,
787 inv_sigma_rho, inv_sigma_theta, fmaxf(p->neutral_protection, 1e-6f));
788 d->clut_level = DT_COLORRINGS_CLUT_LEVEL;
789
790 if(log_perf)
791 dt_print(DT_DEBUG_PERF, "[colorprimaries] build_clut level=%u anchors=%d total=%.3fms\n", d->clut_level,
792 anchor_count, 1000.0 * (dt_get_wtime() - start));
793}
794
797{
798 /* Nothing to prepare for a piece that will not run. dt_iop_commit_params() commits DISABLED
799 * modules too -- deliberately, because some of them self-enable there on runtime context. This
800 * one does not, and process()/process_cl() are the only readers of piece->data, so nothing
801 * downstream can observe what we skip.
802 *
803 * The return has to come BEFORE the profile lookup below, not merely before the LUT build.
804 * dt_colorspaces_add_profile() memoises, but the FIRST caller pays for it, and in the whole
805 * pipeline DT_COLORSPACE_HLG_REC2020 is asked for by nothing except this module and iop/colorequal.c.
806 * On an image that enables neither, that profile -- two matrices plus six eagerly built
807 * 65536-entry LUTs, tone-curve inversion included -- was being constructed for nobody.
808 *
809 * Returning rather than clearing also keeps a LUT that was already built: toggle the module off
810 * and on again and the memo survives; only a real parameter change rebuilds.
811 *
812 * piece->enabled is set from history immediately before every dt_iop_commit_params() call
813 * (dev_pixelpipe.c 288, 1288, 1367; pixelpipe_hb.c:700 at node creation), so it is settled here.
814 */
815 if(!piece->enabled) return;
816
819 const dt_iop_order_iccprofile_info_t *lut_profile
821 : NULL;
823
824 if(IS_NULL_PTR(lut_profile) || IS_NULL_PTR(work_profile))
825 {
826 // Leave d->clut's allocation (if any) alone -- it is this piece's own buffer now,
827 // not a borrowed pointer. clut_level == 0 already makes process()/process_cl() treat
828 // it as not-ready; freeing/reallocating it is cleanup_pipe's job.
829 d->clut_level = 0;
830 d->clut_valid = FALSE;
831 d->white_level = 1.f;
832 d->lut_profile = NULL;
833 d->work_profile = NULL;
834 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
835 return;
836 }
837
838 // This LUT is expensive enough to be worth memoizing, but the cache must be scoped to
839 // this piece alone -- see the dt_iop_colorprimaries_global_data_t comment for why a
840 // shared cache silently bleeds one instance's content into another's.
841 if(!d->clut_valid || !_lut_fields_equal(&d->built_params, p))
842 {
843 _build_clut(d, p, lut_profile);
844 d->built_params = *p;
845 d->clut_valid = TRUE;
846 }
847
848 d->white_level = exp2f(p->white_level);
849 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
850 d->work_profile = (dt_iop_order_iccprofile_info_t *)work_profile;
851 d->interpolation = (dt_lut3d_interpolation_t)p->interpolation;
852}
853
855{
857 piece->data = d;
859 // Checked AFTER both piece->data and piece->data_size are set, deliberately.
860 // dt_iop_init_pipe() disables the node for us when it sees data_size > 0 with a NULL
861 // data -- returning before data_size is assigned skips that and leaves the node enabled
862 // with no storage. dt_calloc_align() returns NULL on failure and pipe nodes are built
863 // exactly when memory is tightest (Sentry 134134395 faulted in atrous's init_pipe).
864 if(IS_NULL_PTR(piece->data)) return;
865 d->white_level = 1.f;
866 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
867}
868
870{
871 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
872 if(IS_NULL_PTR(piece->data)) return;
874 dt_free_align(d->clut);
875 d->clut = NULL;
876 dt_free_align(piece->data);
877 piece->data = NULL;
878}
879
880#ifdef HAVE_OPENCL
881int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
882 cl_mem dev_in, cl_mem dev_out)
883{
886 const int width = piece->roi_in.width;
887 const int height = piece->roi_in.height;
888 const int devid = pipe->devid;
889 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
890 const float white_level = fmaxf(d->white_level, 1e-6f);
891 const float normalize = 1.f / white_level;
892 const float denormalize = white_level;
893 const float black = 0.f;
894 cl_mem clut_cl = NULL;
895 cl_int err = CL_SUCCESS;
896 const int kernel = (d->interpolation == DT_LUT3D_INTERP_TRILINEAR) ? gd->kernel_lut3d_trilinear
897 : (d->interpolation == DT_LUT3D_INTERP_PYRAMID) ? gd->kernel_lut3d_pyramid
899
900 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile)) return FALSE;
901
902 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_in);
903 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
904 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
905 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
906 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
907 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &normalize);
908 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
909 if(err != CL_SUCCESS) goto cleanup;
910
911 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->work_profile,
912 d->lut_profile, "colorequal work to HLG Rec2020"))
913 {
914 err = CL_INVALID_OPERATION;
915 goto cleanup;
916 }
917
918 clut_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3 * d->clut_level * d->clut_level * d->clut_level,
919 d->clut);
920 if(IS_NULL_PTR(clut_cl))
921 {
922 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
923 goto cleanup;
924 }
925
926 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dev_out);
927 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &dev_out);
928 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &width);
929 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &height);
930 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &clut_cl);
931 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &d->clut_level);
932 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
933 if(err != CL_SUCCESS) goto cleanup;
934
935 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->lut_profile,
936 d->work_profile, "colorequal HLG Rec2020 to work"))
937 {
938 err = CL_INVALID_OPERATION;
939 goto cleanup;
940 }
941
942 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_out);
943 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
944 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
945 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
946 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
947 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &denormalize);
948 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
949
950cleanup:
952 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_colorequal] couldn't enqueue kernel! %d\n", err);
953 return err == CL_SUCCESS;
954}
955#endif
956
958int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
959 const void *const ibuf, void *const obuf)
960{
962 const int width = piece->roi_in.width;
963 const int height = piece->roi_in.height;
964 const int ch = piece->dsc_in.channels;
965
966 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile))
967 {
969 return 0;
970 }
971
972 const float white_level = fmaxf(d->white_level, 1e-6f);
974 for(size_t k = 0; k < (size_t)width * height; k++)
975 {
976 const float *in = (const float *)ibuf + k * ch;
977 float *out = (float *)obuf + k * ch;
978 out[0] = in[0] / white_level;
979 out[1] = in[1] / white_level;
980 out[2] = in[2] / white_level;
981 if(ch > 3) out[3] = in[3];
982 }
983
984 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->work_profile, d->lut_profile,
985 "colorprimaries work to HLG Rec2020");
986 dt_lut3d_apply((float *)obuf, (float *)obuf, (size_t)width * height, d->clut, d->clut_level, 1.f, d->interpolation);
987 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->lut_profile, d->work_profile,
988 "colorprimaries HLG Rec2020 to work");
990 for(size_t k = 0; k < (size_t)width * height; k++)
991 {
992 float *out = (float *)obuf + k * ch;
993 out[0] *= white_level;
994 out[1] *= white_level;
995 out[2] *= white_level;
996 }
997
998 return 0;
999}
1000
1003{
1005 if(IS_NULL_PTR(work_profile))
1006 {
1007 memset(Ych, 0, sizeof(dt_aligned_pixel_t));
1008 return;
1009 }
1010
1011 dt_colorrings_profile_rgb_to_Ych(RGB, work_profile, Ych);
1012}
1013
1014static void _set_slider_stop_from_hsb(GtkWidget *slider, const float stop, const dt_aligned_pixel_t HSB,
1015 const dt_iop_order_iccprofile_info_t *display_profile)
1016{
1017 dt_aligned_pixel_t RGB = { 0.f };
1019 dt_bauhaus_slider_set_stop(slider, stop, RGB[0], RGB[1], RGB[2]);
1020}
1021
1022static void _set_slider_stop_from_profile_rgb(GtkWidget *slider, const float stop, const dt_aligned_pixel_t RGB,
1023 const dt_iop_order_iccprofile_info_t *lut_profile,
1024 const dt_iop_order_iccprofile_info_t *display_profile)
1025{
1026 dt_aligned_pixel_t display_rgb = { 0.f };
1027 dt_colorrings_profile_rgb_to_display_rgb(RGB, lut_profile, display_profile, display_rgb);
1028 dt_bauhaus_slider_set_stop(slider, stop, display_rgb[0], display_rgb[1], display_rgb[2]);
1029}
1030
1032{
1035 const dt_iop_order_iccprofile_info_t *lut_profile
1037 : NULL;
1038 const dt_iop_order_iccprofile_info_t *display_profile
1040 : NULL;
1041
1042 if(IS_NULL_PTR(lut_profile)) return;
1043
1049 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
1050 {
1051 dt_aligned_pixel_t source_hsb = { 0.f };
1052 dt_aligned_pixel_t target_hsb = { 0.f };
1053 _node_source_hsb(p, (dt_iop_colorprimaries_node_t)node, lut_profile, source_hsb);
1054 _node_target_hsb(p, (dt_iop_colorprimaries_node_t)node, lut_profile, target_hsb);
1055
1056 dt_bauhaus_slider_clear_stops(g->node_hue[node]);
1057 for(int stop = 0; stop <= 6; stop++)
1058 {
1059 const float hue_shift = -M_PI_F + 2.f * M_PI_F * (float)stop / 6.f;
1060 dt_aligned_pixel_t HSB = { dt_colorrings_wrap_hue_2pi(source_hsb[0] + hue_shift), target_hsb[1], target_hsb[2],
1061 0.f };
1062 _set_slider_stop_from_hsb(g->node_hue[node], (float)stop / 6.f, HSB, display_profile);
1063 }
1064
1065 dt_bauhaus_slider_clear_stops(g->node_saturation[node]);
1066 {
1067 dt_aligned_pixel_t axis_rgb = { 0.f };
1068 dt_aligned_pixel_t target_rgb = { 0.f };
1069 dt_aligned_pixel_t shell_rgb = { 0.f };
1070
1078 dt_colorrings_brightness_to_axis_rgb(target_hsb[2], dt_colorrings_graph_white(), lut_profile, axis_rgb);
1079 dt_colorrings_hsb_to_profile_rgb(target_hsb, dt_colorrings_graph_white(), lut_profile, target_rgb);
1080 memcpy(shell_rgb, target_rgb, sizeof(shell_rgb));
1081 dt_colorrings_project_to_cube_shell(axis_rgb, shell_rgb);
1082
1083 _set_slider_stop_from_profile_rgb(g->node_saturation[node], 0.f, axis_rgb, lut_profile, display_profile);
1084 _set_slider_stop_from_profile_rgb(g->node_saturation[node], 0.5f, target_rgb, lut_profile, display_profile);
1085 _set_slider_stop_from_profile_rgb(g->node_saturation[node], 1.f, shell_rgb, lut_profile, display_profile);
1086 }
1087
1088 dt_bauhaus_slider_clear_stops(g->node_brightness[node]);
1089 _set_slider_stop_from_hsb(g->node_brightness[node], 0.f,
1090 (dt_aligned_pixel_t){ target_hsb[0], target_hsb[1], CLAMP(source_hsb[2] - 0.05f, 0.f, 1.f),
1091 0.f },
1092 display_profile);
1093 _set_slider_stop_from_hsb(g->node_brightness[node], 0.5f,
1094 (dt_aligned_pixel_t){ target_hsb[0], target_hsb[1], source_hsb[2], 0.f }, display_profile);
1095 _set_slider_stop_from_hsb(g->node_brightness[node], 1.f,
1096 (dt_aligned_pixel_t){ target_hsb[0], target_hsb[1], CLAMP(source_hsb[2] + 0.05f, 0.f, 1.f),
1097 0.f },
1098 display_profile);
1099
1100 /* The three sliders in one color node share the same edited hue, saturation
1101 * and brightness. A drag redraws the active slider through GTK events, but
1102 * the two sibling sliders only repaint if we invalidate them explicitly
1103 * after replacing their gradient stops. */
1104 gtk_widget_queue_draw(g->node_hue[node]);
1105 gtk_widget_queue_draw(g->node_saturation[node]);
1106 gtk_widget_queue_draw(g->node_brightness[node]);
1107 }
1108}
1109
1111{
1112 // GUI-thread only (called from gui_changed()/gui_update()): self->params is live GUI
1113 // state and must never be read from a pipeline thread (see commit_params(), which uses
1114 // the historical params it is passed instead). g->viewer_lut is this instance's own gui_data,
1115 // never shared with any other instance's viewer or with the pipeline's piece->data clut.
1118 const dt_iop_order_iccprofile_info_t *lut_profile
1120 : NULL;
1121 const dt_iop_order_iccprofile_info_t *display_profile
1123 : NULL;
1124
1125 if(IS_NULL_PTR(g->viewer)) return;
1126
1127 if(!g->viewer_lut_dirty && g->viewer_lut_valid && _lut_fields_equal(&g->viewer_lut_params, p)
1128 && g->viewer_display_profile == display_profile)
1129 return;
1130
1131 if(IS_NULL_PTR(lut_profile))
1132 {
1133 dt_lut_viewer_set_lut(g->viewer, NULL, 0, NULL, NULL, NULL);
1134 dt_lut_viewer_set_control_nodes(g->viewer, NULL, 0);
1135 g->viewer_lut_dirty = FALSE;
1136 g->viewer_lut_valid = FALSE;
1137 g->viewer_display_profile = display_profile;
1138 g->viewer_control_node_count = 0;
1139 return;
1140 }
1141
1142 _build_clut(&g->viewer_lut, p, lut_profile);
1143 g->viewer_lut_params = *p;
1144 g->viewer_lut.lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
1145 g->viewer_control_node_count
1146 = _build_viewer_control_nodes(p, lut_profile, g->viewer_control_nodes);
1147 dt_lut_viewer_set_lut(g->viewer, g->viewer_lut.clut, g->viewer_lut.clut_level, NULL, g->viewer_lut.lut_profile,
1148 display_profile);
1149 dt_lut_viewer_set_control_nodes(g->viewer, g->viewer_control_nodes, g->viewer_control_node_count);
1150
1151 g->viewer_lut_dirty = FALSE;
1152 g->viewer_lut_valid = TRUE;
1153 g->viewer_display_profile = display_profile;
1154
1155 dt_lut_viewer_queue_draw(g->viewer);
1156}
1157
1165
1167{
1170
1172 dt_bauhaus_slider_set(g->white_level, p->white_level);
1173 dt_bauhaus_slider_set(g->gamut_coverage, p->gamut_coverage);
1174 dt_bauhaus_slider_set(g->sigma_L, p->sigma_L);
1175 dt_bauhaus_slider_set(g->sigma_rho, p->sigma_rho);
1176 dt_bauhaus_slider_set(g->sigma_theta, p->sigma_theta);
1177 dt_bauhaus_slider_set(g->neutral_protection, p->neutral_protection);
1178 dt_bauhaus_combobox_set(g->interpolation, p->interpolation);
1179
1180 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
1181 {
1182 dt_bauhaus_slider_set(g->node_hue[node], p->hue[node]);
1183 dt_bauhaus_slider_set(g->node_saturation[node], p->saturation[node]);
1184 dt_bauhaus_slider_set(g->node_brightness[node], p->brightness[node]);
1185 }
1187
1188 gui_changed(self, NULL, NULL);
1189}
1190
1192{
1194 dt_free_align(g->viewer_lut.clut);
1195 g->viewer_lut.clut = NULL;
1196 dt_lut_viewer_destroy(&g->viewer);
1198}
1199
1200static GtkWidget *_new_section_label(GtkWidget *box, const char *label)
1201{
1202 GtkWidget *section = dt_ui_section_label_new(label);
1203 gtk_widget_set_hexpand(section, TRUE);
1204 gtk_box_pack_start(GTK_BOX(box), section, FALSE, FALSE, 0);
1205 return section;
1206}
1207
1210{
1213 const dt_iop_module_t *sampled_module = piece && piece->module ? piece->module : self;
1214
1215 if(picker != g->white_level) return;
1216 if(sampled_module->picked_color_max[0] < sampled_module->picked_color_min[0]) return;
1217
1218 dt_aligned_pixel_t max_Ych = { 0.f };
1219 _pipe_rgb_to_Ych(self, pipe, (const float *)sampled_module->picked_color_max, max_Ych);
1221 p->white_level = log2f(fmaxf(max_Ych[0], 1e-6f));
1222 dt_bauhaus_slider_set(g->white_level, p->white_level);
1224
1225 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1226}
1227
1228void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
1229 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
1230{
1231 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
1232 if(IS_NULL_PTR(work_profile) || piece->dsc_in.channels != 4) return;
1233
1235 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1236 const float *const restrict in = (const float *)i;
1237 float max_Y = 0.0f;
1238
1239 __OMP_PARALLEL_FOR__(reduction(max:max_Y))
1240 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height * 4; k += 4)
1241 {
1242 dt_aligned_pixel_t Ych = { 0.f };
1243 dt_colorrings_profile_rgb_to_Ych(in + k, work_profile, Ych);
1244 if(isfinite(Ych[0]))
1245 max_Y = fmaxf(max_Y, Ych[0]);
1246 }
1247
1248 p->white_level = log2f(fmaxf(max_Y, 1e-6f));
1249}
1250
1252{
1255
1256 g->viewer_lut_dirty = TRUE;
1257 g->viewer_lut_valid = FALSE;
1258 g->viewer_lut.clut = NULL;
1259 g->viewer_lut.clut_level = 0;
1260 g->viewer_lut.lut_profile = NULL;
1261 g->viewer_lut.work_profile = NULL;
1262 g->preview_signal_connected = FALSE;
1263 g->viewer_display_profile = NULL;
1264
1265 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1266
1267 g->tabs = GTK_NOTEBOOK(gtk_notebook_new());
1268 gtk_notebook_set_show_border(g->tabs, FALSE);
1269 gtk_box_pack_start(GTK_BOX(self->gui->widget), GTK_WIDGET(g->tabs), TRUE, TRUE, 0);
1270 // white_level lives on the "options" tab; reset it if still active once the
1271 // user switches away from that tab.
1273
1274 GtkWidget *colors_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1275 GtkWidget *colors_tab = gtk_label_new(_("colors"));
1276 dt_gui_add_class(colors_tab, "dt_modulegroups_tab_label");
1277 gtk_notebook_append_page(g->tabs, colors_page, colors_tab);
1278 gtk_container_child_set(GTK_CONTAINER(g->tabs), colors_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
1279
1280 GtkWidget *const module_root = self->gui->widget;
1281 self->gui->widget = colors_page;
1282 for(int node = 0; node < DT_IOP_COLORPRIMARIES_NODE_COUNT; node++)
1283 {
1284 char hue_name[16] = { 0 };
1285 char saturation_name[24] = { 0 };
1286 char brightness_name[24] = { 0 };
1287 snprintf(hue_name, sizeof(hue_name), "hue[%d]", node);
1288 snprintf(saturation_name, sizeof(saturation_name), "saturation[%d]", node);
1289 snprintf(brightness_name, sizeof(brightness_name), "brightness[%d]", node);
1290
1292 g->node_hue[node] = dt_bauhaus_slider_from_params(self, hue_name);
1293 dt_bauhaus_widget_set_label(g->node_hue[node], N_("hue"));
1294 dt_bauhaus_slider_set_format(g->node_hue[node], _("°"));
1295 dt_bauhaus_slider_set_default(g->node_hue[node], defaults->hue[node]);
1296
1297 g->node_saturation[node] = dt_bauhaus_slider_from_params(self, saturation_name);
1298 dt_bauhaus_widget_set_label(g->node_saturation[node], N_("saturation"));
1299 dt_bauhaus_slider_set_format(g->node_saturation[node], _(" %"));
1300 dt_bauhaus_slider_set_default(g->node_saturation[node], defaults->saturation[node]);
1301
1302 g->node_brightness[node] = dt_bauhaus_slider_from_params(self, brightness_name);
1303 dt_bauhaus_widget_set_label(g->node_brightness[node], N_("brightness"));
1304 dt_bauhaus_slider_set_format(g->node_brightness[node], _(" %"));
1305 dt_bauhaus_slider_set_soft_range(g->node_brightness[node], -0.25f, 0.25f);
1306 dt_bauhaus_slider_set_default(g->node_brightness[node], defaults->brightness[node]);
1307 }
1308 self->gui->widget = module_root;
1309
1310 GtkWidget *options_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1311 GtkWidget *options_tab = gtk_label_new(_("options"));
1312 dt_gui_add_class(options_tab, "dt_modulegroups_tab_label");
1313 gtk_notebook_append_page(g->tabs, options_page, options_tab);
1314 gtk_container_child_set(GTK_CONTAINER(g->tabs), options_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
1315
1316 self->gui->widget = options_page;
1317 g->white_level = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "white_level"));
1318 dt_bauhaus_slider_set_default(g->white_level, defaults->white_level);
1319 dt_bauhaus_slider_set_format(g->white_level, _(" EV"));
1320 dt_bauhaus_slider_set_soft_range(g->white_level, -2.f, 2.f);
1321
1322 g->gamut_coverage = dt_bauhaus_slider_from_params(self, "gamut_coverage");
1323 dt_bauhaus_slider_set_default(g->gamut_coverage, defaults->gamut_coverage);
1324 dt_bauhaus_slider_set_format(g->gamut_coverage, _(" %"));
1325 dt_bauhaus_slider_set_soft_range(g->gamut_coverage, 0.f, 100.f);
1326
1327 g->sigma_L = dt_bauhaus_slider_from_params(self, "sigma_L");
1328 dt_bauhaus_slider_set_default(g->sigma_L, defaults->sigma_L);
1329 dt_bauhaus_slider_set_format(g->sigma_L, _(" %"));
1330 dt_bauhaus_slider_set_soft_range(g->sigma_L, 1.f, 100.f);
1331
1332 g->sigma_rho = dt_bauhaus_slider_from_params(self, "sigma_rho");
1333 dt_bauhaus_slider_set_default(g->sigma_rho, defaults->sigma_rho);
1334 dt_bauhaus_slider_set_soft_range(g->sigma_rho, 0.1f, 2.f);
1335
1336 g->sigma_theta = dt_bauhaus_slider_from_params(self, "sigma_theta");
1337 dt_bauhaus_slider_set_default(g->sigma_theta, defaults->sigma_theta);
1338 dt_bauhaus_slider_set_soft_range(g->sigma_theta, 0.05f, 2.f);
1339
1340 g->neutral_protection = dt_bauhaus_slider_from_params(self, "neutral_protection");
1341 dt_bauhaus_slider_set_default(g->neutral_protection, defaults->neutral_protection);
1342 dt_bauhaus_slider_set_soft_range(g->neutral_protection, 0.f, 1.f);
1343
1344 g->interpolation = dt_bauhaus_combobox_from_params(self, "interpolation");
1345 dt_bauhaus_combobox_set_default(g->interpolation, defaults->interpolation);
1346 self->gui->widget = module_root;
1347
1348 g->viewer = dt_lut_viewer_new(DT_GUI_MODULE(self));
1349 if(g->viewer)
1350 gtk_box_pack_start(GTK_BOX(options_page), dt_lut_viewer_get_widget(g->viewer), TRUE, TRUE, 0);
1351
1352 gtk_widget_show_all(self->gui->widget);
1353 gui_update(self);
1354}
1355
1357{
1358 dt_iop_colorprimaries_global_data_t *gd = malloc(sizeof(*gd));
1359 memset(gd, 0, sizeof(*gd));
1360 module->data = gd;
1361
1362#ifdef HAVE_OPENCL
1363 const int lut_program = 28; // lut3d.cl, from programs.conf
1364 const int basic_program = 2; // basic.cl, from programs.conf
1365 gd->kernel_lut3d_tetrahedral = dt_opencl_create_kernel(lut_program, "lut3d_tetrahedral");
1366 gd->kernel_lut3d_trilinear = dt_opencl_create_kernel(lut_program, "lut3d_trilinear");
1367 gd->kernel_lut3d_pyramid = dt_opencl_create_kernel(lut_program, "lut3d_pyramid");
1368 gd->kernel_exposure = dt_opencl_create_kernel(basic_program, "exposure");
1369#endif
1370}
1371
1383
1385{
1386 dt_iop_default_init(module);
1388 memcpy(module->params, module->default_params, module->params_size);
1389}
1390
1391// clang-format off
1392// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1393// vim: shiftwidth=2 expandtab tabstop=2 cindent
1394// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1395// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
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
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1400
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
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:351
@ IOP_CS_RGB
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
void dt_colorrings_profile_rgb_to_dt_ucs_hsb(const dt_aligned_pixel_t RGB, const float white, const dt_iop_order_iccprofile_info_t *profile, dt_aligned_pixel_t HSB)
void dt_colorrings_profile_rgb_to_display_rgb(const dt_aligned_pixel_t RGB, const dt_iop_order_iccprofile_info_t *profile, const dt_iop_order_iccprofile_info_t *display_profile, dt_aligned_pixel_t display_rgb)
void dt_colorrings_fill_lut_sparse_local_field(float *lut, const int level, const dt_colorrings_sparse_anchor_t *const anchors, const int anchor_count, const float inv_sigma_L, const float inv_sigma_rho, const float inv_sigma_theta, const float rho0)
void dt_colorrings_hsb_to_profile_rgb(const dt_aligned_pixel_t HSB, const float white, const dt_iop_order_iccprofile_info_t *profile, dt_aligned_pixel_t RGB)
void dt_colorrings_gray_basis_to_rgb(const float L, const float u, const float v, float rgb[3])
void dt_colorrings_gray_axis_rgb_from_L(const float L, dt_aligned_pixel_t RGB)
void dt_colorrings_rgb_to_gray_cyl(const float rgb[3], float *L, float *rho, float *theta)
float dt_colorrings_wrap_hue_pi(float hue)
float dt_colorrings_wrap_hue_2pi(float hue)
void dt_colorrings_project_to_cube_shell(const dt_aligned_pixel_t axis, dt_aligned_pixel_t RGB)
void dt_colorrings_hsb_to_display_rgb(const dt_aligned_pixel_t HSB, const float white, const dt_iop_order_iccprofile_info_t *display_profile, dt_aligned_pixel_t RGB)
float dt_colorrings_wrap_pi(float x)
void dt_colorrings_profile_rgb_to_Ych(const dt_aligned_pixel_t RGB, const dt_iop_order_iccprofile_info_t *profile, dt_aligned_pixel_t Ych)
float dt_colorrings_graph_white(void)
void dt_colorrings_brightness_to_axis_rgb(const float brightness, const float white, const dt_iop_order_iccprofile_info_t *profile, dt_aligned_pixel_t RGB)
#define DT_COLORRINGS_CLUT_LEVEL
static void _refresh_slider_gradients(dt_iop_module_t *self)
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 const char * _node_name(const dt_iop_colorprimaries_node_t node)
void init(dt_iop_module_t *module)
static void _append_anchor(dt_colorrings_sparse_anchor_t *const anchors, int *const anchor_count, const dt_colorrings_sparse_anchor_t *const anchor)
const char ** description(struct dt_iop_module_t *self)
int default_group()
static void _apply_anchor_to_rgb(const dt_aligned_pixel_t source_rgb, const dt_colorrings_sparse_anchor_t *const anchor, dt_aligned_pixel_t target_rgb)
static float _hsb_distance(const dt_aligned_pixel_t a, const dt_aligned_pixel_t b)
static void _node_target_hsb(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_node_t node, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_aligned_pixel_t HSB)
static void _store_viewer_control_node(dt_lut_viewer_control_node_t *const control_nodes, int *const count, const dt_aligned_pixel_t input_rgb, const dt_aligned_pixel_t output_rgb)
void gui_update(dt_iop_module_t *self)
static void _black_white_rgb(const gboolean white, dt_aligned_pixel_t RGB)
static void _node_corner_rgb(const dt_iop_colorprimaries_node_t node, dt_aligned_pixel_t RGB)
static gboolean _lut_fields_equal(const dt_iop_colorprimaries_params_t *const a, const dt_iop_colorprimaries_params_t *const b)
static int _build_viewer_control_nodes(const dt_iop_colorprimaries_params_t *const p, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_lut_viewer_control_node_t *const control_nodes)
const char * aliases()
static void _set_slider_stop_from_profile_rgb(GtkWidget *slider, const float stop, const dt_aligned_pixel_t RGB, const dt_iop_order_iccprofile_info_t *lut_profile, const dt_iop_order_iccprofile_info_t *display_profile)
static gboolean _build_edge_radial_midpoint_anchor(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_edge_t edge, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor, dt_aligned_pixel_t midpoint_rgb)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static gboolean _build_halfway_radial_anchor_from_source_rgb(const dt_aligned_pixel_t source_rgb, const float hue_delta, const float saturation_delta, const float brightness_delta, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor, dt_aligned_pixel_t halfway_rgb)
static GtkWidget * _new_section_label(GtkWidget *box, const char *label)
static gboolean _build_halfway_extreme_anchor_from_source_rgb(const dt_aligned_pixel_t source_rgb, const dt_aligned_pixel_t extreme_rgb, const float hue_delta, const float saturation_delta, const float brightness_delta, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor, dt_aligned_pixel_t halfway_rgb)
static gboolean _build_anchor_from_source_rgb(const dt_aligned_pixel_t source_rgb, const float hue_delta, const float saturation_delta, const float brightness_delta, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor)
const char * name()
static void _halfway_to_axis_rgb(const dt_aligned_pixel_t source_rgb, dt_aligned_pixel_t halfway_rgb)
void gui_init(dt_iop_module_t *self)
static void _node_base_rgb(const dt_iop_colorprimaries_node_t node, const float gamut_coverage, dt_aligned_pixel_t RGB)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
#define DT_IOP_COLORPRIMARIES_EDGE_COUNT
void gui_cleanup(dt_iop_module_t *self)
static gboolean _build_node_black_white_midpoint_anchor(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_node_t node, const gboolean toward_white, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor, dt_aligned_pixel_t midpoint_rgb)
static void _update_gui_lut_cache(dt_iop_module_t *self)
void cleanup_global(dt_iop_module_so_t *module)
static gboolean _build_edge_edit(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_edge_t edge, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_aligned_pixel_t source_rgb, float *const hue_delta, float *const saturation_delta, float *const brightness_delta)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
static gboolean _build_node_anchor(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_node_t node, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor)
int flags()
dt_iop_colorprimaries_node_t
@ DT_IOP_COLORPRIMARIES_BLUE
@ DT_IOP_COLORPRIMARIES_GREEN
@ DT_IOP_COLORPRIMARIES_MAGENTA
@ DT_IOP_COLORPRIMARIES_YELLOW
@ DT_IOP_COLORPRIMARIES_CYAN
@ DT_IOP_COLORPRIMARIES_RED
#define DT_IOP_COLORPRIMARIES_SQRT3
#define DT_IOP_COLORPRIMARIES_INV_SQRT2
dt_iop_colorprimaries_interpolation_t
@ DT_IOP_COLORPRIMARIES_TETRAHEDRAL
@ DT_IOP_COLORPRIMARIES_PYRAMID
@ DT_IOP_COLORPRIMARIES_TRILINEAR
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static gboolean _build_edge_anchor(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_edge_t edge, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor)
static void _init_default_params(dt_iop_colorprimaries_params_t *p)
static gboolean _build_node_radial_midpoint_anchor(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_node_t node, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_colorrings_sparse_anchor_t *const anchor, dt_aligned_pixel_t midpoint_rgb)
static float _mix_hue_delta_weighted(const float hue_a, const float hue_b, const float weight_a, const float weight_b)
static void _node_source_hsb(const dt_iop_colorprimaries_params_t *const p, const dt_iop_colorprimaries_node_t node, const dt_iop_order_iccprofile_info_t *const lut_profile, dt_aligned_pixel_t HSB)
#define DT_IOP_COLORPRIMARIES_NODE_COUNT
static void _pipe_rgb_to_Ych(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_aligned_pixel_t RGB, dt_aligned_pixel_t Ych)
void init_global(dt_iop_module_so_t *module)
__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 ibuf, void *const obuf)
void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
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 const dt_iop_colorprimaries_edge_t _chroma_edges[6]
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void _build_clut(dt_iop_colorprimaries_data_t *d, const dt_iop_colorprimaries_params_t *p, const dt_iop_order_iccprofile_info_t *lut_profile)
#define DT_IOP_COLORPRIMARIES_VIEWER_CONTROL_NODES
#define DT_IOP_COLORPRIMARIES_MAX_ANCHORS
static void _set_slider_stop_from_hsb(GtkWidget *slider, const float stop, const dt_aligned_pixel_t HSB, const dt_iop_order_iccprofile_info_t *display_profile)
#define DT_IOP_COLORPRIMARIES_AXIS_ANCHORS
dt_iop_order_iccprofile_info_t * dt_colorspaces_add_profile(const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
Find-or-build the derived matrix/LUT data for a profile identity, memoised.
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
int dt_ioppr_transform_image_colorspace_rgb_cl(const int devid, cl_mem dev_img_in, cl_mem dev_img_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
OpenCL counterpart of dt_ioppr_transform_image_colorspace_rgb().
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static dt_aligned_pixel_t RGB
#define M_PI_F
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
#define DT_GUI_MODULE(x)
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:308
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
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_GROUP_COLOR
Definition imageop.h:158
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_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
static float kernel(const float *x, const float *y)
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
@ DT_DEBUG_OPENCL
Definition logging.h:57
@ DT_DEBUG_PERF
Definition logging.h:55
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
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
dt_lut3d_interpolation_t
Definition lut3d.h:26
@ DT_LUT3D_INTERP_PYRAMID
Definition lut3d.h:29
@ DT_LUT3D_INTERP_TETRAHEDRAL
Definition lut3d.h:27
@ DT_LUT3D_INTERP_TRILINEAR
Definition lut3d.h:28
void dt_lut_viewer_destroy(dt_lut_viewer_t **viewer)
void dt_lut_viewer_queue_draw(dt_lut_viewer_t *viewer)
void dt_lut_viewer_set_control_nodes(dt_lut_viewer_t *viewer, const dt_lut_viewer_control_node_t *control_nodes, size_t control_node_count)
GtkWidget * dt_lut_viewer_get_widget(dt_lut_viewer_t *viewer)
dt_lut_viewer_t * dt_lut_viewer_new(dt_gui_module_t *module)
void dt_lut_viewer_set_lut(dt_lut_viewer_t *viewer, const float *clut, uint16_t level, dt_pthread_rwlock_t *clut_lock, const dt_iop_order_iccprofile_info_t *lut_profile, const dt_iop_order_iccprofile_info_t *display_profile)
#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
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
#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.
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
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
void dt_lut3d_apply(const float *const in, float *const out, const size_t pixel_nb, const float *const clut, const uint16_t level, const float normalization, const dt_lut3d_interpolation_t interpolation)
Apply one interpolation model over a packed RGB CLUT.
@ DT_INTENT_PERCEPTUAL
@ DT_COLORSPACE_HLG_REC2020
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
return noise *sigma mu
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
unsigned int channels
Definition format.h:83
dt_iop_order_iccprofile_info_t * lut_profile
dt_lut3d_interpolation_t interpolation
dt_iop_colorprimaries_params_t built_params
dt_iop_order_iccprofile_info_t * work_profile
dt_iop_colorprimaries_node_t a
dt_iop_colorprimaries_node_t b
dt_lut_viewer_control_node_t viewer_control_nodes[(6+6+(6+6)+(2 *6))]
const dt_iop_order_iccprofile_info_t * viewer_display_profile
dt_iop_colorprimaries_data_t viewer_lut
dt_iop_colorprimaries_params_t viewer_lut_params
dt_iop_colorprimaries_interpolation_t interpolation
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
int32_t params_size
Definition imageop.h:335
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
static double dt_get_wtime(void)
Definition times.h:43
#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)