Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorequal.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2022-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#include "develop/masks_gui.h"
24
25#include <math.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include "widgets/bauhaus.h"
32#include "system/macros.h"
33#include "system/openmp.h"
35#include "system/mem_alloc.h"
36#include "system/simd.h"
37#include "common/logging.h"
38#include "common/times.h"
40#include "common/imagebuf.h"
41#include "pixel/lut3d.h"
42#include "gui/lut_viewer.h"
43#include "common/opencl.h"
44#include "common/conf.h"
45#include "control/control.h"
46#include "control/signal.h"
47#include "develop/develop.h"
49#include "develop/imageop.h"
50#include "develop/imageop_gui.h"
51#include "develop/masks.h"
54#include "widgets/draw.h"
55#include "gui/application.h"
57#include "iop/iop_api.h"
58#include "widgets/scroll_wrap.h"
60#include "gui/screen_metrics.h"
61
62#ifdef _OPENMP
63#include <omp.h>
64#endif
65
71#define DT_IOP_COLOREQUAL_NUM_CHANNELS 3
72#define DT_IOP_COLOREQUAL_NUM_RINGS 3
73#define DT_IOP_COLOREQUAL_MAXNODES 20
74#define DT_IOP_COLOREQUAL_DEFAULT_NODES 8
75#define DT_IOP_COLOREQUAL_GRAPH_RES 360
76#define DT_IOP_COLOREQUAL_GRAPH_GRADIENTS 48
77#define DT_IOP_COLOREQUAL_HUE_SAMPLES 64
78#define DT_IOP_COLOREQUAL_VIEWER_CONTROL_NODES (DT_IOP_COLOREQUAL_NUM_RINGS * DT_IOP_COLOREQUAL_HUE_SAMPLES)
79#define DT_IOP_COLOREQUAL_AXIAL_SAMPLES 64
80#define DT_IOP_COLOREQUAL_CLUT_LEVEL 64
81#define DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS (DT_IOP_COLOREQUAL_NUM_RINGS + 1)
82#define DT_IOP_COLOREQUAL_MIN_X_DISTANCE 0.01f
83#define DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS DT_PIXEL_APPLY_DPI(14.f)
84#define DT_IOP_COLOREQUAL_GRAPH_INSET DT_PIXEL_APPLY_DPI(4)
85#define DT_IOP_COLOREQUAL_AXIS_HEIGHT DT_PIXEL_APPLY_DPI(14)
86#define DT_IOP_COLOREQUAL_SCROLL_SIGMA 2.f * M_PI_F / 128.f
87#define DT_IOP_COLOREQUAL_SCROLL_STEP 0.05f
88#define DT_IOP_COLOREQUAL_SCROLL_STEP_FINE 0.02f
89#define DT_IOP_COLOREQUAL_SCROLL_STEP_COARSE 0.10f
90#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP (5.f * M_PI_F / 180.f)
91#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_FINE (1.f * M_PI_F / 180.f)
92#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_COARSE (10.f * M_PI_F / 180.f)
93
95
102
109
111{
112 DT_IOP_COLOREQUAL_TETRAHEDRAL = 0, // $DESCRIPTION: "tetrahedral"
113 DT_IOP_COLOREQUAL_TRILINEAR = 1, // $DESCRIPTION: "trilinear"
114 DT_IOP_COLOREQUAL_PYRAMID = 2, // $DESCRIPTION: "pyramid"
116
122
124{
125 float white_level; // $MIN: -2.0 $MAX: 16.0 $DEFAULT: 1.0 $DESCRIPTION: "white level"
126 float sigma_L; // $MIN: 1.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "brightness smoothing"
127 float sigma_rho; // $MIN: 0.01 $MAX: 2.0 $DEFAULT: 1 $DESCRIPTION: "saturation smoothing"
128 float sigma_theta; // $MIN: 0.01 $MAX: 6.28318531 $DEFAULT: 0.40 $DESCRIPTION: "hue smoothing"
129 float neutral_protection; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.05 $DESCRIPTION: "neutral protection"
130 dt_iop_colorequal_interpolation_t interpolation; // $DEFAULT: DT_IOP_COLOREQUAL_TETRAHEDRAL $DESCRIPTION: "interpolation"
135
137{
138 float *clut;
139 uint16_t clut_level;
145 // Params that `clut` was last built from, for this piece alone. The LUT is expensive
146 // enough to be worth not rebuilding on every commit, but it must never be shared across
147 // pieces/instances: see the multi-instance cache-bleed writeup for this module.
149 gboolean clut_valid;
151
153{
154 // Truly global, read-only-after-init state only. Do NOT add a shared LUT/params cache
155 // here: this struct is the same instance for every module instance, every piece, and
156 // every pipe (full/preview/thumbnail/export) of colorequal in the whole
157 // application -- there is exactly one of it, not one per instance. A memoized LUT stored
158 // here can only ever hold one instance's content at a time, and gets silently overwritten
159 // by whichever piece/instance last committed -- including disabled instances, since
160 // commit_params runs for disabled pieces too.
166
168{
170 GtkNotebook *ring_notebook;
196 gboolean has_focus;
197 gboolean picker_valid;
198 gboolean cursor_valid;
218
219const char *name()
220{
221 return _("color equalizer");
222}
223
224const char *aliases()
225{
226 return _("color zones");
227}
228
229const char **description(struct dt_iop_module_t *self)
230{
232 self, _("stretch RGB colors around the achromatic axis from editable dt UCS hue nodes"), _("creative"),
233 _("linear, RGB, display-referred"), _("linear, RGB"), _("linear, RGB, display-referred"));
234}
235
237{
238 return IOP_GROUP_COLOR;
239}
240
245
247{
248 return IOP_CS_RGB;
249}
250
251static void _update_gui_lut_cache(dt_iop_module_t *self);
252static void _switch_preview_cursor(dt_iop_module_t *self);
254static void _preview_cache_wait_restart(gpointer user_data);
255
256static inline float _channel_value_from_y(const dt_iop_colorequal_channel_t channel, const float y)
257{
258 switch(channel)
259 {
261 return (y - 0.5f) * 2.f * M_PI_F;
264 default:
265 return CLAMP(y * 2.f, 0.f, 2.f);
266 }
267}
268
269static inline float _channel_y_from_value(const dt_iop_colorequal_channel_t channel, const float value)
270{
271 switch(channel)
272 {
274 return CLAMP(value / (2.f * M_PI_F) + 0.5f, 0.f, 1.f);
277 default:
278 return CLAMP(value * 0.5f, 0.f, 1.f);
279 }
280}
281
282static inline const char *_ring_label(const dt_iop_colorequal_ring_t ring)
283{
284 switch(ring)
285 {
287 return _("shadows");
289 return _("highlights");
291 default:
292 return _("midtones");
293 }
294}
295
297 const dt_iop_colorequal_ring_t ring,
298 const dt_iop_colorequal_channel_t channel)
299{
300 return p->curve[ring][channel];
301}
302
304 const dt_iop_colorequal_ring_t ring,
305 const dt_iop_colorequal_channel_t channel)
306{
307 return p->curve[ring][channel];
308}
309
311 const dt_iop_colorequal_channel_t channel)
312{
313 return &p->curve_num_nodes[ring][channel];
314}
315
317 const dt_iop_colorequal_ring_t ring,
318 const dt_iop_colorequal_channel_t channel)
319{
320 return p->curve_num_nodes[ring][channel];
321}
322
324 const dt_iop_colorequal_channel_t channel)
325{
327 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
328 for(int k = 0; k < DT_IOP_COLOREQUAL_DEFAULT_NODES; k++)
329 {
330 curve[k].x = (float)k / (float)DT_IOP_COLOREQUAL_DEFAULT_NODES;
331 curve[k].y = 0.5f;
332 }
333}
334
341
342static inline gboolean _curve_fields_equal(const dt_iop_colorequal_params_t *const a,
343 const dt_iop_colorequal_params_t *const b)
344{
345 if(IS_NULL_PTR(a) || IS_NULL_PTR(b)) return a == b; // params can be NULL before first init
346
347 return !memcmp(a->curve_num_nodes, b->curve_num_nodes, sizeof(a->curve_num_nodes))
348 && !memcmp(a->curve, b->curve, sizeof(a->curve));
349}
350
351static inline gboolean _lut_fields_equal(const dt_iop_colorequal_params_t *const a,
352 const dt_iop_colorequal_params_t *const b)
353{
354 if(IS_NULL_PTR(a) || IS_NULL_PTR(b)) return a == b; // params can be NULL before first init
355
356 return _curve_fields_equal(a, b) && a->sigma_L == b->sigma_L && a->sigma_rho == b->sigma_rho
357 && a->sigma_theta == b->sigma_theta && a->neutral_protection == b->neutral_protection;
358}
359
360static inline float _curve_periodic_distance(const float x0, const float x1)
361{
362 const float distance = fabsf(x0 - x1);
363 return fminf(distance, 1.f - distance);
364}
365
367{
368 switch(CLAMP(page, 0, DT_IOP_COLOREQUAL_NUM_CHANNELS - 1))
369 {
370 case 1:
372 case 2:
374 case 0:
375 default:
377 }
378}
379
381{
382 const int page = (g && g->ring_notebook) ? gtk_notebook_get_current_page(g->ring_notebook) : -1;
383 if(page >= 0 && page < DT_IOP_COLOREQUAL_NUM_RINGS) return (dt_iop_colorequal_ring_t)page;
384 return (dt_iop_colorequal_ring_t)CLAMP(dt_conf_get_int("plugins/darkroom/colorequal/gui_ring_page"), 0,
386}
387
389 const dt_iop_colorequal_ring_t ring)
390{
391 const int page = (g && g->channel_notebook[ring]) ? gtk_notebook_get_current_page(g->channel_notebook[ring]) : -1;
392 if(page >= 0 && page < DT_IOP_COLOREQUAL_NUM_CHANNELS) return _channel_from_page(page);
393 return _channel_from_page(dt_conf_get_int("plugins/darkroom/colorequal/gui_channel_page"));
394}
395
397{
398 g->cursor_sample_valid = FALSE;
399 g->cursor_hue = 0.f;
400 memset(g->cursor_input_display, 0, sizeof(g->cursor_input_display));
401 memset(g->cursor_output_display, 0, sizeof(g->cursor_output_display));
402}
403
405 const dt_iop_colorequal_channel_t channel, const float hue,
406 float *curve_x, float *curve_y, float *offset_normalized)
407{
408 if(IS_NULL_PTR(p) || !isfinite(hue)) return FALSE;
409
410 const int nodes = _curve_nodes_count_const(p, ring, channel);
411 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
412 if(nodes < 1 || IS_NULL_PTR(curve)) return FALSE;
413
414 const float x = dt_colorrings_hue_to_curve_x(hue);
415 const float y = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, nodes, x);
416 const float value = _channel_value_from_y(channel, y);
417
418 if(!IS_NULL_PTR(curve_x)) *curve_x = x;
419 if(!IS_NULL_PTR(curve_y)) *curve_y = y;
420
421 if(!IS_NULL_PTR(offset_normalized))
422 {
423 switch(channel)
424 {
426 *offset_normalized = CLAMP(value / M_PI_F, -1.f, 1.f);
427 break;
430 default:
431 *offset_normalized = CLAMP(value - 1.f, -1.f, 1.f);
432 break;
433 }
434 }
435
436 return TRUE;
437}
438
440{
441 RGB[0] = CLAMP(RGB[0], 0.f, 1.f);
442 RGB[1] = CLAMP(RGB[1], 0.f, 1.f);
443 RGB[2] = CLAMP(RGB[2], 0.f, 1.f);
444 RGB[3] = 0.f;
445}
446
448 dt_aligned_pixel_t display_rgb)
449{
450 if(IS_NULL_PTR(display_rgb)) return;
451
452 memcpy(display_rgb, work_rgb, sizeof(dt_aligned_pixel_t));
453 _clamp_display_rgb(display_rgb);
454
455 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe)) return;
456
457 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
459 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(display_profile)) return;
460
461 float in[4] = { work_rgb[0], work_rgb[1], work_rgb[2], 0.f };
462 float out[4] = { work_rgb[0], work_rgb[1], work_rgb[2], 0.f };
463 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, work_profile, display_profile, "colorequal swatch");
464 display_rgb[0] = out[0];
465 display_rgb[1] = out[1];
466 display_rgb[2] = out[2];
467 _clamp_display_rgb(display_rgb);
468}
469
470static inline void _mix_rgb_anchors(const dt_aligned_pixel_t low, const dt_aligned_pixel_t high, const float mix,
472{
473 for(int c = 0; c < 3; c++) RGB[c] = low[c] * (1.f - mix) + high[c] * mix;
474}
475
476static inline void
478 const int ring, const float hue_position, dt_aligned_pixel_t RGB)
479{
480 const float hue = dt_colorrings_wrap_hue_2pi(hue_position * 2.f * M_PI_F) / (2.f * M_PI_F)
482 const int hue0 = ((int)floorf(hue)) % DT_IOP_COLOREQUAL_HUE_SAMPLES;
483 const int hue1 = (hue0 + 1) % DT_IOP_COLOREQUAL_HUE_SAMPLES;
484 const float mix = hue - floorf(hue);
485
486 for(int c = 0; c < 3; c++)
487 RGB[c] = ring_surface[ring][hue0][c] * (1.f - mix) + ring_surface[ring][hue1][c] * mix;
488}
489
496static inline void
498 const float brightness, const float hue_position, const float white,
500{
501 const float anchor_positions[DT_IOP_COLOREQUAL_NUM_RINGS + 2]
502 = { 0.f,
506 1.f };
507 int segment = 0;
508
509 while(segment < DT_IOP_COLOREQUAL_NUM_RINGS + 1 && brightness > anchor_positions[segment + 1]) segment++;
510
511 const float low_position = anchor_positions[segment];
512 const float high_position = anchor_positions[segment + 1];
513 const float mix
514 = (high_position > low_position) ? (brightness - low_position) / (high_position - low_position) : 0.f;
515 dt_aligned_pixel_t low = { 0.f };
516 dt_aligned_pixel_t high = { 0.f };
517
518 if(segment == 0)
519 {
520 dt_colorrings_brightness_to_axis_rgb(0.f, white, profile, low);
521 _sample_ring_hue(ring_surface, 0, hue_position, high);
522 }
523 else if(segment == DT_IOP_COLOREQUAL_NUM_RINGS)
524 {
525 _sample_ring_hue(ring_surface, DT_IOP_COLOREQUAL_NUM_RINGS - 1, hue_position, low);
526 dt_colorrings_brightness_to_axis_rgb(1.f, white, profile, high);
527 }
528 else
529 {
530 _sample_ring_hue(ring_surface, segment - 1, hue_position, low);
531 _sample_ring_hue(ring_surface, segment, hue_position, high);
532 }
533
534 _mix_rgb_anchors(low, high, CLAMP(mix, 0.f, 1.f), RGB);
535}
536
560 const dt_iop_order_iccprofile_info_t *lut_profile)
561{
562 const gboolean log_perf = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
563 const double start = log_perf ? dt_get_wtime() : 0.0;
564 const float white = dt_colorrings_graph_white();
565 const size_t clut_size
567
568 if(IS_NULL_PTR(d->clut)) d->clut = dt_alloc_align_float(clut_size);
569 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
570
571 dt_colorrings_compute_reference_saturations(white, d->reference_saturation);
578
579 for(int ring = 0; ring < DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS; ring++)
580 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
581 chroma_scale[ring][hue_sample] = 1.f;
582
588 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
589 {
590 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
591 const float brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)current_ring);
592 const float reference_saturation = d->reference_saturation[ring];
593 dt_aligned_pixel_t neutral_rgb = { 0.f };
594 dt_colorrings_brightness_to_axis_rgb(brightness, white, lut_profile, neutral_rgb);
595
596 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
597 {
598 const float x = (float)hue_sample / (float)DT_IOP_COLOREQUAL_HUE_SAMPLES;
599 const float hue = dt_colorrings_curve_x_to_hue(x);
600 const float hue_shift = _channel_value_from_y(
605 const float sat_gain = _channel_value_from_y(
610 const float bright_gain = _channel_value_from_y(
615
616 const dt_aligned_pixel_t before_hsb = { hue, reference_saturation, brightness, 0.f };
617 const dt_aligned_pixel_t after_hsb
618 = { dt_colorrings_wrap_hue_pi(hue + hue_shift), CLAMP(reference_saturation * sat_gain, 0.f, 1.f),
619 CLAMP(brightness * bright_gain, 0.f, 1.f), 0.f };
620
621 dt_aligned_pixel_t before_rgb = { 0.f };
622 dt_aligned_pixel_t after_rgb = { 0.f };
623 dt_colorrings_hsb_to_profile_rgb(before_hsb, white, lut_profile, before_rgb);
624 dt_colorrings_hsb_to_profile_rgb(after_hsb, white, lut_profile, after_rgb);
625 dt_colorrings_project_to_cube_shell(neutral_rgb, before_rgb);
626 dt_colorrings_project_to_cube_shell(neutral_rgb, after_rgb);
627
628 float Lp, rhop, thetap;
629 float La, rhoa;
630 float unused_theta;
631 dt_colorrings_rgb_to_gray_cyl(before_rgb, &Lp, &rhop, &thetap);
632 dt_colorrings_rgb_to_gray_cyl(after_rgb, &La, &rhoa, &unused_theta);
633
647 const float requested_scale = sat_gain;
648 const float projected_scale = (rhop > 1e-6f) ? (rhoa / rhop) : 1.f;
649 const float effective_scale
650 = (requested_scale <= 1.f) ? requested_scale : fminf(requested_scale, projected_scale);
651
652 anchor_L[ring][hue_sample] = Lp;
653 anchor_rho[ring][hue_sample] = rhop;
654 anchor_theta[ring][hue_sample] = thetap;
655 delta_L[ring][hue_sample] = La - Lp;
656 chroma_scale[ring][hue_sample] = effective_scale;
657 delta_theta[ring][hue_sample] = dt_colorrings_wrap_pi(hue_shift);
658 }
659 }
660
661 // Fill the achromatic locus after the hue rings
662 for(int sample = 0; sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; sample++)
663 {
664 const float value = (float)sample / (float)(DT_IOP_COLOREQUAL_HUE_SAMPLES - 1);
665 anchor_L[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = value * 1.7320508075688772f;
666 anchor_rho[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
667 anchor_theta[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
668 delta_L[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
669 chroma_scale[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 1.f;
670 delta_theta[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
671 }
672
684 const float sigma_L = fmaxf(p->sigma_L * 0.01f, 1e-6f);
685 const float sigma_rho = fmaxf(p->sigma_rho, 1e-6f);
686 const float sigma_theta = fmaxf(p->sigma_theta, 1e-6f);
687 const float neutral_protection = fmaxf(p->neutral_protection, 0.f);
688 dt_colorrings_fill_lut_local_field(d->clut, DT_IOP_COLOREQUAL_CLUT_LEVEL, anchor_L, anchor_rho, anchor_theta,
689 delta_L, chroma_scale, delta_theta, 1.f / sigma_L, 1.f / sigma_rho,
690 1.f / sigma_theta, neutral_protection * sigma_rho);
691
692 d->clut_level = DT_IOP_COLOREQUAL_CLUT_LEVEL;
693
694 if(log_perf)
695 dt_print(DT_DEBUG_PERF, "[colorequal] build_clut level=%u total=%.3fms\n", d->clut_level,
696 1000.0 * (dt_get_wtime() - start));
697}
698
700 const dt_iop_order_iccprofile_info_t *lut_profile,
701 dt_lut_viewer_control_node_t *control_nodes)
702{
703 if(IS_NULL_PTR(p) || IS_NULL_PTR(lut_profile) || IS_NULL_PTR(control_nodes)) return 0;
704
705 const float white = dt_colorrings_graph_white();
706 float reference_saturation[DT_IOP_COLOREQUAL_NUM_RINGS] = { 0.f };
707 size_t count = 0;
708
709 dt_colorrings_compute_reference_saturations(white, reference_saturation);
710
717 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
718 {
719 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
720 const float brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)current_ring);
721 const float saturation = reference_saturation[ring];
722 dt_aligned_pixel_t neutral_rgb = { 0.f };
723
724 dt_colorrings_brightness_to_axis_rgb(brightness, white, lut_profile, neutral_rgb);
725
726 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
727 {
728 const float x = (float)hue_sample / (float)DT_IOP_COLOREQUAL_HUE_SAMPLES;
729 const float hue = dt_colorrings_curve_x_to_hue(x);
730 const float hue_shift = _channel_value_from_y(
735 const float sat_gain = _channel_value_from_y(
740 const float bright_gain = _channel_value_from_y(
745 const dt_aligned_pixel_t before_hsb = { hue, saturation, brightness, 0.f };
746 const dt_aligned_pixel_t after_hsb
747 = { dt_colorrings_wrap_hue_pi(hue + hue_shift), CLAMP(saturation * sat_gain, 0.f, 1.f),
748 CLAMP(brightness * bright_gain, 0.f, 1.f), 0.f };
749 dt_aligned_pixel_t before_rgb = { 0.f };
750 dt_aligned_pixel_t after_rgb = { 0.f };
751
752 dt_colorrings_hsb_to_profile_rgb(before_hsb, white, lut_profile, before_rgb);
753 dt_colorrings_hsb_to_profile_rgb(after_hsb, white, lut_profile, after_rgb);
754 dt_colorrings_project_to_cube_shell(neutral_rgb, before_rgb);
755 dt_colorrings_project_to_cube_shell(neutral_rgb, after_rgb);
756
757 for(int c = 0; c < 3; c++)
758 {
759 control_nodes[count].input_rgb[c] = before_rgb[c];
760 control_nodes[count].output_rgb[c] = after_rgb[c];
761 }
762 count++;
763 }
764 }
765
766 return count;
767}
768
771{
772 /* Nothing to prepare for a piece that will not run. dt_iop_commit_params() commits DISABLED
773 * modules too -- deliberately, because some of them self-enable there on runtime context. This
774 * one does not, and process()/process_cl() are the only readers of piece->data, so nothing
775 * downstream can observe what we skip.
776 *
777 * The return has to come BEFORE the profile lookup below, not merely before the LUT build.
778 * dt_colorspaces_add_profile() memoises, but the FIRST caller pays for it, and in the whole
779 * pipeline DT_COLORSPACE_HLG_REC2020 is asked for by nothing except this module and iop/colorprimaries.c.
780 * On an image that enables neither, that profile -- two matrices plus six eagerly built
781 * 65536-entry LUTs, tone-curve inversion included -- was being constructed for nobody.
782 *
783 * Returning rather than clearing also keeps a LUT that was already built: toggle the module off
784 * and on again and the memo survives; only a real parameter change rebuilds.
785 *
786 * piece->enabled is set from history immediately before every dt_iop_commit_params() call
787 * (dev_pixelpipe.c 288, 1288, 1367; pixelpipe_hb.c:700 at node creation), so it is settled here.
788 */
789 if(!piece->enabled) return;
790
793 const dt_iop_order_iccprofile_info_t *lut_profile
795 : NULL;
797
798 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(lut_profile))
799 {
800 // Leave d->clut's allocation (if any) alone -- it is this piece's own buffer now,
801 // not a borrowed pointer. clut_level == 0 already makes process()/process_cl() treat
802 // it as not-ready; freeing/reallocating it is cleanup_pipe's job.
803 d->clut_level = 0;
804 d->clut_valid = FALSE;
805 d->lut_profile = NULL;
806 d->work_profile = NULL;
807 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
808 return;
809 }
810
811 // This LUT is expensive enough to be worth memoizing, but the cache must be scoped to
812 // this piece alone -- see the dt_iop_colorequal_global_data_t comment for why a shared
813 // cache silently bleeds one instance's content into another's.
814 if(!d->clut_valid || !_lut_fields_equal(&d->built_params, p))
815 {
816 _build_clut(d, p, lut_profile);
817 d->built_params = *p;
818 d->clut_valid = TRUE;
819 }
820
821 d->white_level = exp2f(p->white_level);
822 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
823 d->work_profile = (dt_iop_order_iccprofile_info_t *)work_profile;
824 d->interpolation = (dt_lut3d_interpolation_t)p->interpolation;
825}
826
828{
830 piece->data = d;
831 piece->data_size = sizeof(dt_iop_colorequal_data_t);
832 // Checked AFTER both piece->data and piece->data_size are set, deliberately.
833 // dt_iop_init_pipe() disables the node for us when it sees data_size > 0 with a NULL
834 // data -- returning before data_size is assigned skips that and leaves the node enabled
835 // with no storage. dt_calloc_align() returns NULL on failure and pipe nodes are built
836 // exactly when memory is tightest (Sentry 134134395 faulted in atrous's init_pipe).
837 if(IS_NULL_PTR(piece->data)) return;
838 d->clut = NULL;
839 d->clut_level = 0;
840 d->white_level = 1.f;
841 d->lut_profile = NULL;
842 memset(d->reference_saturation, 0, sizeof(d->reference_saturation));
843 d->work_profile = NULL;
844 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
845}
846
848{
849 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
850 if(IS_NULL_PTR(piece->data)) return;
852 dt_free_align(d->clut);
853 d->clut = NULL;
854 dt_free_align(piece->data);
855 piece->data = NULL;
856}
857
858#ifdef HAVE_OPENCL
859int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
860 cl_mem dev_in, cl_mem dev_out)
861{
864 const int width = piece->roi_in.width;
865 const int height = piece->roi_in.height;
866 const int devid = pipe->devid;
867 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
868 const float white_level = fmaxf(d->white_level, 1e-6f);
869 const float normalize = 1.f / white_level;
870 const float denormalize = white_level;
871 const float black = 0.f;
872 cl_mem clut_cl = NULL;
873 cl_int err = CL_SUCCESS;
874 const int kernel = (d->interpolation == DT_LUT3D_INTERP_TRILINEAR) ? gd->kernel_lut3d_trilinear
875 : (d->interpolation == DT_LUT3D_INTERP_PYRAMID) ? gd->kernel_lut3d_pyramid
877
878 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile)) return FALSE;
879
880 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_in);
881 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
882 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
883 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
884 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
885 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &normalize);
886 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
887 if(err != CL_SUCCESS) goto cleanup;
888
889 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->work_profile,
890 d->lut_profile, "colorequal work to HLG Rec2020"))
891 {
892 err = CL_INVALID_OPERATION;
893 goto cleanup;
894 }
895
896 clut_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3 * d->clut_level * d->clut_level * d->clut_level,
897 d->clut);
898 if(IS_NULL_PTR(clut_cl))
899 {
900 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
901 goto cleanup;
902 }
903
904 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dev_out);
905 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &dev_out);
906 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &width);
907 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &height);
908 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &clut_cl);
909 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &d->clut_level);
910 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
911 if(err != CL_SUCCESS) goto cleanup;
912
913 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->lut_profile,
914 d->work_profile, "colorequal HLG Rec2020 to work"))
915 {
916 err = CL_INVALID_OPERATION;
917 goto cleanup;
918 }
919
920 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_out);
921 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
922 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
923 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
924 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
925 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &denormalize);
926 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
927
928cleanup:
930 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_colorequal] couldn't enqueue kernel! %d\n", err);
931 return err == CL_SUCCESS;
932}
933#endif
934
936int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
937 const void *const ibuf, void *const obuf)
938{
940 const int width = piece->roi_in.width;
941 const int height = piece->roi_in.height;
942 const int ch = piece->dsc_in.channels;
943
944 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile))
945 {
947 return 0;
948 }
949
950 const float white_level = fmaxf(d->white_level, 1e-6f);
952 for(size_t k = 0; k < (size_t)width * height; k++)
953 {
954 const float *const in = (const float *)ibuf + k * ch;
955 float *const out = (float *)obuf + k * ch;
956 out[0] = in[0] / white_level;
957 out[1] = in[1] / white_level;
958 out[2] = in[2] / white_level;
959 if(ch > 3) out[3] = in[3];
960 }
961
962 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->work_profile,
963 d->lut_profile, "colorequal work to HLG Rec2020");
964
965 dt_lut3d_apply((float *)obuf, (float *)obuf, (size_t)width * height, d->clut, d->clut_level, 1.f,
966 d->interpolation);
967
968 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->lut_profile, d->work_profile,
969 "colorequal HLG Rec2020 to work");
971 for(size_t k = 0; k < (size_t)width * height; k++)
972 {
973 float *const out = (float *)obuf + k * ch;
974 out[0] *= white_level;
975 out[1] *= white_level;
976 out[2] *= white_level;
977 }
978 return 0;
979}
980
989{
990 if(!self->enabled) return;
992 const dt_iop_colorequal_params_t *p = g ? &g->gui_params : (const dt_iop_colorequal_params_t *)self->params;
993 const gboolean log_perf = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
994 const double start = log_perf ? dt_get_wtime() : 0.0;
995
996 if(IS_NULL_PTR(g->viewer)) return;
997
998 if(!g->viewer_lut_dirty && g->viewer_lut_valid && _lut_fields_equal(&g->viewer_lut_params, p)) return;
999
1000 const dt_iop_order_iccprofile_info_t *lut_profile
1002 : NULL;
1003 const dt_iop_order_iccprofile_info_t *display_profile
1005 : NULL;
1006
1007 if(IS_NULL_PTR(lut_profile))
1008 {
1009 dt_lut_viewer_set_lut(g->viewer, NULL, 0, NULL, NULL, NULL);
1010 dt_lut_viewer_set_control_nodes(g->viewer, NULL, 0);
1011 g->viewer_lut_dirty = FALSE;
1012 g->viewer_lut_valid = FALSE;
1013 g->viewer_control_node_count = 0;
1014 return;
1015 }
1016
1017 _build_clut(&g->viewer_lut, p, lut_profile);
1018 g->viewer_lut_params = *p;
1019 g->viewer_lut.lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
1020 g->viewer_control_node_count = _build_viewer_control_nodes(p, lut_profile, g->viewer_control_nodes);
1021 dt_lut_viewer_set_lut(g->viewer, g->viewer_lut.clut, g->viewer_lut.clut_level, NULL,
1022 g->viewer_lut.lut_profile, display_profile);
1023 dt_lut_viewer_set_control_nodes(g->viewer, g->viewer_control_nodes, g->viewer_control_node_count);
1024 g->viewer_lut_dirty = FALSE;
1025 g->viewer_lut_valid = TRUE;
1026
1027 dt_lut_viewer_queue_draw(g->viewer);
1028
1029 if(log_perf)
1030 dt_print(DT_DEBUG_PERF, "[colorequal] gui LUT cache sync level=%u total=%.3fms\n", g->viewer_lut.clut_level,
1031 1000.0 * (dt_get_wtime() - start));
1032}
1033
1041static void _update_gui_lut_cache_throttled(gpointer data)
1042{
1044}
1045
1047{
1048 const gboolean log_perf = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
1049 const double start = log_perf ? dt_get_wtime() : 0.0;
1050 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1051 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
1052 {
1053 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
1055 const int nodes = _curve_nodes_count_const(p, current_ring, channel);
1056 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, current_ring, channel);
1057
1058 if(!g->curve[ring][ch] || g->curve_nodes[ring][ch] != nodes || g->curve[ring][ch]->c.m_numAnchors != nodes)
1059 {
1060 if(g->curve[ring][ch]) dt_draw_curve_destroy(g->curve[ring][ch]);
1061 g->curve[ring][ch] = dt_draw_curve_new(0.f, 1.f, MONOTONE_HERMITE);
1062 g->curve_nodes[ring][ch] = nodes;
1063
1064 for(int k = 0; k < nodes; k++) dt_draw_curve_add_point(g->curve[ring][ch], curve[k].x, curve[k].y);
1065 }
1066 else
1067 {
1068 for(int k = 0; k < nodes; k++) dt_draw_curve_set_point(g->curve[ring][ch], k, curve[k].x, curve[k].y);
1069 }
1070
1071 dt_draw_curve_calc_values_V2(g->curve[ring][ch], 0.f, 1.f, DT_IOP_COLOREQUAL_GRAPH_RES, NULL,
1072 g->draw_ys[ring][ch], TRUE);
1073 }
1074
1075 memcpy(&g->cached_curve_params, p, sizeof(*p));
1076 g->curve_cache_valid = TRUE;
1077
1078 if(log_perf)
1079 dt_print(DT_DEBUG_PERF, "[colorequal] gui curve cache rebuild total=%.3fms\n",
1080 1000.0 * (dt_get_wtime() - start));
1081}
1082
1083static inline void _graph_background_hsb(const dt_iop_colorequal_channel_t channel, const float x, const float y,
1084 const float ring_brightness, const float reference_saturation,
1086{
1087 const float hue = dt_colorrings_curve_x_to_hue(x);
1088
1089 switch(channel)
1090 {
1092 HSB[0] = dt_colorrings_wrap_hue_pi(hue + _channel_value_from_y(channel, y));
1093 HSB[1] = reference_saturation;
1094 HSB[2] = ring_brightness;
1095 break;
1097 HSB[0] = hue;
1098 HSB[1] = reference_saturation;
1099 HSB[2] = CLAMP(ring_brightness * _channel_value_from_y(channel, y), 0.f, 1.f);
1100 break;
1102 default:
1103 HSB[0] = hue;
1104 HSB[1] = CLAMP(reference_saturation * _channel_value_from_y(channel, y), 0.f, 1.f);
1105 HSB[2] = ring_brightness;
1106 break;
1107 }
1108}
1109
1110static void _draw_graph_background(cairo_t *cr, const dt_iop_colorequal_channel_t channel,
1111 const dt_iop_colorequal_ring_t ring, const float graph_width,
1112 const float graph_height, const float white, const float reference_saturation,
1113 const dt_iop_order_iccprofile_info_t *display_profile)
1114{
1115 const float ring_brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)ring);
1117
1118 // Fast loop
1120 for(int slice = 0; slice < DT_IOP_COLOREQUAL_GRAPH_GRADIENTS; slice++)
1121 {
1122 const float y = (float)(DT_IOP_COLOREQUAL_GRAPH_GRADIENTS - slice) / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS;
1123 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1124 {
1125 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1126 float *const colors = bg + (slice * DT_IOP_COLOREQUAL_GRAPH_RES + k) * 4;
1127 dt_aligned_pixel_t HSB = { 0.f };
1128 _graph_background_hsb(channel, x, y, ring_brightness, reference_saturation, HSB);
1129 dt_colorrings_hsb_to_display_rgb(HSB, white, display_profile, colors);
1130 }
1131 }
1132
1133 // Slow loop
1134 for(int slice = 0; slice < DT_IOP_COLOREQUAL_GRAPH_GRADIENTS; slice++)
1135 {
1136 cairo_pattern_t *gradient = cairo_pattern_create_linear(0.0, 0.0, graph_width, 0.0);
1137 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1138 {
1139 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1140 float *const colors = bg + (slice * DT_IOP_COLOREQUAL_GRAPH_RES + k) * 4;
1141 cairo_pattern_add_color_stop_rgba(gradient, x, colors[0], colors[1], colors[2], 1.0);
1142 }
1143
1144 cairo_rectangle(cr, 0.f, graph_height / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS * (float)slice, graph_width,
1145 graph_height / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS);
1146 cairo_set_source(cr, gradient);
1147 cairo_fill(cr);
1148 cairo_pattern_destroy(gradient);
1149 }
1150 dt_free_align(bg);
1151}
1152
1153static gboolean _draw_curve(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1154{
1155 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1157 const dt_iop_colorequal_params_t *p = &g->gui_params;
1158 const dt_iop_colorequal_ring_t ring
1159 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1160 const dt_iop_colorequal_channel_t channel
1161 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1162
1163 if(!g->curve_cache_valid || !_curve_fields_equal(&g->cached_curve_params, p)) _update_curve_cache(g, p);
1164
1165 GtkAllocation allocation;
1166 gtk_widget_get_allocation(widget, &allocation);
1167 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1168
1169 const float inset = DT_IOP_COLOREQUAL_GRAPH_INSET;
1170 const float graph_width = allocation.width - 2.f * inset;
1171 const float graph_height = allocation.height - DT_IOP_COLOREQUAL_AXIS_HEIGHT - 2.f * inset;
1172 const float white = dt_colorrings_graph_white();
1173 const dt_iop_order_iccprofile_info_t *const display_profile
1175 : NULL;
1176 dt_colorrings_compute_reference_saturations(white, g->reference_saturation);
1184 const float background_saturation
1185 = (channel == DT_IOP_COLOREQUAL_HUE) ? g->reference_saturation[ring]
1186 : CLAMP(g->reference_saturation[ring] * 1.35f, 0.f, 1.f);
1187
1188 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1189 cairo_t *cr = cairo_create(cst);
1190
1191 if(!g->background_surface[ring][channel] || g->cached_width[ring][channel] != allocation.width
1192 || g->cached_height[ring][channel] != allocation.height
1193 || fabsf(g->cached_white[ring][channel] - white) > 1e-6f
1194 || fabsf(g->cached_reference_saturation[ring][channel] - background_saturation) > 1e-6f
1195 || g->cached_display_profile[ring][channel] != display_profile)
1196 {
1197 if(g->background_surface[ring][channel]) cairo_surface_destroy(g->background_surface[ring][channel]);
1198 g->background_surface[ring][channel]
1199 = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1200 cairo_t *background_cr = cairo_create(g->background_surface[ring][channel]);
1201
1202 gtk_render_background(context, background_cr, 0, 0, allocation.width, allocation.height);
1203 cairo_translate(background_cr, inset, inset);
1204 _draw_graph_background(background_cr, channel, ring, graph_width, graph_height, white, background_saturation,
1205 display_profile);
1206
1207 cairo_rectangle(background_cr, 0.f, 0.f, graph_width, graph_height);
1208 cairo_clip(background_cr);
1209
1210 cairo_set_line_width(background_cr, DT_PIXEL_APPLY_DPI(0.5f));
1211 set_color(background_cr, dt_bauhaus_get_global()->graph_border);
1212 dt_draw_grid(background_cr, 8, 0, 0, graph_width, graph_height);
1213
1214 set_color(background_cr, dt_bauhaus_get_global()->graph_fg);
1215 cairo_set_line_width(background_cr, DT_PIXEL_APPLY_DPI(1.f));
1216 cairo_move_to(background_cr, 0.f, 0.5f * graph_height);
1217 cairo_line_to(background_cr, graph_width, 0.5f * graph_height);
1218 cairo_stroke(background_cr);
1219
1220 cairo_reset_clip(background_cr);
1221 cairo_translate(background_cr, 0.f, graph_height);
1222 cairo_pattern_t *axis = cairo_pattern_create_linear(0.0, 0.0, graph_width, 0.0);
1223
1224 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1225 {
1226 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1227 dt_aligned_pixel_t RGB = { 0.f };
1229 (dt_aligned_pixel_t){ dt_colorrings_curve_x_to_hue(x), background_saturation,
1231 white, display_profile, RGB);
1232 cairo_pattern_add_color_stop_rgba(axis, x, RGB[0], RGB[1], RGB[2], 1.0);
1233 }
1234
1235 cairo_rectangle(background_cr, 0.f, 0.f, graph_width, DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1236 cairo_set_source(background_cr, axis);
1237 cairo_fill(background_cr);
1238 cairo_pattern_destroy(axis);
1239 cairo_destroy(background_cr);
1240
1241 g->cached_width[ring][channel] = allocation.width;
1242 g->cached_height[ring][channel] = allocation.height;
1243 g->cached_white[ring][channel] = white;
1244 g->cached_reference_saturation[ring][channel] = background_saturation;
1245 g->cached_display_profile[ring][channel] = display_profile;
1246 }
1247
1248 cairo_set_source_surface(cr, g->background_surface[ring][channel], 0, 0);
1249 cairo_paint(cr);
1250
1251 cairo_translate(cr, inset, inset);
1252
1253 if(g->picker_valid)
1254 {
1255 const float picker_x = dt_colorrings_hue_to_curve_x(g->picker_hue) * graph_width;
1256 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5f));
1257 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1258 cairo_move_to(cr, picker_x, 0.f);
1259 cairo_line_to(cr, picker_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1260 cairo_stroke(cr);
1261 }
1262
1264 const dt_iop_colorequal_channel_t active_channel = _active_channel_from_gui(g, active_ring);
1265 if(g->cursor_sample_valid && ring == active_ring && channel == active_channel)
1266 {
1267 const float cursor_x = dt_colorrings_hue_to_curve_x(g->cursor_hue) * graph_width;
1268 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.5f));
1269 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.75f);
1270 cairo_move_to(cr, cursor_x, 0.f);
1271 cairo_line_to(cr, cursor_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1272 cairo_stroke(cr);
1273
1274 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f));
1275 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1276 0.95);
1277 cairo_move_to(cr, cursor_x, 0.f);
1278 cairo_line_to(cr, cursor_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1279 cairo_stroke(cr);
1280 }
1281
1282 cairo_rectangle(cr, 0.f, 0.f, graph_width, graph_height);
1283 cairo_clip(cr);
1284
1285 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
1286 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1287
1288 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1289 {
1290 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1) * graph_width;
1291 const float y = (1.f - g->draw_ys[ring][channel][k]) * graph_height;
1292
1293 if(k == 0)
1294 cairo_move_to(cr, x, y);
1295 else
1296 cairo_line_to(cr, x, y);
1297 }
1298
1299 cairo_stroke(cr);
1300
1301 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
1302 const int nodes = _curve_nodes_count_const(p, ring, channel);
1303
1304 for(int k = 0; k < nodes; k++)
1305 {
1306 const float x = curve[k].x * graph_width;
1307 const float y = (1.f - curve[k].y) * graph_height;
1308 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.f));
1309 cairo_arc(cr, x, y, DT_PIXEL_APPLY_DPI(k == g->selected[ring][channel] ? 5.f : 4.f), 0.f, 2.f * M_PI_F);
1310 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1311 cairo_stroke_preserve(cr);
1312 set_color(cr, dt_bauhaus_get_global()->graph_bg);
1313 cairo_fill(cr);
1314 }
1315
1316 if(g->cursor_sample_valid && ring == active_ring && channel == active_channel)
1317 {
1318 float curve_x = 0.f;
1319 float curve_y = 0.f;
1320 float offset_normalized = 0.f;
1321 if(_cursor_curve_state(p, ring, channel, g->cursor_hue, &curve_x, &curve_y, &offset_normalized))
1322 {
1323 const float marker_x = curve_x * graph_width;
1324 const float marker_y = (1.f - curve_y) * graph_height;
1325 const float outer_radius = DT_PIXEL_APPLY_DPI(7.f);
1326 const float inner_radius = DT_PIXEL_APPLY_DPI(4.f);
1327 const float intensity_radius = DT_PIXEL_APPLY_DPI(11.f);
1328
1329 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f));
1330 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.4f);
1331 cairo_arc(cr, marker_x, marker_y, intensity_radius, 0.f, 2.f * M_PI_F);
1332 cairo_stroke(cr);
1333
1334 if(fabsf(offset_normalized) > 1e-4f)
1335 {
1336 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.25f));
1337 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1],
1338 g->cursor_output_display[2], 0.95);
1339 if(offset_normalized > 0.f)
1340 cairo_arc(cr, marker_x, marker_y, intensity_radius, -M_PI_F / 2.f,
1341 -M_PI_F / 2.f + 2.f * M_PI_F * fabsf(offset_normalized));
1342 else
1343 cairo_arc_negative(cr, marker_x, marker_y, intensity_radius, -M_PI_F / 2.f,
1344 -M_PI_F / 2.f - 2.f * M_PI_F * fabsf(offset_normalized));
1345 cairo_stroke(cr);
1346 }
1347
1348 cairo_arc(cr, marker_x, marker_y, outer_radius, 0.f, 2.f * M_PI_F);
1349 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1],
1350 g->cursor_output_display[2], 0.95);
1351 cairo_fill_preserve(cr);
1352 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.9f);
1353 cairo_stroke(cr);
1354
1355 cairo_arc(cr, marker_x, marker_y, inner_radius, 0.f, 2.f * M_PI_F);
1356 cairo_set_source_rgba(cr, g->cursor_input_display[0], g->cursor_input_display[1], g->cursor_input_display[2],
1357 0.95);
1358 cairo_fill_preserve(cr);
1359 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.8f);
1360 cairo_stroke(cr);
1361 }
1362 }
1363
1364 cairo_reset_clip(cr);
1365
1366 cairo_destroy(cr);
1367 cairo_set_source_surface(crf, cst, 0, 0);
1368 cairo_paint(crf);
1369 cairo_surface_destroy(cst);
1370 return TRUE;
1371}
1372
1373static void _cacheline_ready_callback(gpointer instance, const guint64 hash,
1374 const guint64 producer_node_key, gpointer user_data)
1375{
1376 (void)instance;
1377 (void)producer_node_key;
1378 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1380 if(g->pending_preview_hash != hash || !_refresh_preview_cursor_sample(self)) return;
1381
1384 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1386}
1387
1388static void _preview_cache_wait_restart(gpointer user_data)
1389{
1390 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1391 if(IS_NULL_PTR(self) || IS_NULL_PTR(dt_iop_gui_data(self))) return;
1392
1394 if(IS_NULL_PTR(g) || !g->has_focus || !_refresh_preview_cursor_sample(self)) return;
1395
1398 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1400}
1401
1403 const dt_iop_colorequal_channel_t channel, const float mouse_x, const float mouse_y,
1404 const float graph_width, const float graph_height)
1405{
1407 const dt_iop_colorequal_params_t *p = &g->gui_params;
1408 int selected = -1;
1409 float best = DT_PIXEL_APPLY_DPI(10.f) * DT_PIXEL_APPLY_DPI(10.f);
1410 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
1411 const int nodes = _curve_nodes_count_const(p, ring, channel);
1412
1413 for(int k = 0; k < nodes; k++)
1414 {
1415 const float node_x = curve[k].x * graph_width;
1416 const float node_y = (1.f - curve[k].y) * graph_height;
1417 const float dx = mouse_x - node_x;
1418 const float dy = mouse_y - node_y;
1419 const float distance = dx * dx + dy * dy;
1420
1421 if(distance < best)
1422 {
1423 best = distance;
1424 selected = k;
1425 }
1426 }
1427
1428 return selected;
1429}
1430
1431static int _add_node(dt_iop_colorequal_node_t *curve, int *nodes, const float x, const float y)
1432{
1433 int selected = -1;
1434
1435 if(curve[0].x > x)
1436 selected = 0;
1437 else
1438 {
1439 for(int k = 1; k < *nodes; k++)
1440 if(curve[k].x > x)
1441 {
1442 selected = k;
1443 break;
1444 }
1445 }
1446
1447 if(selected == -1) selected = *nodes;
1448
1449 if((selected > 0 && x - curve[selected - 1].x <= DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1450 || (selected < *nodes && curve[selected].x - x <= DT_IOP_COLOREQUAL_MIN_X_DISTANCE))
1451 return -1;
1452
1453 for(int k = *nodes; k > selected; k--)
1454 {
1455 curve[k].x = curve[k - 1].x;
1456 curve[k].y = curve[k - 1].y;
1457 }
1458
1459 curve[selected].x = x;
1460 curve[selected].y = y;
1461 (*nodes)++;
1462 return selected;
1463}
1464
1466 const dt_iop_colorequal_channel_t channel, const int node, const float x,
1467 const float y)
1468{
1470 dt_iop_colorequal_params_t *p = &g->gui_params;
1471 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
1472 const int nodes = _curve_nodes_count_const(p, ring, channel);
1473
1474 float new_x = CLAMP(x, 0.f, 0.999f);
1475 const float new_y = CLAMP(y, 0.f, 1.f);
1476
1477 if(node == 0)
1478 {
1479 if(new_x + 1.f - curve[nodes - 1].x < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1480 new_x = curve[nodes - 1].x + DT_IOP_COLOREQUAL_MIN_X_DISTANCE - 1.f;
1481 }
1482 else if(node == nodes - 1)
1483 {
1484 if(curve[0].x + 1.f - new_x < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1485 new_x = curve[0].x + 1.f - DT_IOP_COLOREQUAL_MIN_X_DISTANCE;
1486 }
1487 else if((new_x - curve[node - 1].x) < DT_IOP_COLOREQUAL_MIN_X_DISTANCE
1488 || (curve[node + 1].x - new_x) < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1489 {
1490 return FALSE;
1491 }
1492
1493 curve[node].x = new_x;
1494 curve[node].y = new_y;
1495 return TRUE;
1496}
1497
1498static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1499{
1500 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1502 const dt_iop_colorequal_ring_t ring
1503 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1504 const dt_iop_colorequal_channel_t channel
1505 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1506 GtkAllocation allocation;
1507 gtk_widget_get_allocation(widget, &allocation);
1508
1509 const float graph_width = allocation.width - 2.f * DT_IOP_COLOREQUAL_GRAPH_INSET;
1510 const float graph_height
1512 const float mouse_x = CLAMP(event->x - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_width);
1513 const float mouse_y = CLAMP(event->y - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_height);
1514
1515 if(g->dragging[ring][channel] && g->selected[ring][channel] >= 0)
1516 {
1517 if(_move_selected_node(self, ring, channel, g->selected[ring][channel], mouse_x / graph_width,
1518 1.f - mouse_y / graph_height))
1519 {
1520 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1521 g->curve_cache_valid = FALSE;
1522 g->viewer_lut_dirty = TRUE;
1523 if(g->cursor_valid && g->has_focus)
1524 {
1527 }
1528 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1530 gtk_widget_queue_draw(widget);
1531 }
1532
1533 return TRUE;
1534 }
1535
1536 g->selected[ring][channel]
1537 = _find_selected_node(self, ring, channel, mouse_x, mouse_y, graph_width, graph_height);
1538 gtk_widget_queue_draw(widget);
1539 return TRUE;
1540}
1541
1542static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1543{
1544 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1546 const dt_iop_colorequal_ring_t ring
1547 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1548 const dt_iop_colorequal_channel_t channel
1549 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1550 dt_iop_colorequal_params_t *p = &g->gui_params;
1552 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
1553 dt_iop_colorequal_node_t *default_curve = _curve_nodes(d, ring, channel);
1554 int *nodes = _curve_nodes_count(p, ring, channel);
1555 const int default_nodes = _curve_nodes_count_const(d, ring, channel);
1556
1557 GtkAllocation allocation;
1558 gtk_widget_get_allocation(widget, &allocation);
1559 const float graph_width = allocation.width - 2.f * DT_IOP_COLOREQUAL_GRAPH_INSET;
1560 const float graph_height
1562 const float mouse_x = CLAMP(event->x - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_width) / graph_width;
1563 const float mouse_y = 1.f - CLAMP(event->y - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_height) / graph_height;
1564
1565 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1566 {
1567 *nodes = default_nodes;
1568 for(int k = 0; k < default_nodes; k++) curve[k] = default_curve[k];
1569
1570 g->selected[ring][channel] = -1;
1571 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1572 g->curve_cache_valid = FALSE;
1573 g->viewer_lut_dirty = TRUE;
1574 if(g->cursor_valid && g->has_focus)
1575 {
1578 }
1579 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1581 gtk_widget_queue_draw(widget);
1582 return TRUE;
1583 }
1584
1585 if(event->button == 1 && dt_modifier_is(event->state, DT_PRIMARY_MASK) && *nodes < DT_IOP_COLOREQUAL_MAXNODES)
1586 {
1587 const float y
1588 = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, *nodes, mouse_x);
1589 const int selected = _add_node(curve, nodes, mouse_x, y);
1590
1591 if(selected >= 0)
1592 {
1593 g->selected[ring][channel] = selected;
1594 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1595 g->curve_cache_valid = FALSE;
1596 g->viewer_lut_dirty = TRUE;
1597 if(g->cursor_valid && g->has_focus)
1598 {
1601 }
1602 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1604 gtk_widget_queue_draw(widget);
1605 }
1606
1607 return TRUE;
1608 }
1609
1610 if(event->button == 1)
1611 {
1612 g->selected[ring][channel] = _find_selected_node(self, ring, channel, mouse_x * graph_width,
1613 (1.f - mouse_y) * graph_height, graph_width, graph_height);
1614 g->dragging[ring][channel] = (g->selected[ring][channel] >= 0);
1615 return TRUE;
1616 }
1617
1618 if(event->button == 3 && g->selected[ring][channel] >= 0 && *nodes > 2)
1619 {
1620 for(int k = g->selected[ring][channel]; k < *nodes - 1; k++) curve[k] = curve[k + 1];
1621
1622 (*nodes)--;
1623 g->selected[ring][channel] = -1;
1624 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1625 g->curve_cache_valid = FALSE;
1626 g->viewer_lut_dirty = TRUE;
1627 if(g->cursor_valid && g->has_focus)
1628 {
1631 }
1632 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1634 gtk_widget_queue_draw(widget);
1635 return TRUE;
1636 }
1637
1638 return FALSE;
1639}
1640
1641static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1642{
1643 if(event->button == 1)
1644 {
1645 const dt_iop_colorequal_ring_t ring
1646 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1647 const dt_iop_colorequal_channel_t channel
1648 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1649 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1651 const gboolean was_dragging = g->dragging[ring][channel];
1652 g->dragging[ring][channel] = FALSE;
1653
1661 if(was_dragging)
1662 {
1663 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1664 dt_gui_throttle_cancel(&g->viewer_lut);
1666 }
1667 }
1668
1669 return TRUE;
1670}
1671
1672static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num,
1673 gpointer user_data)
1674{
1675 if(dt_gui_widgets_suppressed()) return;
1676
1677 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1679 const int source_ring = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(notebook), "colorequal-ring"));
1680 const dt_iop_colorequal_channel_t channel
1681 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(page), "colorequal-channel"));
1682 dt_conf_set_int("plugins/darkroom/colorequal/gui_channel_page", (int)page_num);
1683
1684 if(channel < DT_IOP_COLOREQUAL_NUM_CHANNELS)
1685 {
1687 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1688 if(ring != source_ring) gtk_notebook_set_current_page(g->channel_notebook[ring], (int)page_num);
1690
1691 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1692 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1693
1694 if(g->cursor_valid && g->has_focus) dt_control_queue_redraw_center();
1695 }
1696}
1697
1698static void _ring_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
1699{
1700 if(dt_gui_widgets_suppressed()) return;
1701
1702 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1704
1705 if(page_num < DT_IOP_COLOREQUAL_NUM_RINGS)
1706 {
1707 dt_conf_set_int("plugins/darkroom/colorequal/gui_ring_page", (int)page_num);
1708 GtkWidget *page_widget = gtk_notebook_get_nth_page(
1709 g->channel_notebook[page_num], gtk_notebook_get_current_page(g->channel_notebook[page_num]));
1710 if(page_widget)
1711 {
1712 const dt_iop_colorequal_channel_t channel = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(
1713 g_object_get_data(G_OBJECT(page_widget), "colorequal-channel"));
1714 if(channel < DT_IOP_COLOREQUAL_NUM_CHANNELS) gtk_widget_queue_draw(GTK_WIDGET(g->area[page_num][channel]));
1715 }
1716 }
1717
1718 if(g->cursor_valid && g->has_focus)
1719 {
1722 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1724 }
1725}
1726
1727int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
1728{
1730 dt_develop_t *dev = self ? self->dev : NULL;
1731 if(!g->has_focus) return 0;
1732
1734 {
1735 g->cursor_valid = FALSE;
1739 return 0;
1740 }
1741
1742 const int wd = dt_dev_roi_request_preview_width(dev);
1743 const int ht = dt_dev_roi_request_preview_height(dev);
1744 if(wd < 1 || ht < 1) return 0;
1745
1746 float point[2] = { (float)x, (float)y };
1749
1750 const int cursor_x = (int)point[0];
1751 const int cursor_y = (int)point[1];
1752 if(cursor_x >= 0 && cursor_x < wd && cursor_y >= 0 && cursor_y < ht)
1753 {
1754 g->cursor_valid = TRUE;
1755 g->cursor_pos_x = cursor_x;
1756 g->cursor_pos_y = cursor_y;
1757
1759 }
1760 else
1761 {
1762 g->cursor_valid = FALSE;
1764 }
1765
1767
1770 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1772 return g->cursor_valid ? 1 : 0;
1773}
1774
1776{
1778 g->cursor_valid = FALSE;
1781 // The pointer just left the whole darkroom center view, not just the preview area within
1782 // it -- unlike mouse_moved/focus changes, there is no later _darkroom_set_default_cursor
1783 // call to fall back on here, so this is the one place that must still force a cursor.
1786
1789 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1791 return 1;
1792}
1793
1794int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
1795 uint32_t state)
1796{
1798 if(!g->has_focus || which != 3 || !g->cursor_valid || !g->cursor_sample_valid
1800 return 0;
1801
1802 if(!self->enabled)
1803 {
1804 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1805 return 1;
1806 }
1807
1810 dt_iop_colorequal_node_t *curve = _curve_nodes(&g->gui_params, ring, channel);
1811 int *nodes = _curve_nodes_count(&g->gui_params, ring, channel);
1812 if(*nodes >= DT_IOP_COLOREQUAL_MAXNODES) return 1;
1813
1814 const float curve_x = dt_colorrings_hue_to_curve_x(g->cursor_hue);
1815 const float curve_y
1816 = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, *nodes, curve_x);
1817 const int selected = _add_node(curve, nodes, curve_x, curve_y);
1818 if(selected < 0) return 1;
1819
1820 g->selected[ring][channel] = selected;
1821 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1822 g->curve_cache_valid = FALSE;
1823 g->viewer_lut_dirty = TRUE;
1825 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1827 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1829 return 1;
1830}
1831
1832int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
1833{
1835 // A drawn mask being edited (anywhere on canvas, not just hovered directly -- that case
1836 // already wins via dt_masks_events_mouse_scrolled in views/darkroom.c) must not leak scroll
1837 // input into this module's own hue/chroma curve adjustment.
1838 if(!g->has_focus || !g->cursor_valid || !g->cursor_sample_valid || dt_iop_color_picker_is_visible(self->dev)
1839 || (self->dev->form_gui && dt_masks_get_visible_form(self->dev)))
1840 return 0;
1841
1842 if(!self->enabled)
1843 {
1844 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1845 return 1;
1846 }
1847
1850 dt_iop_colorequal_node_t *curve = _curve_nodes(&g->gui_params, ring, channel);
1851 const int nodes = _curve_nodes_count_const(&g->gui_params, ring, channel);
1852 if(nodes < 1) return 1;
1853
1854 const float direction = up ? 1.f : -1.f;
1855 const float curve_x = dt_colorrings_hue_to_curve_x(g->cursor_hue);
1857 const float sigma2 = 2.f * sigma * sigma;
1858 const float step = (channel == DT_IOP_COLOREQUAL_HUE)
1865
1866 for(int k = 0; k < nodes; k++)
1867 {
1868 const float distance = _curve_periodic_distance(curve[k].x, curve_x);
1869 const float weight = expf(-(distance * distance) / sigma2);
1870 float value = _channel_value_from_y(channel, curve[k].y);
1871 value += direction * step * weight;
1872
1873 switch(channel)
1874 {
1876 value = CLAMP(value, -M_PI_F, M_PI_F);
1877 break;
1880 default:
1881 value = CLAMP(value, 0.f, 2.f);
1882 break;
1883 }
1884
1885 curve[k].y = _channel_y_from_value(channel, value);
1886 }
1887
1888 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1889 g->curve_cache_valid = FALSE;
1890 g->viewer_lut_dirty = TRUE;
1892 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1894 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1896 return 1;
1897}
1898
1899void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
1900 int32_t pointerx, int32_t pointery)
1901{
1903 dt_develop_t *dev = self ? self->dev : NULL;
1904 if(IS_NULL_PTR(g) || IS_NULL_PTR(dev)) return;
1905 if(!g->has_focus || !self->enabled || !g->cursor_valid || dt_iop_color_picker_is_visible(dev))
1906 return;
1907
1908 if(!g->cursor_sample_valid)
1909 {
1910 if(!_refresh_preview_cursor_sample(self)) return;
1911 }
1912
1915 float curve_x = 0.f;
1916 float curve_y = 0.f;
1917 float offset_normalized = 0.f;
1918 if(!_cursor_curve_state(&g->gui_params, ring, channel, g->cursor_hue, &curve_x, &curve_y, &offset_normalized))
1919 return;
1920
1921 const float zoom_scale = dt_dev_get_overlay_scale(dev);
1922 dt_dev_rescale_roi(dev, cr, width, height);
1923
1924 const float pointer_x = g->cursor_pos_x;
1925 const float pointer_y = g->cursor_pos_y;
1926 const float outer_radius = DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS / zoom_scale;
1927 const float inner_radius = outer_radius * 0.55f;
1928 const float intensity_radius = outer_radius * 1.55f;
1929 const float crosshair_gap = outer_radius * 0.25f;
1930 const float crosshair_extent = intensity_radius + DT_PIXEL_APPLY_DPI(5.f) / zoom_scale;
1931
1932 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5f) / zoom_scale);
1933 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.35f);
1934 cairo_arc(cr, pointer_x, pointer_y, intensity_radius, 0.f, 2.f * M_PI_F);
1935 cairo_stroke(cr);
1936
1937 if(fabsf(offset_normalized) > 1e-4f)
1938 {
1939 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.f) / zoom_scale);
1940 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1941 0.95);
1942 if(offset_normalized > 0.f)
1943 cairo_arc(cr, pointer_x, pointer_y, intensity_radius, -M_PI_F / 2.f,
1944 -M_PI_F / 2.f + 2.f * M_PI_F * fabsf(offset_normalized));
1945 else
1946 cairo_arc_negative(cr, pointer_x, pointer_y, intensity_radius, -M_PI_F / 2.f,
1947 -M_PI_F / 2.f - 2.f * M_PI_F * fabsf(offset_normalized));
1948 cairo_stroke(cr);
1949 }
1950
1951 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f) / zoom_scale);
1952 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.6f);
1953 cairo_move_to(cr, pointer_x - crosshair_extent, pointer_y);
1954 cairo_line_to(cr, pointer_x - outer_radius - crosshair_gap, pointer_y);
1955 cairo_move_to(cr, pointer_x + outer_radius + crosshair_gap, pointer_y);
1956 cairo_line_to(cr, pointer_x + crosshair_extent, pointer_y);
1957 cairo_move_to(cr, pointer_x, pointer_y - crosshair_extent);
1958 cairo_line_to(cr, pointer_x, pointer_y - outer_radius - crosshair_gap);
1959 cairo_move_to(cr, pointer_x, pointer_y + outer_radius + crosshair_gap);
1960 cairo_line_to(cr, pointer_x, pointer_y + crosshair_extent);
1961 cairo_stroke(cr);
1962
1963 cairo_arc(cr, pointer_x, pointer_y, outer_radius, 0.f, 2.f * M_PI_F);
1964 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1965 0.95);
1966 cairo_fill_preserve(cr);
1967 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.9f);
1968 cairo_stroke(cr);
1969
1970 cairo_arc(cr, pointer_x, pointer_y, inner_radius, 0.f, 2.f * M_PI_F);
1971 cairo_set_source_rgba(cr, g->cursor_input_display[0], g->cursor_input_display[1], g->cursor_input_display[2], 0.95);
1972 cairo_fill_preserve(cr);
1973 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.8f);
1974 cairo_stroke(cr);
1975}
1976
1979{
1981 if(IS_NULL_PTR(work_profile)) return;
1982 dt_colorrings_profile_rgb_to_Ych(RGB, work_profile, Ych);
1983}
1984
1987{
1989 if(IS_NULL_PTR(work_profile)) return;
1991}
1992
1994{
1996 GtkWidget *widget = dt_gui_main_window();
1997
1998 if(!widget || !gtk_widget_get_window(widget)) return;
1999
2000 // Un-hide the cursor (it may have been hidden by the branch below) and, in every case that
2001 // doesn't need a specific shape of its own, leave it there: darkroom's own default cursor
2002 // logic (dot/crosshair/left_ptr, picked per position in _darkroom_set_default_cursor)
2003 // already queued the right one before this module's mouse_moved ran.
2005
2006 if(!g->has_focus || dt_iop_color_picker_is_visible(self->dev))
2007 return;
2008
2009 if(g->cursor_valid && self->dev && self->dev->preview_pipe && self->dev->preview_pipe->processing)
2010 {
2012 return;
2013 }
2014
2015 if(g->cursor_valid && self->enabled)
2016 {
2019 _("scroll over image to adjust the selected color graph\n"
2020 "right-click to add a node at the sampled hue"));
2021 }
2022}
2023
2025{
2027 dt_develop_t *dev = self ? self->dev : NULL;
2028 if(!self->enabled || !g->cursor_valid)
2029 {
2030 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-preview-inactive");
2032 return FALSE;
2033 }
2034
2036 const dt_dev_pixelpipe_iop_t *const previous_piece
2037 = piece ? dt_dev_pixelpipe_get_prev_enabled_piece(dev->preview_pipe, piece) : NULL;
2038 if(IS_NULL_PTR(piece) || IS_NULL_PTR(previous_piece) || previous_piece->dsc_out.datatype != TYPE_FLOAT || previous_piece->dsc_out.channels < 3)
2039 {
2040 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2041 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-preview-piece-missing");
2043 return FALSE;
2044 }
2045
2046 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2047 void *input = NULL;
2048 dt_pixel_cache_entry_t *input_entry = NULL;
2049 dt_dev_pixelpipe_cache_wait_set_owner(&g->preview_wait, "colorequal-preview-cursor", self);
2050 /* Retained: this is an intermediate module cacheline, at refcount 0 between renders, so the
2051 * reference has to be taken with the lookup and not after it. */
2052 if(!dt_dev_pixelpipe_cache_peek_gui_retained(dev->preview_pipe, previous_piece, &input, &input_entry,
2053 &g->preview_wait, _preview_cache_wait_restart, self)
2054 || IS_NULL_PTR(input) || IS_NULL_PTR(input_entry))
2055 {
2056 g->pending_preview_hash = previous_piece->global_hash;
2058 if(!g->cursor_sample_valid) _invalidate_preview_cursor(g);
2059 return FALSE;
2060 }
2061
2063
2064 const float point_preview[2] = { (float)g->cursor_pos_x, (float)g->cursor_pos_y };
2065 float point_image[2] = { point_preview[0], point_preview[1] };
2067
2068 const float scale_x = (previous_piece->buf_out.width > 0)
2069 ? (float)previous_piece->roi_out.width / (float)previous_piece->buf_out.width
2070 : 1.f;
2071 const float scale_y = (previous_piece->buf_out.height > 0)
2072 ? (float)previous_piece->roi_out.height / (float)previous_piece->buf_out.height
2073 : 1.f;
2074 const float sample_x
2075 = point_image[0] * (float)previous_piece->buf_out.width * scale_x - (float)previous_piece->roi_out.x;
2076 const float sample_y
2077 = point_image[1] * (float)previous_piece->buf_out.height * scale_y - (float)previous_piece->roi_out.y;
2078 const int xi = CLAMP((int)lroundf(sample_x), 0, previous_piece->roi_out.width - 1);
2079 const int yi = CLAMP((int)lroundf(sample_y), 0, previous_piece->roi_out.height - 1);
2080
2081 dt_aligned_pixel_t input_rgb = { 0.f };
2082 const float *const input_rgbf
2083 = (const float *)input + ((size_t)yi * (size_t)previous_piece->roi_out.width + (size_t)xi) * previous_piece->dsc_out.channels;
2084 input_rgb[0] = input_rgbf[0];
2085 input_rgb[1] = input_rgbf[1];
2086 input_rgb[2] = input_rgbf[2];
2087 input_rgb[3] = 0.f;
2088
2091
2093 const dt_iop_order_iccprofile_info_t *const lut_profile = g->viewer_lut.lut_profile;
2094 dt_aligned_pixel_t output_rgb = { input_rgb[0], input_rgb[1], input_rgb[2], 0.f };
2095 dt_colorrings_apply_rgb_lut(input_rgb, exp2f(g->gui_params.white_level), work_profile, lut_profile,
2096 g->viewer_lut.clut, g->viewer_lut.clut_level, NULL,
2097 (dt_lut3d_interpolation_t)g->gui_params.interpolation, output_rgb);
2098
2099 dt_aligned_pixel_t projected_rgb = { 0.f };
2100 const float white_level = fmaxf(exp2f(g->gui_params.white_level), 1e-6f);
2101 projected_rgb[0] = input_rgb[0] / white_level;
2102 projected_rgb[1] = input_rgb[1] / white_level;
2103 projected_rgb[2] = input_rgb[2] / white_level;
2104
2105 const float neutral = CLAMP((projected_rgb[0] + projected_rgb[1] + projected_rgb[2]) / 3.f, 0.f, 1.f);
2106 dt_aligned_pixel_t axis = { neutral, neutral, neutral, 0.f };
2107 dt_colorrings_project_to_cube_shell(axis, projected_rgb);
2108
2109 dt_aligned_pixel_t HSB = { 0.f };
2110 _pipe_rgb_to_dt_ucs_hsb(self, dev->preview_pipe, projected_rgb, HSB);
2111 if(!isfinite(HSB[0]))
2112 {
2114 return FALSE;
2115 }
2116
2117 g->cursor_hue = dt_colorrings_wrap_hue_pi(HSB[0]);
2118 _work_rgb_to_display_rgb(self, dev->preview_pipe, input_rgb, g->cursor_input_display);
2119 _work_rgb_to_display_rgb(self, dev->preview_pipe, output_rgb, g->cursor_output_display);
2120 g->cursor_sample_valid = TRUE;
2121 return TRUE;
2122}
2123
2130static void _format_picker_brightness_position(const float brightness, char *text, const size_t size)
2131{
2132 if(brightness <= 0.15f)
2133 {
2134 g_snprintf(text, size, "%d%% %s", 100, _("shadows"));
2135 }
2136 else if(brightness < 0.45f)
2137 {
2138 const float t = (brightness - 0.15f) / (0.45f - 0.15f);
2139 const int shadows = CLAMP((int)lroundf((1.f - t) * 100.f), 0, 100);
2140 g_snprintf(text, size, "%d%% %s, %d%% %s", shadows, _("shadows"), 100 - shadows, _("midtones"));
2141 }
2142 else if(brightness < 0.75f)
2143 {
2144 const float t = (brightness - 0.45f) / (0.75f - 0.45f);
2145 const int midtones = CLAMP((int)lroundf((1.f - t) * 100.f), 0, 100);
2146 g_snprintf(text, size, "%d%% %s, %d%% %s", midtones, _("midtones"), 100 - midtones, _("highlights"));
2147 }
2148 else
2149 {
2150 g_snprintf(text, size, "%d%% %s", 100, _("highlights"));
2151 }
2152}
2153
2156{
2159 const dt_iop_module_t *sampled_module = piece && piece->module ? piece->module : self;
2160
2161 if(picker == g->white_level)
2162 {
2163 dt_aligned_pixel_t max_Ych = { 0.f };
2164 _pipe_rgb_to_Ych(self, pipe, (const float *)sampled_module->picked_color_max, max_Ych);
2165
2167 p->white_level = log2f(max_Ych[0]);
2168 g->gui_params.white_level = p->white_level;
2169 dt_bauhaus_slider_set(g->white_level, p->white_level);
2171
2172 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2173 }
2174 else if(picker == g->module_picker)
2175 {
2176 if(sampled_module->picked_color_max[0] < sampled_module->picked_color_min[0])
2177 {
2178 g->picker_valid = FALSE;
2179 gtk_label_set_text(GTK_LABEL(g->picker_info), _("no sample"));
2180 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2181 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2182 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2183 return;
2184 }
2185
2186 dt_aligned_pixel_t HSB = { 0.f };
2187 dt_aligned_pixel_t RGB = { 0.f };
2188 dt_aligned_pixel_t axis = { 0.f };
2189 char text[128] = { 0 };
2190 const float white_level = fmaxf(exp2f(g->gui_params.white_level), 1e-6f);
2191
2192 RGB[0] = sampled_module->picked_color[0] / white_level;
2193 RGB[1] = sampled_module->picked_color[1] / white_level;
2194 RGB[2] = sampled_module->picked_color[2] / white_level;
2195
2203 const float neutral = CLAMP((RGB[0] + RGB[1] + RGB[2]) / 3.f, 0.f, 1.f);
2204 axis[0] = neutral;
2205 axis[1] = neutral;
2206 axis[2] = neutral;
2208 _pipe_rgb_to_dt_ucs_hsb(self, pipe, RGB, HSB);
2209
2210 g->picker_valid = TRUE;
2211 g->picker_hue = dt_colorrings_wrap_hue_pi(HSB[0]);
2212 g->picker_brightness = CLAMP(HSB[2], 0.f, 1.f);
2213 _format_picker_brightness_position(g->picker_brightness, text, sizeof(text));
2214 gtk_label_set_text(GTK_LABEL(g->picker_info), text);
2215
2216 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2217 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2218 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2219 }
2220}
2221
2222void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2223 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
2224{
2225 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
2226 if(IS_NULL_PTR(work_profile) || piece->dsc_in.channels != 4) return;
2227
2230 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2231 const float *const restrict in = (const float *)i;
2232 float max_Y = 0.0f;
2233
2234 __OMP_PARALLEL_FOR__(reduction(max:max_Y))
2235 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height * 4; k += 4)
2236 {
2237 dt_aligned_pixel_t Ych = { 0.f };
2238 dt_colorrings_profile_rgb_to_Ych(in + k, work_profile, Ych);
2239 if(isfinite(Ych[0]))
2240 max_Y = fmaxf(max_Y, Ych[0]);
2241 }
2242
2243 p->white_level = log2f(fmaxf(max_Y, 1e-6f));
2244 if(!IS_NULL_PTR(g))
2245 g->gui_params.white_level = p->white_level;
2246}
2247
2248void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2249{
2252 const gboolean curves_changed = !_curve_fields_equal(&g->gui_params, p);
2253 memcpy(&g->gui_params, self->params, sizeof(dt_iop_colorequal_params_t));
2254 if(curves_changed)
2255 {
2256 g->curve_cache_valid = FALSE;
2257 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2258 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2259 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2260
2261 if(g->cursor_valid && g->has_focus) dt_control_queue_redraw_center();
2262 }
2264}
2265
2267{
2270 // dt_iop_gui_data() returns NULL whenever module->gui is -- a hidden, not-yet-initialised
2271 // or torn-down module. There is nothing to update in that case, and the memcpy below would
2272 // write 1500 bytes through the NULL, which is what GCC reports as "a region of size 0".
2273 if(IS_NULL_PTR(g)) return;
2274 memcpy(&g->gui_params, p, sizeof(dt_iop_colorequal_params_t));
2275 dt_bauhaus_slider_set(g->white_level, p->white_level);
2276 dt_bauhaus_slider_set(g->sigma_L, p->sigma_L);
2277 dt_bauhaus_slider_set(g->sigma_rho, p->sigma_rho);
2278 dt_bauhaus_slider_set(g->sigma_theta, p->sigma_theta);
2279 dt_bauhaus_slider_set(g->neutral_protection, p->neutral_protection);
2280 dt_bauhaus_combobox_set(g->interpolation, p->interpolation);
2281 gui_changed(self, NULL, NULL);
2282}
2283
2284void gui_focus(struct dt_iop_module_t *self, gboolean in)
2285{
2287 g->has_focus = in;
2288
2289 if(in)
2290 {
2291 if(!g->preview_signal_connected)
2292 {
2294 G_CALLBACK(_cacheline_ready_callback), self);
2295 g->preview_signal_connected = TRUE;
2296 }
2297
2298 if(g->cursor_valid && self->dev && self->dev->preview_pipe && !self->dev->preview_pipe->processing)
2300 }
2301 else if(g->preview_signal_connected)
2302 {
2304 g->preview_signal_connected = FALSE;
2305 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2306 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-focus-leave");
2307 }
2308
2311}
2312
2314{
2317 // The LUT viewer's task holds `self` and reads g->..., both invalid past this point.
2318 // (It was never cancelled here: only the history task, which no longer exists, was.)
2319 dt_gui_throttle_cancel(&g->viewer_lut);
2320
2321 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2322 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2323 {
2324 if(g->curve[ring][ch]) dt_draw_curve_destroy(g->curve[ring][ch]);
2325 g->curve[ring][ch] = NULL;
2326 if(g->background_surface[ring][ch]) cairo_surface_destroy(g->background_surface[ring][ch]);
2327 g->background_surface[ring][ch] = NULL;
2328 }
2329
2330 dt_free_align(g->viewer_lut.clut);
2331 g->viewer_lut.clut = NULL;
2332
2333 dt_lut_viewer_destroy(&g->viewer);
2334 if(g->preview_signal_connected)
2335 {
2337 g->preview_signal_connected = FALSE;
2338 }
2339 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-gui-cleanup");
2340
2341 const int current_primary = CLAMP(gtk_notebook_get_current_page(g->ring_notebook), 0, DT_IOP_COLOREQUAL_NUM_RINGS);
2342 dt_conf_set_int("plugins/darkroom/colorequal/gui_ring_page", current_primary);
2343 dt_conf_set_int("plugins/darkroom/colorequal/gui_channel_page",
2344 gtk_notebook_get_current_page(g->channel_notebook[0]));
2346}
2347
2349{
2351 memcpy(&g->gui_params, self->params, sizeof(dt_iop_colorequal_params_t));
2352 g->curve_cache_valid = FALSE;
2353 g->viewer_lut_dirty = TRUE;
2354 g->viewer_lut_valid = FALSE;
2355 g->preview_signal_connected = FALSE;
2356 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2357 g->has_focus = FALSE;
2358
2359 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2360
2361 GtkWidget *ring_tabs_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2362 gtk_box_pack_start(GTK_BOX(self->gui->widget), ring_tabs_box, TRUE, TRUE, 0);
2363
2364 g->ring_notebook = GTK_NOTEBOOK(gtk_notebook_new());
2365 gtk_widget_set_name(GTK_WIDGET(g->ring_notebook), "colorequal-ring-tabs");
2366 gtk_notebook_set_show_border(g->ring_notebook, FALSE);
2367 g_signal_connect(G_OBJECT(g->ring_notebook), "switch_page", G_CALLBACK(_ring_tabs_switch_callback), self);
2368 gtk_box_pack_start(GTK_BOX(ring_tabs_box), GTK_WIDGET(g->ring_notebook), TRUE, TRUE, 0);
2369
2372 const char *channel_labels[DT_IOP_COLOREQUAL_NUM_CHANNELS] = { _("saturation"), _("brightness"), _("hue") };
2373
2374 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2375 {
2376 GtkWidget *ring_label = gtk_label_new(_ring_label((dt_iop_colorequal_ring_t)ring));
2377 dt_gui_add_class(ring_label, "dt_modulegroups_tab_label");
2378 GtkWidget *ring_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2379 gtk_notebook_append_page(g->ring_notebook, ring_page, ring_label);
2380 gtk_container_child_set(GTK_CONTAINER(g->ring_notebook), ring_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2381
2382 g->channel_notebook[ring] = GTK_NOTEBOOK(gtk_notebook_new());
2383 g_object_set_data(G_OBJECT(g->channel_notebook[ring]), "colorequal-ring", GINT_TO_POINTER(ring));
2384 g_signal_connect(G_OBJECT(g->channel_notebook[ring]), "switch_page", G_CALLBACK(_channel_tabs_switch_callback),
2385 self);
2386 gtk_box_pack_start(GTK_BOX(ring_page), GTK_WIDGET(g->channel_notebook[ring]), TRUE, TRUE, 0);
2387
2388 for(int order = 0; order < DT_IOP_COLOREQUAL_NUM_CHANNELS; order++)
2389 {
2390 const dt_iop_colorequal_channel_t ch = channel_order[order];
2391 GtkWidget *channel_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2392 g_object_set_data(G_OBJECT(channel_page), "colorequal-channel", GINT_TO_POINTER(ch));
2393 g->area[ring][ch] = GTK_DRAWING_AREA(gtk_drawing_area_new());
2394 gtk_widget_set_hexpand(GTK_WIDGET(g->area[ring][ch]), TRUE);
2395 gtk_widget_add_events(GTK_WIDGET(g->area[ring][ch]),
2396 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
2397 g_object_set_data(G_OBJECT(g->area[ring][ch]), "colorequal-ring", GINT_TO_POINTER(ring));
2398 g_object_set_data(G_OBJECT(g->area[ring][ch]), "colorequal-channel", GINT_TO_POINTER(ch));
2399 g_signal_connect(G_OBJECT(g->area[ring][ch]), "draw", G_CALLBACK(_draw_curve), self);
2400 g_signal_connect(G_OBJECT(g->area[ring][ch]), "motion-notify-event",
2401 G_CALLBACK(_area_motion_notify_callback), self);
2402 g_signal_connect(G_OBJECT(g->area[ring][ch]), "button-press-event", G_CALLBACK(_area_button_press_callback),
2403 self);
2404 g_signal_connect(G_OBJECT(g->area[ring][ch]), "button-release-event",
2405 G_CALLBACK(_area_button_release_callback), self);
2406 // All ring/channel graphs are alternate views of the same plot, so they share one height key.
2407 gtk_box_pack_start(GTK_BOX(channel_page),
2408 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area[ring][ch]),
2409 "plugins/darkroom/colorequal/graphheight", 220, 100),
2410 FALSE, FALSE, 0);
2411 gtk_notebook_append_page(g->channel_notebook[ring], channel_page, gtk_label_new(channel_labels[order]));
2412 gtk_container_child_set(GTK_CONTAINER(g->channel_notebook[ring]), channel_page, "tab-expand", TRUE,
2413 "tab-fill", TRUE, NULL);
2414 }
2415 }
2416
2417 GtkWidget *options_label = gtk_label_new(_("options"));
2418 dt_gui_add_class(options_label, "dt_modulegroups_tab_label");
2419 GtkWidget *options_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2420 gtk_notebook_append_page(g->ring_notebook, options_page, options_label);
2421 gtk_container_child_set(GTK_CONTAINER(g->ring_notebook), options_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2422
2423 GtkWidget *const module_root = self->gui->widget;
2424 self->gui->widget = options_page;
2425
2426 g->white_level
2428 dt_bauhaus_slider_set_soft_range(g->white_level, -2.f, 2.f);
2429 dt_bauhaus_slider_set_format(g->white_level, _(" EV"));
2430
2431 g->sigma_L = dt_bauhaus_slider_from_params(self, "sigma_L");
2432 dt_bauhaus_slider_set_soft_range(g->sigma_L, 1.f, 100.f);
2433 dt_bauhaus_slider_set_format(g->sigma_L, _(" %"));
2434
2435 g->sigma_rho = dt_bauhaus_slider_from_params(self, "sigma_rho");
2436 dt_bauhaus_slider_set_soft_range(g->sigma_rho, 0.1f, 1.5f);
2437
2438 g->sigma_theta = dt_bauhaus_slider_from_params(self, "sigma_theta");
2439 dt_bauhaus_slider_set_soft_range(g->sigma_theta, 0.05f, 0.8f);
2440
2441 g->neutral_protection = dt_bauhaus_slider_from_params(self, "neutral_protection");
2442 dt_bauhaus_slider_set_soft_range(g->neutral_protection, 0.f, 1.f);
2443
2444 g->interpolation = dt_bauhaus_combobox_from_params(self, "interpolation");
2445 gtk_widget_set_tooltip_text(g->interpolation, _("select the interpolation method"));
2446
2447 self->gui->widget = module_root;
2448
2449 GtkWidget *picker_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2450 gtk_box_pack_start(GTK_BOX(ring_tabs_box), picker_box, FALSE, FALSE, 0);
2451
2452 g->module_picker = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_AREA, NULL, IOP_CS_RGB);
2453 gtk_box_pack_start(GTK_BOX(picker_box), g->module_picker, FALSE, FALSE, 0);
2454
2455 g->picker_info = gtk_label_new(_("no sample"));
2456 gtk_widget_set_hexpand(g->picker_info, TRUE);
2457 gtk_widget_set_halign(g->picker_info, GTK_ALIGN_START);
2458 gtk_label_set_xalign(GTK_LABEL(g->picker_info), 0.f);
2459 gtk_box_pack_start(GTK_BOX(picker_box), g->picker_info, TRUE, TRUE, 0);
2460
2461 const int active_ring = dt_conf_get_int("plugins/darkroom/colorequal/gui_ring_page");
2462 const int active_channel = dt_conf_get_int("plugins/darkroom/colorequal/gui_channel_page");
2463 const int current_ring_page = CLAMP(active_ring, 0, DT_IOP_COLOREQUAL_NUM_RINGS);
2464 const int current_channel_page = CLAMP(active_channel, 0, DT_IOP_COLOREQUAL_NUM_CHANNELS - 1);
2465
2467 gtk_notebook_set_current_page(g->ring_notebook, current_ring_page);
2468 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2469 gtk_notebook_set_current_page(g->channel_notebook[ring], current_channel_page);
2471
2472 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2473 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++) g->selected[ring][ch] = -1;
2474
2475 g->viewer_lut.clut = NULL;
2476 g->viewer_lut.clut_level = 0;
2477 memset(g->viewer_lut.reference_saturation, 0, sizeof(g->viewer_lut.reference_saturation));
2478 g->viewer_lut.lut_profile = NULL;
2479 g->viewer_lut.work_profile = NULL;
2480 g->viewer_control_node_count = 0;
2481 g->viewer = dt_lut_viewer_new(DT_GUI_MODULE(self));
2482 if(g->viewer)
2483 gtk_box_pack_start(GTK_BOX(GTK_BOX(self->gui->widget)), dt_lut_viewer_get_widget(g->viewer), TRUE, TRUE, 0);
2484
2485 gtk_widget_show_all(self->gui->widget);
2486}
2487
2489{
2490 dt_iop_colorequal_global_data_t *gd = malloc(sizeof(*gd));
2491 module->data = gd;
2492 gd->kernel_lut3d_tetrahedral = -1;
2493 gd->kernel_lut3d_trilinear = -1;
2494 gd->kernel_lut3d_pyramid = -1;
2495 gd->kernel_exposure = -1;
2496
2497#ifdef HAVE_OPENCL
2498 const int lut_program = 28; // lut3d.cl, from programs.conf
2499 const int basic_program = 2; // basic.cl, from programs.conf
2500 gd->kernel_lut3d_tetrahedral = dt_opencl_create_kernel(lut_program, "lut3d_tetrahedral");
2501 gd->kernel_lut3d_trilinear = dt_opencl_create_kernel(lut_program, "lut3d_trilinear");
2502 gd->kernel_lut3d_pyramid = dt_opencl_create_kernel(lut_program, "lut3d_pyramid");
2503 gd->kernel_exposure = dt_opencl_create_kernel(basic_program, "exposure");
2504#endif
2505}
2506
2518
2520{
2521 dt_iop_default_init(module);
2523 memcpy(module->params, module->default_params, module->params_size);
2524}
2525
2526// clang-format off
2527// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2528// vim: shiftwidth=2 expandtab tabstop=2 cindent
2529// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2530// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
GtkWidget * dt_gui_main_window(void)
#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
static const char neutral[]
Definition basecurve.c:253
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
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_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
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
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)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
@ DT_COLOR_PICKER_AREA
static void _update_gui_lut_cache_throttled(gpointer data)
#define DT_IOP_COLOREQUAL_HUE_SAMPLES
Definition colorequal.c:77
#define DT_IOP_COLOREQUAL_SCROLL_SIGMA
Definition colorequal.c:86
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)
Definition colorequal.c:769
static void _reset_channel_nodes(dt_iop_colorequal_params_t *p, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel)
Definition colorequal.c:323
void init(dt_iop_module_t *module)
static int _add_node(dt_iop_colorequal_node_t *curve, int *nodes, const float x, const float y)
static void _pipe_rgb_to_dt_ucs_hsb(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_aligned_pixel_t RGB, dt_aligned_pixel_t HSB)
static void _mix_rgb_anchors(const dt_aligned_pixel_t low, const dt_aligned_pixel_t high, const float mix, dt_aligned_pixel_t RGB)
Definition colorequal.c:470
const char ** description(struct dt_iop_module_t *self)
Definition colorequal.c:229
static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
int default_group()
Definition colorequal.c:236
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP
Definition colorequal.c:90
static const char * _ring_label(const dt_iop_colorequal_ring_t ring)
Definition colorequal.c:282
#define DT_IOP_COLOREQUAL_GRAPH_INSET
Definition colorequal.c:84
static gboolean _draw_curve(GtkWidget *widget, cairo_t *crf, gpointer user_data)
static void _update_curve_cache(dt_iop_colorequal_gui_data_t *g, const dt_iop_colorequal_params_t *p)
#define DT_IOP_COLOREQUAL_AXIS_HEIGHT
Definition colorequal.c:85
static void _switch_preview_cursor(dt_iop_module_t *self)
#define DT_IOP_COLOREQUAL_CLUT_LEVEL
Definition colorequal.c:80
static float _curve_periodic_distance(const float x0, const float x1)
Definition colorequal.c:360
void gui_update(dt_iop_module_t *self)
static void _sample_ring_hue(const float ring_surface[3][64][3], const int ring, const float hue_position, dt_aligned_pixel_t RGB)
Definition colorequal.c:477
#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_FINE
Definition colorequal.c:91
const char * aliases()
Definition colorequal.c:224
static gboolean _cursor_curve_state(const dt_iop_colorequal_params_t *p, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel, const float hue, float *curve_x, float *curve_y, float *offset_normalized)
Definition colorequal.c:404
static dt_iop_colorequal_channel_t _active_channel_from_gui(const dt_iop_colorequal_gui_data_t *g, const dt_iop_colorequal_ring_t ring)
Definition colorequal.c:388
static dt_iop_colorequal_node_t * _curve_nodes(dt_iop_colorequal_params_t *p, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel)
Definition colorequal.c:296
static gboolean _move_selected_node(dt_iop_module_t *self, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel, const int node, const float x, const float y)
static void _graph_background_hsb(const dt_iop_colorequal_channel_t channel, const float x, const float y, const float ring_brightness, const float reference_saturation, dt_aligned_pixel_t HSB)
static void _preview_cache_wait_restart(gpointer user_data)
static void _build_clut(dt_iop_colorequal_data_t *d, const dt_iop_colorequal_params_t *p, const dt_iop_order_iccprofile_info_t *lut_profile)
Definition colorequal.c:559
static int _curve_nodes_count_const(const dt_iop_colorequal_params_t *p, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel)
Definition colorequal.c:316
void gui_focus(struct dt_iop_module_t *self, gboolean in)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition colorequal.c:827
static void _cacheline_ready_callback(gpointer instance, const guint64 hash, const guint64 producer_node_key, gpointer user_data)
#define DT_IOP_COLOREQUAL_NUM_RINGS
Definition colorequal.c:72
const char * name()
Definition colorequal.c:219
static void _ring_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
static void _draw_graph_background(cairo_t *cr, const dt_iop_colorequal_channel_t channel, const dt_iop_colorequal_ring_t ring, const float graph_width, const float graph_height, const float white, const float reference_saturation, const dt_iop_order_iccprofile_info_t *display_profile)
static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
dt_iop_colorequal_ring_t
Definition colorequal.c:104
@ DT_IOP_COLOREQUAL_RING_DARK
Definition colorequal.c:105
@ DT_IOP_COLOREQUAL_RING_LIGHT
Definition colorequal.c:107
@ DT_IOP_COLOREQUAL_RING_MID
Definition colorequal.c:106
#define DT_IOP_COLOREQUAL_SCROLL_STEP_COARSE
Definition colorequal.c:89
void gui_init(dt_iop_module_t *self)
static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
#define DT_IOP_COLOREQUAL_NUM_CHANNELS
Definition colorequal.c:71
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
static void _clamp_display_rgb(dt_aligned_pixel_t RGB)
Definition colorequal.c:439
#define DT_IOP_COLOREQUAL_MAXNODES
Definition colorequal.c:73
static const dt_iop_colorequal_node_t * _curve_nodes_const(const dt_iop_colorequal_params_t *p, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel)
Definition colorequal.c:303
static dt_iop_colorequal_channel_t _channel_from_page(const int page)
Definition colorequal.c:366
void gui_cleanup(dt_iop_module_t *self)
#define DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS
Definition colorequal.c:81
static void _update_gui_lut_cache(dt_iop_module_t *self)
Definition colorequal.c:988
void cleanup_global(dt_iop_module_so_t *module)
static void _invalidate_preview_cursor(dt_iop_colorequal_gui_data_t *g)
Definition colorequal.c:396
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition colorequal.c:246
static void _format_picker_brightness_position(const float brightness, char *text, const size_t size)
int flags()
Definition colorequal.c:241
#define DT_IOP_COLOREQUAL_VIEWER_CONTROL_NODES
Definition colorequal.c:78
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
int mouse_leave(struct dt_iop_module_t *self)
static gboolean _lut_fields_equal(const dt_iop_colorequal_params_t *const a, const dt_iop_colorequal_params_t *const b)
Definition colorequal.c:351
static int * _curve_nodes_count(dt_iop_colorequal_params_t *p, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel)
Definition colorequal.c:310
#define DT_IOP_COLOREQUAL_GRAPH_RES
Definition colorequal.c:75
static gboolean _curve_fields_equal(const dt_iop_colorequal_params_t *const a, const dt_iop_colorequal_params_t *const b)
Definition colorequal.c:342
dt_iop_colorequal_channel_t
Definition colorequal.c:97
@ DT_IOP_COLOREQUAL_BRIGHTNESS
Definition colorequal.c:100
@ DT_IOP_COLOREQUAL_SATURATION
Definition colorequal.c:98
@ DT_IOP_COLOREQUAL_HUE
Definition colorequal.c:99
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition colorequal.c:847
static void _work_rgb_to_display_rgb(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_aligned_pixel_t work_rgb, dt_aligned_pixel_t display_rgb)
Definition colorequal.c:447
static void _init_default_curves(dt_iop_colorequal_params_t *p)
Definition colorequal.c:335
#define DT_IOP_COLOREQUAL_GRAPH_GRADIENTS
Definition colorequal.c:76
#define DT_IOP_COLOREQUAL_MIN_X_DISTANCE
Definition colorequal.c:82
#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_COARSE
Definition colorequal.c:92
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)
Definition colorequal.c:936
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)
static size_t _build_viewer_control_nodes(const dt_iop_colorequal_params_t *p, const dt_iop_order_iccprofile_info_t *lut_profile, dt_lut_viewer_control_node_t *control_nodes)
Definition colorequal.c:699
#define DT_IOP_COLOREQUAL_DEFAULT_NODES
Definition colorequal.c:74
static gboolean _refresh_preview_cursor_sample(dt_iop_module_t *self)
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)
Definition colorequal.c:859
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
#define DT_IOP_COLOREQUAL_SCROLL_STEP_FINE
Definition colorequal.c:88
static float _channel_value_from_y(const dt_iop_colorequal_channel_t channel, const float y)
Definition colorequal.c:256
#define DT_IOP_COLOREQUAL_SCROLL_STEP
Definition colorequal.c:87
#define DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS
Definition colorequal.c:83
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static int _find_selected_node(const dt_iop_module_t *self, const dt_iop_colorequal_ring_t ring, const dt_iop_colorequal_channel_t channel, const float mouse_x, const float mouse_y, const float graph_width, const float graph_height)
static float _channel_y_from_value(const dt_iop_colorequal_channel_t channel, const float value)
Definition colorequal.c:269
dt_iop_colorequal_interpolation_t
Definition colorequal.c:111
@ DT_IOP_COLOREQUAL_PYRAMID
Definition colorequal.c:114
@ DT_IOP_COLOREQUAL_TRILINEAR
Definition colorequal.c:113
@ DT_IOP_COLOREQUAL_TETRAHEDRAL
Definition colorequal.c:112
static void _sample_ring_anchor(const float ring_surface[3][64][3], const float brightness, const float hue_position, const float white, const dt_iop_order_iccprofile_info_t *const profile, dt_aligned_pixel_t RGB)
Definition colorequal.c:497
static dt_iop_colorequal_ring_t _active_ring_from_gui(const dt_iop_colorequal_gui_data_t *g)
Definition colorequal.c:380
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_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)
float dt_colorrings_curve_periodic_sample(const dt_colorrings_node_t *curve, const int nodes, const float x)
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)
gboolean dt_colorrings_apply_rgb_lut(const dt_aligned_pixel_t input_rgb, const float white_level, const dt_iop_order_iccprofile_info_t *work_profile, const dt_iop_order_iccprofile_info_t *lut_profile, const float *clut, const uint16_t clut_level, dt_pthread_rwlock_t *clut_lock, const dt_lut3d_interpolation_t interpolation, dt_aligned_pixel_t output_rgb)
float dt_colorrings_ring_brightness(const dt_colorrings_ring_t ring)
void dt_colorrings_compute_reference_saturations(const float white, float reference_saturation[DT_COLORRINGS_NUM_RINGS])
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)
float dt_colorrings_hue_to_curve_x(const float hue)
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)
float dt_colorrings_curve_x_to_hue(const float x)
void dt_colorrings_fill_lut_local_field(float *lut, const int level, const float anchor_L[DT_COLORRINGS_LOCAL_FIELD_RINGS][DT_COLORRINGS_HUE_SAMPLES], const float anchor_rho[DT_COLORRINGS_LOCAL_FIELD_RINGS][DT_COLORRINGS_HUE_SAMPLES], const float anchor_theta[DT_COLORRINGS_LOCAL_FIELD_RINGS][DT_COLORRINGS_HUE_SAMPLES], const float delta_L[DT_COLORRINGS_LOCAL_FIELD_RINGS][DT_COLORRINGS_HUE_SAMPLES], const float chroma_scale[DT_COLORRINGS_LOCAL_FIELD_RINGS][DT_COLORRINGS_HUE_SAMPLES], const float delta_theta[DT_COLORRINGS_LOCAL_FIELD_RINGS][DT_COLORRINGS_HUE_SAMPLES], const float inv_sigma_L, const float inv_sigma_rho, const float inv_sigma_theta, const float rho0)
dt_colorrings_ring_t
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().
static const float x
const int t
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
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:407
#define dt_control_set_cursor_visible(visible)
Definition control.h:153
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
#define MONOTONE_HERMITE
Definition curve_tools.h:36
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define M_PI_F
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
const dt_dev_pixelpipe_iop_t * dt_dev_pixelpipe_get_module_piece(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
void dt_dev_pixelpipe_cache_wait_cleanup(dt_dev_pixelpipe_cache_wait_t *wait, const char *reason)
Cancel one pending GUI cache wait request and clear its runtime state.
const dt_dev_pixelpipe_iop_t * dt_dev_pixelpipe_get_prev_enabled_piece(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void dt_dev_pixelpipe_cache_wait_set_owner(dt_dev_pixelpipe_cache_wait_t *wait, const char *owner_tag, gpointer owner_object)
Attach debug ownership metadata to one cache wait request.
gboolean dt_dev_pixelpipe_cache_peek_gui_retained(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, void **data, dt_pixel_cache_entry_t **cache_entry, dt_dev_pixelpipe_cache_wait_t *wait, dt_dev_pixelpipe_cache_ready_callback_t restart, gpointer restart_data)
#define dt_dev_pixelpipe_update_history_preview(dev)
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
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)
void dt_dev_coordinates_preview_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1306
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1291
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:2071
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
GHashTable * selected
set of checked row labels, mirrored to conf on every change
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:158
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:297
static void dt_draw_curve_calc_values_V2(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y, const gboolean periodic)
Definition draw.h:351
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:303
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:379
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:281
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
const dt_collection_filter_flag_t colors[6]
Definition filter.c:302
@ TYPE_FLOAT
Definition format.h:56
#define DT_GUI_MODULE(x)
void dt_gui_throttle_cancel(gpointer source)
void dt_gui_throttle_queue(gpointer source, dt_gui_throttle_callback_t callback, gpointer user_data)
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
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:215
@ 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)
static float mix(const float a, const float b, const float t)
Definition liquify.c:751
_lib_location_type_t type
Definition location.c:1
@ 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
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
#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
size_t size
Definition mipmap_cache.c:3
#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
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.
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
@ DT_INTENT_PERCEPTUAL
@ DT_COLORSPACE_HLG_REC2020
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:188
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
const float sigma
unsigned __int64 uint64_t
Definition strptime.c:75
dt_iop_buffer_dsc_t dsc_out
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
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
dt_lut3d_interpolation_t interpolation
Definition colorequal.c:144
dt_iop_colorequal_params_t built_params
Definition colorequal.c:148
dt_iop_order_iccprofile_info_t * work_profile
Definition colorequal.c:143
dt_iop_order_iccprofile_info_t * lut_profile
Definition colorequal.c:142
dt_iop_colorequal_data_t viewer_lut
Definition colorequal.c:181
dt_lut_viewer_t * viewer
Definition colorequal.c:180
dt_lut_viewer_control_node_t viewer_control_nodes[(3 *64)]
Definition colorequal.c:207
dt_iop_colorequal_params_t gui_params
Definition colorequal.c:183
dt_aligned_pixel_t cursor_input_display
Definition colorequal.c:205
dt_aligned_pixel_t cursor_output_display
Definition colorequal.c:206
GtkNotebook * channel_notebook[3]
Definition colorequal.c:171
dt_dev_pixelpipe_cache_wait_t preview_wait
Definition colorequal.c:195
cairo_surface_t * background_surface[3][3]
Definition colorequal.c:216
GtkDrawingArea * area[3][3]
Definition colorequal.c:169
dt_draw_curve_t * curve[3][3]
Definition colorequal.c:185
const dt_iop_order_iccprofile_info_t * cached_display_profile[3][3]
Definition colorequal.c:215
dt_iop_colorequal_params_t viewer_lut_params
Definition colorequal.c:182
float cached_reference_saturation[3][3]
Definition colorequal.c:211
dt_iop_colorequal_params_t cached_curve_params
Definition colorequal.c:184
dt_iop_colorequal_node_t curve[3][3][20]
Definition colorequal.c:132
dt_iop_colorequal_interpolation_t interpolation
Definition colorequal.c:130
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
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
gboolean enabled
Definition imageop.h:313
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_aligned_pixel_t picked_color
Definition imageop.h:287
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
One consumer's outstanding request, owned by that consumer, not by the queue.
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
static double dt_get_wtime(void)
Definition times.h:43
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)