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#endif
22
23#include <math.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include "bauhaus/bauhaus.h"
31#include "common/darktable.h"
32#include "common/imagebuf.h"
34#include "common/lut3d.h"
35#include "common/lut_viewer.h"
36#include "common/opencl.h"
37#include "control/conf.h"
38#include "control/control.h"
39#include "control/signal.h"
40#include "develop/develop.h"
42#include "develop/imageop.h"
43#include "develop/imageop_gui.h"
46#include "dtgtk/drawingarea.h"
48#include "gui/draw.h"
49#include "gui/gtk.h"
50#include "gui/gui_throttle.h"
51#include "iop/iop_api.h"
52
53#ifdef _OPENMP
54#include <omp.h>
55#endif
56
62#define DT_IOP_COLOREQUAL_NUM_CHANNELS 3
63#define DT_IOP_COLOREQUAL_NUM_RINGS 3
64#define DT_IOP_COLOREQUAL_MAXNODES 20
65#define DT_IOP_COLOREQUAL_DEFAULT_NODES 8
66#define DT_IOP_COLOREQUAL_GRAPH_RES 360
67#define DT_IOP_COLOREQUAL_GRAPH_GRADIENTS 48
68#define DT_IOP_COLOREQUAL_HUE_SAMPLES 64
69#define DT_IOP_COLOREQUAL_VIEWER_CONTROL_NODES (DT_IOP_COLOREQUAL_NUM_RINGS * DT_IOP_COLOREQUAL_HUE_SAMPLES)
70#define DT_IOP_COLOREQUAL_AXIAL_SAMPLES 64
71#define DT_IOP_COLOREQUAL_CLUT_LEVEL 64
72#define DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS (DT_IOP_COLOREQUAL_NUM_RINGS + 1)
73#define DT_IOP_COLOREQUAL_MIN_X_DISTANCE 0.01f
74#define DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS DT_PIXEL_APPLY_DPI(14.f)
75#define DT_IOP_COLOREQUAL_GRAPH_INSET DT_PIXEL_APPLY_DPI(4)
76#define DT_IOP_COLOREQUAL_AXIS_HEIGHT DT_PIXEL_APPLY_DPI(14)
77#define DT_IOP_COLOREQUAL_SCROLL_SIGMA 2.f * M_PI_F / 128.f
78#define DT_IOP_COLOREQUAL_SCROLL_STEP 0.05f
79#define DT_IOP_COLOREQUAL_SCROLL_STEP_FINE 0.02f
80#define DT_IOP_COLOREQUAL_SCROLL_STEP_COARSE 0.10f
81#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP (5.f * M_PI_F / 180.f)
82#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_FINE (1.f * M_PI_F / 180.f)
83#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_COARSE (10.f * M_PI_F / 180.f)
84
86
93
100
102{
103 DT_IOP_COLOREQUAL_TETRAHEDRAL = 0, // $DESCRIPTION: "tetrahedral"
104 DT_IOP_COLOREQUAL_TRILINEAR = 1, // $DESCRIPTION: "trilinear"
105 DT_IOP_COLOREQUAL_PYRAMID = 2, // $DESCRIPTION: "pyramid"
107
113
115{
116 float white_level; // $MIN: -2.0 $MAX: 16.0 $DEFAULT: 1.0 $DESCRIPTION: "white level"
117 float sigma_L; // $MIN: 1.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "brightness smoothing"
118 float sigma_rho; // $MIN: 0.01 $MAX: 2.0 $DEFAULT: 1 $DESCRIPTION: "saturation smoothing"
119 float sigma_theta; // $MIN: 0.01 $MAX: 6.28318531 $DEFAULT: 0.40 $DESCRIPTION: "hue smoothing"
120 float neutral_protection; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.05 $DESCRIPTION: "neutral protection"
121 dt_iop_colorequal_interpolation_t interpolation; // $DEFAULT: DT_IOP_COLOREQUAL_TETRAHEDRAL $DESCRIPTION: "interpolation"
126
128{
129 float *clut;
130 uint16_t clut_level;
136 // Params that `clut` was last built from, for this piece alone. The LUT is expensive
137 // enough to be worth not rebuilding on every commit, but it must never be shared across
138 // pieces/instances: see the multi-instance cache-bleed writeup for this module.
140 gboolean clut_valid;
142
144{
145 // Truly global, read-only-after-init state only. Do NOT add a shared LUT/params cache
146 // here: this struct is the same instance for every module instance, every piece, and
147 // every pipe (full/preview/thumbnail/export/virtual-preview) of colorequal in the whole
148 // application -- there is exactly one of it, not one per instance. A memoized LUT stored
149 // here can only ever hold one instance's content at a time, and gets silently overwritten
150 // by whichever piece/instance last committed -- including disabled instances, since
151 // commit_params runs for disabled pieces too.
157
159{
161 GtkNotebook *ring_notebook;
187 gboolean has_focus;
188 gboolean picker_valid;
189 gboolean cursor_valid;
209
210const char *name()
211{
212 return _("color equalizer");
213}
214
215const char *aliases()
216{
217 return _("color zones");
218}
219
220const char **description(struct dt_iop_module_t *self)
221{
223 self, _("stretch RGB colors around the achromatic axis from editable dt UCS hue nodes"), _("creative"),
224 _("linear, RGB, display-referred"), _("linear, RGB"), _("linear, RGB, display-referred"));
225}
226
228{
229 return IOP_GROUP_COLOR;
230}
231
236
238{
239 return IOP_CS_RGB;
240}
241
242static void _update_gui_lut_cache(dt_iop_module_t *self);
243static void _switch_preview_cursor(dt_iop_module_t *self);
245static void _preview_cache_wait_restart(gpointer user_data);
246
247static inline float _channel_value_from_y(const dt_iop_colorequal_channel_t channel, const float y)
248{
249 switch(channel)
250 {
252 return (y - 0.5f) * 2.f * M_PI_F;
255 default:
256 return CLAMP(y * 2.f, 0.f, 2.f);
257 }
258}
259
260static inline float _channel_y_from_value(const dt_iop_colorequal_channel_t channel, const float value)
261{
262 switch(channel)
263 {
265 return CLAMP(value / (2.f * M_PI_F) + 0.5f, 0.f, 1.f);
268 default:
269 return CLAMP(value * 0.5f, 0.f, 1.f);
270 }
271}
272
273static inline const char *_ring_label(const dt_iop_colorequal_ring_t ring)
274{
275 switch(ring)
276 {
278 return _("shadows");
280 return _("highlights");
282 default:
283 return _("midtones");
284 }
285}
286
288 const dt_iop_colorequal_ring_t ring,
289 const dt_iop_colorequal_channel_t channel)
290{
291 return p->curve[ring][channel];
292}
293
295 const dt_iop_colorequal_ring_t ring,
296 const dt_iop_colorequal_channel_t channel)
297{
298 return p->curve[ring][channel];
299}
300
302 const dt_iop_colorequal_channel_t channel)
303{
304 return &p->curve_num_nodes[ring][channel];
305}
306
308 const dt_iop_colorequal_ring_t ring,
309 const dt_iop_colorequal_channel_t channel)
310{
311 return p->curve_num_nodes[ring][channel];
312}
313
315 const dt_iop_colorequal_channel_t channel)
316{
318 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
319 for(int k = 0; k < DT_IOP_COLOREQUAL_DEFAULT_NODES; k++)
320 {
321 curve[k].x = (float)k / (float)DT_IOP_COLOREQUAL_DEFAULT_NODES;
322 curve[k].y = 0.5f;
323 }
324}
325
332
333static inline gboolean _curve_fields_equal(const dt_iop_colorequal_params_t *const a,
334 const dt_iop_colorequal_params_t *const b)
335{
336 return !memcmp(a->curve_num_nodes, b->curve_num_nodes, sizeof(a->curve_num_nodes))
337 && !memcmp(a->curve, b->curve, sizeof(a->curve));
338}
339
340static inline gboolean _lut_fields_equal(const dt_iop_colorequal_params_t *const a,
341 const dt_iop_colorequal_params_t *const b)
342{
343 return _curve_fields_equal(a, b) && a->sigma_L == b->sigma_L && a->sigma_rho == b->sigma_rho
344 && a->sigma_theta == b->sigma_theta && a->neutral_protection == b->neutral_protection;
345}
346
347static inline float _curve_periodic_distance(const float x0, const float x1)
348{
349 const float distance = fabsf(x0 - x1);
350 return fminf(distance, 1.f - distance);
351}
352
354{
355 switch(CLAMP(page, 0, DT_IOP_COLOREQUAL_NUM_CHANNELS - 1))
356 {
357 case 1:
359 case 2:
361 case 0:
362 default:
364 }
365}
366
368{
369 const int page = (g && g->ring_notebook) ? gtk_notebook_get_current_page(g->ring_notebook) : -1;
370 if(page >= 0 && page < DT_IOP_COLOREQUAL_NUM_RINGS) return (dt_iop_colorequal_ring_t)page;
371 return (dt_iop_colorequal_ring_t)CLAMP(dt_conf_get_int("plugins/darkroom/colorequal/gui_ring_page"), 0,
373}
374
376 const dt_iop_colorequal_ring_t ring)
377{
378 const int page = (g && g->channel_notebook[ring]) ? gtk_notebook_get_current_page(g->channel_notebook[ring]) : -1;
379 if(page >= 0 && page < DT_IOP_COLOREQUAL_NUM_CHANNELS) return _channel_from_page(page);
380 return _channel_from_page(dt_conf_get_int("plugins/darkroom/colorequal/gui_channel_page"));
381}
382
384{
385 g->cursor_sample_valid = FALSE;
386 g->cursor_hue = 0.f;
387 memset(g->cursor_input_display, 0, sizeof(g->cursor_input_display));
388 memset(g->cursor_output_display, 0, sizeof(g->cursor_output_display));
389}
390
392 const dt_iop_colorequal_channel_t channel, const float hue,
393 float *curve_x, float *curve_y, float *offset_normalized)
394{
395 if(IS_NULL_PTR(p) || !isfinite(hue)) return FALSE;
396
397 const int nodes = _curve_nodes_count_const(p, ring, channel);
398 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
399 if(nodes < 1 || IS_NULL_PTR(curve)) return FALSE;
400
401 const float x = dt_colorrings_hue_to_curve_x(hue);
402 const float y = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, nodes, x);
403 const float value = _channel_value_from_y(channel, y);
404
405 if(!IS_NULL_PTR(curve_x)) *curve_x = x;
406 if(!IS_NULL_PTR(curve_y)) *curve_y = y;
407
408 if(!IS_NULL_PTR(offset_normalized))
409 {
410 switch(channel)
411 {
413 *offset_normalized = CLAMP(value / M_PI_F, -1.f, 1.f);
414 break;
417 default:
418 *offset_normalized = CLAMP(value - 1.f, -1.f, 1.f);
419 break;
420 }
421 }
422
423 return TRUE;
424}
425
427{
428 RGB[0] = CLAMP(RGB[0], 0.f, 1.f);
429 RGB[1] = CLAMP(RGB[1], 0.f, 1.f);
430 RGB[2] = CLAMP(RGB[2], 0.f, 1.f);
431 RGB[3] = 0.f;
432}
433
435 dt_aligned_pixel_t display_rgb)
436{
437 if(IS_NULL_PTR(display_rgb)) return;
438
439 memcpy(display_rgb, work_rgb, sizeof(dt_aligned_pixel_t));
440 _clamp_display_rgb(display_rgb);
441
442 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe)) return;
443
444 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
446 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(display_profile)) return;
447
448 float in[4] = { work_rgb[0], work_rgb[1], work_rgb[2], 0.f };
449 float out[4] = { work_rgb[0], work_rgb[1], work_rgb[2], 0.f };
450 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, work_profile, display_profile, "colorequal swatch");
451 display_rgb[0] = out[0];
452 display_rgb[1] = out[1];
453 display_rgb[2] = out[2];
454 _clamp_display_rgb(display_rgb);
455}
456
457static inline void _mix_rgb_anchors(const dt_aligned_pixel_t low, const dt_aligned_pixel_t high, const float mix,
459{
460 for(int c = 0; c < 3; c++) RGB[c] = low[c] * (1.f - mix) + high[c] * mix;
461}
462
463static inline void
465 const int ring, const float hue_position, dt_aligned_pixel_t RGB)
466{
467 const float hue = dt_colorrings_wrap_hue_2pi(hue_position * 2.f * M_PI_F) / (2.f * M_PI_F)
469 const int hue0 = ((int)floorf(hue)) % DT_IOP_COLOREQUAL_HUE_SAMPLES;
470 const int hue1 = (hue0 + 1) % DT_IOP_COLOREQUAL_HUE_SAMPLES;
471 const float mix = hue - floorf(hue);
472
473 for(int c = 0; c < 3; c++)
474 RGB[c] = ring_surface[ring][hue0][c] * (1.f - mix) + ring_surface[ring][hue1][c] * mix;
475}
476
483static inline void
485 const float brightness, const float hue_position, const float white,
487{
488 const float anchor_positions[DT_IOP_COLOREQUAL_NUM_RINGS + 2]
489 = { 0.f,
493 1.f };
494 int segment = 0;
495
496 while(segment < DT_IOP_COLOREQUAL_NUM_RINGS + 1 && brightness > anchor_positions[segment + 1]) segment++;
497
498 const float low_position = anchor_positions[segment];
499 const float high_position = anchor_positions[segment + 1];
500 const float mix
501 = (high_position > low_position) ? (brightness - low_position) / (high_position - low_position) : 0.f;
502 dt_aligned_pixel_t low = { 0.f };
503 dt_aligned_pixel_t high = { 0.f };
504
505 if(segment == 0)
506 {
507 dt_colorrings_brightness_to_axis_rgb(0.f, white, profile, low);
508 _sample_ring_hue(ring_surface, 0, hue_position, high);
509 }
510 else if(segment == DT_IOP_COLOREQUAL_NUM_RINGS)
511 {
512 _sample_ring_hue(ring_surface, DT_IOP_COLOREQUAL_NUM_RINGS - 1, hue_position, low);
513 dt_colorrings_brightness_to_axis_rgb(1.f, white, profile, high);
514 }
515 else
516 {
517 _sample_ring_hue(ring_surface, segment - 1, hue_position, low);
518 _sample_ring_hue(ring_surface, segment, hue_position, high);
519 }
520
521 _mix_rgb_anchors(low, high, CLAMP(mix, 0.f, 1.f), RGB);
522}
523
547 const dt_iop_order_iccprofile_info_t *lut_profile)
548{
549 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
550 const double start = log_perf ? dt_get_wtime() : 0.0;
551 const float white = dt_colorrings_graph_white();
552 const size_t clut_size
554
555 if(IS_NULL_PTR(d->clut)) d->clut = dt_alloc_align_float(clut_size);
556 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
557
558 dt_colorrings_compute_reference_saturations(white, d->reference_saturation);
565
566 for(int ring = 0; ring < DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS; ring++)
567 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
568 chroma_scale[ring][hue_sample] = 1.f;
569
575 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
576 {
577 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
578 const float brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)current_ring);
579 const float reference_saturation = d->reference_saturation[ring];
580 dt_aligned_pixel_t neutral_rgb = { 0.f };
581 dt_colorrings_brightness_to_axis_rgb(brightness, white, lut_profile, neutral_rgb);
582
583 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
584 {
585 const float x = (float)hue_sample / (float)DT_IOP_COLOREQUAL_HUE_SAMPLES;
586 const float hue = dt_colorrings_curve_x_to_hue(x);
587 const float hue_shift = _channel_value_from_y(
592 const float sat_gain = _channel_value_from_y(
597 const float bright_gain = _channel_value_from_y(
602
603 const dt_aligned_pixel_t before_hsb = { hue, reference_saturation, brightness, 0.f };
604 const dt_aligned_pixel_t after_hsb
605 = { dt_colorrings_wrap_hue_pi(hue + hue_shift), CLAMP(reference_saturation * sat_gain, 0.f, 1.f),
606 CLAMP(brightness * bright_gain, 0.f, 1.f), 0.f };
607
608 dt_aligned_pixel_t before_rgb = { 0.f };
609 dt_aligned_pixel_t after_rgb = { 0.f };
610 dt_colorrings_hsb_to_profile_rgb(before_hsb, white, lut_profile, before_rgb);
611 dt_colorrings_hsb_to_profile_rgb(after_hsb, white, lut_profile, after_rgb);
612 dt_colorrings_project_to_cube_shell(neutral_rgb, before_rgb);
613 dt_colorrings_project_to_cube_shell(neutral_rgb, after_rgb);
614
615 float Lp, rhop, thetap;
616 float La, rhoa;
617 float unused_theta;
618 dt_colorrings_rgb_to_gray_cyl(before_rgb, &Lp, &rhop, &thetap);
619 dt_colorrings_rgb_to_gray_cyl(after_rgb, &La, &rhoa, &unused_theta);
620
634 const float requested_scale = sat_gain;
635 const float projected_scale = (rhop > 1e-6f) ? (rhoa / rhop) : 1.f;
636 const float effective_scale
637 = (requested_scale <= 1.f) ? requested_scale : fminf(requested_scale, projected_scale);
638
639 anchor_L[ring][hue_sample] = Lp;
640 anchor_rho[ring][hue_sample] = rhop;
641 anchor_theta[ring][hue_sample] = thetap;
642 delta_L[ring][hue_sample] = La - Lp;
643 chroma_scale[ring][hue_sample] = effective_scale;
644 delta_theta[ring][hue_sample] = dt_colorrings_wrap_pi(hue_shift);
645 }
646 }
647
648 // Fill the achromatic locus after the hue rings
649 for(int sample = 0; sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; sample++)
650 {
651 const float value = (float)sample / (float)(DT_IOP_COLOREQUAL_HUE_SAMPLES - 1);
652 anchor_L[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = value * 1.7320508075688772f;
653 anchor_rho[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
654 anchor_theta[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
655 delta_L[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
656 chroma_scale[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 1.f;
657 delta_theta[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
658 }
659
671 const float sigma_L = fmaxf(p->sigma_L * 0.01f, 1e-6f);
672 const float sigma_rho = fmaxf(p->sigma_rho, 1e-6f);
673 const float sigma_theta = fmaxf(p->sigma_theta, 1e-6f);
674 const float neutral_protection = fmaxf(p->neutral_protection, 0.f);
675 dt_colorrings_fill_lut_local_field(d->clut, DT_IOP_COLOREQUAL_CLUT_LEVEL, anchor_L, anchor_rho, anchor_theta,
676 delta_L, chroma_scale, delta_theta, 1.f / sigma_L, 1.f / sigma_rho,
677 1.f / sigma_theta, neutral_protection * sigma_rho);
678
679 d->clut_level = DT_IOP_COLOREQUAL_CLUT_LEVEL;
680
681 if(log_perf)
682 dt_print(DT_DEBUG_PERF, "[colorequal] build_clut level=%u total=%.3fms\n", d->clut_level,
683 1000.0 * (dt_get_wtime() - start));
684}
685
687 const dt_iop_order_iccprofile_info_t *lut_profile,
688 dt_lut_viewer_control_node_t *control_nodes)
689{
690 if(IS_NULL_PTR(p) || IS_NULL_PTR(lut_profile) || IS_NULL_PTR(control_nodes)) return 0;
691
692 const float white = dt_colorrings_graph_white();
693 float reference_saturation[DT_IOP_COLOREQUAL_NUM_RINGS] = { 0.f };
694 size_t count = 0;
695
696 dt_colorrings_compute_reference_saturations(white, reference_saturation);
697
704 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
705 {
706 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
707 const float brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)current_ring);
708 const float saturation = reference_saturation[ring];
709 dt_aligned_pixel_t neutral_rgb = { 0.f };
710
711 dt_colorrings_brightness_to_axis_rgb(brightness, white, lut_profile, neutral_rgb);
712
713 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
714 {
715 const float x = (float)hue_sample / (float)DT_IOP_COLOREQUAL_HUE_SAMPLES;
716 const float hue = dt_colorrings_curve_x_to_hue(x);
717 const float hue_shift = _channel_value_from_y(
722 const float sat_gain = _channel_value_from_y(
727 const float bright_gain = _channel_value_from_y(
732 const dt_aligned_pixel_t before_hsb = { hue, saturation, brightness, 0.f };
733 const dt_aligned_pixel_t after_hsb
734 = { dt_colorrings_wrap_hue_pi(hue + hue_shift), CLAMP(saturation * sat_gain, 0.f, 1.f),
735 CLAMP(brightness * bright_gain, 0.f, 1.f), 0.f };
736 dt_aligned_pixel_t before_rgb = { 0.f };
737 dt_aligned_pixel_t after_rgb = { 0.f };
738
739 dt_colorrings_hsb_to_profile_rgb(before_hsb, white, lut_profile, before_rgb);
740 dt_colorrings_hsb_to_profile_rgb(after_hsb, white, lut_profile, after_rgb);
741 dt_colorrings_project_to_cube_shell(neutral_rgb, before_rgb);
742 dt_colorrings_project_to_cube_shell(neutral_rgb, after_rgb);
743
744 for(int c = 0; c < 3; c++)
745 {
746 control_nodes[count].input_rgb[c] = before_rgb[c];
747 control_nodes[count].output_rgb[c] = after_rgb[c];
748 }
749 count++;
750 }
751 }
752
753 return count;
754}
755
758{
761 const dt_iop_order_iccprofile_info_t *lut_profile
763 : NULL;
765
766 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(lut_profile))
767 {
768 // Leave d->clut's allocation (if any) alone -- it is this piece's own buffer now,
769 // not a borrowed pointer. clut_level == 0 already makes process()/process_cl() treat
770 // it as not-ready; freeing/reallocating it is cleanup_pipe's job.
771 d->clut_level = 0;
772 d->clut_valid = FALSE;
773 d->lut_profile = NULL;
774 d->work_profile = NULL;
775 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
776 return;
777 }
778
779 // This LUT is expensive enough to be worth memoizing, but the cache must be scoped to
780 // this piece alone -- see the dt_iop_colorequal_global_data_t comment for why a shared
781 // cache silently bleeds one instance's content into another's.
782 if(!d->clut_valid || !_lut_fields_equal(&d->built_params, p))
783 {
784 _build_clut(d, p, lut_profile);
785 d->built_params = *p;
786 d->clut_valid = TRUE;
787 }
788
789 d->white_level = exp2f(p->white_level);
790 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
791 d->work_profile = (dt_iop_order_iccprofile_info_t *)work_profile;
792 d->interpolation = (dt_lut3d_interpolation_t)p->interpolation;
793}
794
796{
798 piece->data = d;
799 piece->data_size = sizeof(dt_iop_colorequal_data_t);
800 d->clut = NULL;
801 d->clut_level = 0;
802 d->white_level = 1.f;
803 d->lut_profile = NULL;
804 memset(d->reference_saturation, 0, sizeof(d->reference_saturation));
805 d->work_profile = NULL;
806 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
807}
808
810{
812 dt_free_align(d->clut);
813 d->clut = NULL;
814 dt_free_align(piece->data);
815 piece->data = NULL;
816}
817
818#ifdef HAVE_OPENCL
819int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
820 cl_mem dev_in, cl_mem dev_out)
821{
824 const int width = piece->roi_in.width;
825 const int height = piece->roi_in.height;
826 const int devid = pipe->devid;
827 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
828 const float white_level = fmaxf(d->white_level, 1e-6f);
829 const float normalize = 1.f / white_level;
830 const float denormalize = white_level;
831 const float black = 0.f;
832 cl_mem clut_cl = NULL;
833 cl_int err = CL_SUCCESS;
834 const int kernel = (d->interpolation == DT_LUT3D_INTERP_TRILINEAR) ? gd->kernel_lut3d_trilinear
835 : (d->interpolation == DT_LUT3D_INTERP_PYRAMID) ? gd->kernel_lut3d_pyramid
837
838 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile)) return FALSE;
839
840 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_in);
841 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
842 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
843 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
844 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
845 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &normalize);
846 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
847 if(err != CL_SUCCESS) goto cleanup;
848
849 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->work_profile,
850 d->lut_profile, "colorequal work to HLG Rec2020"))
851 {
852 err = CL_INVALID_OPERATION;
853 goto cleanup;
854 }
855
856 clut_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3 * d->clut_level * d->clut_level * d->clut_level,
857 d->clut);
858 if(IS_NULL_PTR(clut_cl))
859 {
860 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
861 goto cleanup;
862 }
863
864 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dev_out);
865 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &dev_out);
866 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &width);
867 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &height);
868 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &clut_cl);
869 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &d->clut_level);
870 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
871 if(err != CL_SUCCESS) goto cleanup;
872
873 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->lut_profile,
874 d->work_profile, "colorequal HLG Rec2020 to work"))
875 {
876 err = CL_INVALID_OPERATION;
877 goto cleanup;
878 }
879
880 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_out);
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), &denormalize);
886 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
887
888cleanup:
890 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_colorequal] couldn't enqueue kernel! %d\n", err);
891 return err == CL_SUCCESS;
892}
893#endif
894
896int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
897 const void *const ibuf, void *const obuf)
898{
900 const int width = piece->roi_in.width;
901 const int height = piece->roi_in.height;
902 const int ch = piece->dsc_in.channels;
903
904 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile))
905 {
907 return 0;
908 }
909
910 const float white_level = fmaxf(d->white_level, 1e-6f);
912 for(size_t k = 0; k < (size_t)width * height; k++)
913 {
914 const float *const in = (const float *)ibuf + k * ch;
915 float *const out = (float *)obuf + k * ch;
916 out[0] = in[0] / white_level;
917 out[1] = in[1] / white_level;
918 out[2] = in[2] / white_level;
919 if(ch > 3) out[3] = in[3];
920 }
921
922 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->work_profile,
923 d->lut_profile, "colorequal work to HLG Rec2020");
924
925 dt_lut3d_apply((float *)obuf, (float *)obuf, (size_t)width * height, d->clut, d->clut_level, 1.f,
926 d->interpolation);
927
928 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->lut_profile, d->work_profile,
929 "colorequal HLG Rec2020 to work");
931 for(size_t k = 0; k < (size_t)width * height; k++)
932 {
933 float *const out = (float *)obuf + k * ch;
934 out[0] *= white_level;
935 out[1] *= white_level;
936 out[2] *= white_level;
937 }
938 return 0;
939}
940
949{
950 if(!self->enabled) return;
952 const dt_iop_colorequal_params_t *p = g ? &g->gui_params : (const dt_iop_colorequal_params_t *)self->params;
953 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
954 const double start = log_perf ? dt_get_wtime() : 0.0;
955
956 if(IS_NULL_PTR(g->viewer)) return;
957
958 if(!g->viewer_lut_dirty && g->viewer_lut_valid && _lut_fields_equal(&g->viewer_lut_params, p)) return;
959
960 const dt_iop_order_iccprofile_info_t *lut_profile
962 : NULL;
963 const dt_iop_order_iccprofile_info_t *display_profile
965 : NULL;
966
967 if(IS_NULL_PTR(lut_profile))
968 {
969 dt_lut_viewer_set_lut(g->viewer, NULL, 0, NULL, NULL, NULL);
970 dt_lut_viewer_set_control_nodes(g->viewer, NULL, 0);
971 g->viewer_lut_dirty = FALSE;
972 g->viewer_lut_valid = FALSE;
973 g->viewer_control_node_count = 0;
974 return;
975 }
976
977 _build_clut(&g->viewer_lut, p, lut_profile);
978 g->viewer_lut_params = *p;
979 g->viewer_lut.lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
980 g->viewer_control_node_count = _build_viewer_control_nodes(p, lut_profile, g->viewer_control_nodes);
981 dt_lut_viewer_set_lut(g->viewer, g->viewer_lut.clut, g->viewer_lut.clut_level, NULL,
982 g->viewer_lut.lut_profile, display_profile);
983 dt_lut_viewer_set_control_nodes(g->viewer, g->viewer_control_nodes, g->viewer_control_node_count);
984 g->viewer_lut_dirty = FALSE;
985 g->viewer_lut_valid = TRUE;
986
988
989 if(log_perf)
990 dt_print(DT_DEBUG_PERF, "[colorequal] gui LUT cache sync level=%u total=%.3fms\n", g->viewer_lut.clut_level,
991 1000.0 * (dt_get_wtime() - start));
992}
993
1001static void _update_gui_lut_cache_throttled(gpointer data)
1002{
1004}
1005
1007{
1008 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
1009 const double start = log_perf ? dt_get_wtime() : 0.0;
1010 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1011 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
1012 {
1013 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
1015 const int nodes = _curve_nodes_count_const(p, current_ring, channel);
1016 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, current_ring, channel);
1017
1018 if(!g->curve[ring][ch] || g->curve_nodes[ring][ch] != nodes || g->curve[ring][ch]->c.m_numAnchors != nodes)
1019 {
1020 if(g->curve[ring][ch]) dt_draw_curve_destroy(g->curve[ring][ch]);
1021 g->curve[ring][ch] = dt_draw_curve_new(0.f, 1.f, MONOTONE_HERMITE);
1022 g->curve_nodes[ring][ch] = nodes;
1023
1024 for(int k = 0; k < nodes; k++) dt_draw_curve_add_point(g->curve[ring][ch], curve[k].x, curve[k].y);
1025 }
1026 else
1027 {
1028 for(int k = 0; k < nodes; k++) dt_draw_curve_set_point(g->curve[ring][ch], k, curve[k].x, curve[k].y);
1029 }
1030
1031 dt_draw_curve_calc_values_V2(g->curve[ring][ch], 0.f, 1.f, DT_IOP_COLOREQUAL_GRAPH_RES, NULL,
1032 g->draw_ys[ring][ch], TRUE);
1033 }
1034
1035 memcpy(&g->cached_curve_params, p, sizeof(*p));
1036 g->curve_cache_valid = TRUE;
1037
1038 if(log_perf)
1039 dt_print(DT_DEBUG_PERF, "[colorequal] gui curve cache rebuild total=%.3fms\n",
1040 1000.0 * (dt_get_wtime() - start));
1041}
1042
1043static inline void _graph_background_hsb(const dt_iop_colorequal_channel_t channel, const float x, const float y,
1044 const float ring_brightness, const float reference_saturation,
1046{
1047 const float hue = dt_colorrings_curve_x_to_hue(x);
1048
1049 switch(channel)
1050 {
1052 HSB[0] = dt_colorrings_wrap_hue_pi(hue + _channel_value_from_y(channel, y));
1053 HSB[1] = reference_saturation;
1054 HSB[2] = ring_brightness;
1055 break;
1057 HSB[0] = hue;
1058 HSB[1] = reference_saturation;
1059 HSB[2] = CLAMP(ring_brightness * _channel_value_from_y(channel, y), 0.f, 1.f);
1060 break;
1062 default:
1063 HSB[0] = hue;
1064 HSB[1] = CLAMP(reference_saturation * _channel_value_from_y(channel, y), 0.f, 1.f);
1065 HSB[2] = ring_brightness;
1066 break;
1067 }
1068}
1069
1070static void _draw_graph_background(cairo_t *cr, const dt_iop_colorequal_channel_t channel,
1071 const dt_iop_colorequal_ring_t ring, const float graph_width,
1072 const float graph_height, const float white, const float reference_saturation,
1073 const dt_iop_order_iccprofile_info_t *display_profile)
1074{
1075 const float ring_brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)ring);
1077
1078 // Fast loop
1080 for(int slice = 0; slice < DT_IOP_COLOREQUAL_GRAPH_GRADIENTS; slice++)
1081 {
1082 const float y = (float)(DT_IOP_COLOREQUAL_GRAPH_GRADIENTS - slice) / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS;
1083 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1084 {
1085 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1086 float *const colors = bg + (slice * DT_IOP_COLOREQUAL_GRAPH_RES + k) * 4;
1087 dt_aligned_pixel_t HSB = { 0.f };
1088 _graph_background_hsb(channel, x, y, ring_brightness, reference_saturation, HSB);
1089 dt_colorrings_hsb_to_display_rgb(HSB, white, display_profile, colors);
1090 }
1091 }
1092
1093 // Slow loop
1094 for(int slice = 0; slice < DT_IOP_COLOREQUAL_GRAPH_GRADIENTS; slice++)
1095 {
1096 cairo_pattern_t *gradient = cairo_pattern_create_linear(0.0, 0.0, graph_width, 0.0);
1097 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1098 {
1099 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1100 float *const colors = bg + (slice * DT_IOP_COLOREQUAL_GRAPH_RES + k) * 4;
1101 cairo_pattern_add_color_stop_rgba(gradient, x, colors[0], colors[1], colors[2], 1.0);
1102 }
1103
1104 cairo_rectangle(cr, 0.f, graph_height / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS * (float)slice, graph_width,
1105 graph_height / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS);
1106 cairo_set_source(cr, gradient);
1107 cairo_fill(cr);
1108 cairo_pattern_destroy(gradient);
1109 }
1110 dt_free_align(bg);
1111}
1112
1113static gboolean _draw_curve(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1114{
1115 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1117 const dt_iop_colorequal_params_t *p = &g->gui_params;
1118 const dt_iop_colorequal_ring_t ring
1119 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1120 const dt_iop_colorequal_channel_t channel
1121 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1122
1123 if(!g->curve_cache_valid || !_curve_fields_equal(&g->cached_curve_params, p)) _update_curve_cache(g, p);
1124
1125 GtkAllocation allocation;
1126 gtk_widget_get_allocation(widget, &allocation);
1127 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1128
1129 const float inset = DT_IOP_COLOREQUAL_GRAPH_INSET;
1130 const float graph_width = allocation.width - 2.f * inset;
1131 const float graph_height = allocation.height - DT_IOP_COLOREQUAL_AXIS_HEIGHT - 2.f * inset;
1132 const float white = dt_colorrings_graph_white();
1133 const dt_iop_order_iccprofile_info_t *const display_profile
1135 : NULL;
1136 dt_colorrings_compute_reference_saturations(white, g->reference_saturation);
1144 const float background_saturation
1145 = (channel == DT_IOP_COLOREQUAL_HUE) ? g->reference_saturation[ring]
1146 : CLAMP(g->reference_saturation[ring] * 1.35f, 0.f, 1.f);
1147
1148 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1149 cairo_t *cr = cairo_create(cst);
1150
1151 if(!g->background_surface[ring][channel] || g->cached_width[ring][channel] != allocation.width
1152 || g->cached_height[ring][channel] != allocation.height
1153 || fabsf(g->cached_white[ring][channel] - white) > 1e-6f
1154 || fabsf(g->cached_reference_saturation[ring][channel] - background_saturation) > 1e-6f
1155 || g->cached_display_profile[ring][channel] != display_profile)
1156 {
1157 if(g->background_surface[ring][channel]) cairo_surface_destroy(g->background_surface[ring][channel]);
1158 g->background_surface[ring][channel]
1159 = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1160 cairo_t *background_cr = cairo_create(g->background_surface[ring][channel]);
1161
1162 gtk_render_background(context, background_cr, 0, 0, allocation.width, allocation.height);
1163 cairo_translate(background_cr, inset, inset);
1164 _draw_graph_background(background_cr, channel, ring, graph_width, graph_height, white, background_saturation,
1165 display_profile);
1166
1167 cairo_rectangle(background_cr, 0.f, 0.f, graph_width, graph_height);
1168 cairo_clip(background_cr);
1169
1170 cairo_set_line_width(background_cr, DT_PIXEL_APPLY_DPI(0.5f));
1171 set_color(background_cr, darktable.bauhaus->graph_border);
1172 dt_draw_grid(background_cr, 8, 0, 0, graph_width, graph_height);
1173
1174 set_color(background_cr, darktable.bauhaus->graph_fg);
1175 cairo_set_line_width(background_cr, DT_PIXEL_APPLY_DPI(1.f));
1176 cairo_move_to(background_cr, 0.f, 0.5f * graph_height);
1177 cairo_line_to(background_cr, graph_width, 0.5f * graph_height);
1178 cairo_stroke(background_cr);
1179
1180 cairo_reset_clip(background_cr);
1181 cairo_translate(background_cr, 0.f, graph_height);
1182 cairo_pattern_t *axis = cairo_pattern_create_linear(0.0, 0.0, graph_width, 0.0);
1183
1184 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1185 {
1186 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1187 dt_aligned_pixel_t RGB = { 0.f };
1189 (dt_aligned_pixel_t){ dt_colorrings_curve_x_to_hue(x), background_saturation,
1191 white, display_profile, RGB);
1192 cairo_pattern_add_color_stop_rgba(axis, x, RGB[0], RGB[1], RGB[2], 1.0);
1193 }
1194
1195 cairo_rectangle(background_cr, 0.f, 0.f, graph_width, DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1196 cairo_set_source(background_cr, axis);
1197 cairo_fill(background_cr);
1198 cairo_pattern_destroy(axis);
1199 cairo_destroy(background_cr);
1200
1201 g->cached_width[ring][channel] = allocation.width;
1202 g->cached_height[ring][channel] = allocation.height;
1203 g->cached_white[ring][channel] = white;
1204 g->cached_reference_saturation[ring][channel] = background_saturation;
1205 g->cached_display_profile[ring][channel] = display_profile;
1206 }
1207
1208 cairo_set_source_surface(cr, g->background_surface[ring][channel], 0, 0);
1209 cairo_paint(cr);
1210
1211 cairo_translate(cr, inset, inset);
1212
1213 if(g->picker_valid)
1214 {
1215 const float picker_x = dt_colorrings_hue_to_curve_x(g->picker_hue) * graph_width;
1216 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5f));
1218 cairo_move_to(cr, picker_x, 0.f);
1219 cairo_line_to(cr, picker_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1220 cairo_stroke(cr);
1221 }
1222
1224 const dt_iop_colorequal_channel_t active_channel = _active_channel_from_gui(g, active_ring);
1225 if(g->cursor_sample_valid && ring == active_ring && channel == active_channel)
1226 {
1227 const float cursor_x = dt_colorrings_hue_to_curve_x(g->cursor_hue) * graph_width;
1228 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.5f));
1229 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.75f);
1230 cairo_move_to(cr, cursor_x, 0.f);
1231 cairo_line_to(cr, cursor_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1232 cairo_stroke(cr);
1233
1234 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f));
1235 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1236 0.95);
1237 cairo_move_to(cr, cursor_x, 0.f);
1238 cairo_line_to(cr, cursor_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1239 cairo_stroke(cr);
1240 }
1241
1242 cairo_rectangle(cr, 0.f, 0.f, graph_width, graph_height);
1243 cairo_clip(cr);
1244
1245 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
1247
1248 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1249 {
1250 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1) * graph_width;
1251 const float y = (1.f - g->draw_ys[ring][channel][k]) * graph_height;
1252
1253 if(k == 0)
1254 cairo_move_to(cr, x, y);
1255 else
1256 cairo_line_to(cr, x, y);
1257 }
1258
1259 cairo_stroke(cr);
1260
1261 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
1262 const int nodes = _curve_nodes_count_const(p, ring, channel);
1263
1264 for(int k = 0; k < nodes; k++)
1265 {
1266 const float x = curve[k].x * graph_width;
1267 const float y = (1.f - curve[k].y) * graph_height;
1268 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.f));
1269 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);
1271 cairo_stroke_preserve(cr);
1273 cairo_fill(cr);
1274 }
1275
1276 if(g->cursor_sample_valid && ring == active_ring && channel == active_channel)
1277 {
1278 float curve_x = 0.f;
1279 float curve_y = 0.f;
1280 float offset_normalized = 0.f;
1281 if(_cursor_curve_state(p, ring, channel, g->cursor_hue, &curve_x, &curve_y, &offset_normalized))
1282 {
1283 const float marker_x = curve_x * graph_width;
1284 const float marker_y = (1.f - curve_y) * graph_height;
1285 const float outer_radius = DT_PIXEL_APPLY_DPI(7.f);
1286 const float inner_radius = DT_PIXEL_APPLY_DPI(4.f);
1287 const float intensity_radius = DT_PIXEL_APPLY_DPI(11.f);
1288
1289 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f));
1290 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.4f);
1291 cairo_arc(cr, marker_x, marker_y, intensity_radius, 0.f, 2.f * M_PI_F);
1292 cairo_stroke(cr);
1293
1294 if(fabsf(offset_normalized) > 1e-4f)
1295 {
1296 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.25f));
1297 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1],
1298 g->cursor_output_display[2], 0.95);
1299 if(offset_normalized > 0.f)
1300 cairo_arc(cr, marker_x, marker_y, intensity_radius, -M_PI_F / 2.f,
1301 -M_PI_F / 2.f + 2.f * M_PI_F * fabsf(offset_normalized));
1302 else
1303 cairo_arc_negative(cr, marker_x, marker_y, intensity_radius, -M_PI_F / 2.f,
1304 -M_PI_F / 2.f - 2.f * M_PI_F * fabsf(offset_normalized));
1305 cairo_stroke(cr);
1306 }
1307
1308 cairo_arc(cr, marker_x, marker_y, outer_radius, 0.f, 2.f * M_PI_F);
1309 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1],
1310 g->cursor_output_display[2], 0.95);
1311 cairo_fill_preserve(cr);
1312 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.9f);
1313 cairo_stroke(cr);
1314
1315 cairo_arc(cr, marker_x, marker_y, inner_radius, 0.f, 2.f * M_PI_F);
1316 cairo_set_source_rgba(cr, g->cursor_input_display[0], g->cursor_input_display[1], g->cursor_input_display[2],
1317 0.95);
1318 cairo_fill_preserve(cr);
1319 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.8f);
1320 cairo_stroke(cr);
1321 }
1322 }
1323
1324 cairo_reset_clip(cr);
1325
1326 cairo_destroy(cr);
1327 cairo_set_source_surface(crf, cst, 0, 0);
1328 cairo_paint(crf);
1329 cairo_surface_destroy(cst);
1330 return TRUE;
1331}
1332
1333static void _cacheline_ready_callback(gpointer instance, const guint64 hash,
1334 const guint64 producer_node_key, gpointer user_data)
1335{
1336 (void)instance;
1337 (void)producer_node_key;
1338 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1340 if(g->pending_preview_hash != hash || !_refresh_preview_cursor_sample(self)) return;
1341
1344 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1346}
1347
1348static void _preview_cache_wait_restart(gpointer user_data)
1349{
1350 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1351 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->gui_data)) return;
1352
1354 if(IS_NULL_PTR(g) || !g->has_focus || !_refresh_preview_cursor_sample(self)) return;
1355
1358 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1360}
1361
1363 const dt_iop_colorequal_channel_t channel, const float mouse_x, const float mouse_y,
1364 const float graph_width, const float graph_height)
1365{
1367 const dt_iop_colorequal_params_t *p = &g->gui_params;
1368 int selected = -1;
1369 float best = DT_PIXEL_APPLY_DPI(10.f) * DT_PIXEL_APPLY_DPI(10.f);
1370 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
1371 const int nodes = _curve_nodes_count_const(p, ring, channel);
1372
1373 for(int k = 0; k < nodes; k++)
1374 {
1375 const float node_x = curve[k].x * graph_width;
1376 const float node_y = (1.f - curve[k].y) * graph_height;
1377 const float dx = mouse_x - node_x;
1378 const float dy = mouse_y - node_y;
1379 const float distance = dx * dx + dy * dy;
1380
1381 if(distance < best)
1382 {
1383 best = distance;
1384 selected = k;
1385 }
1386 }
1387
1388 return selected;
1389}
1390
1391static int _add_node(dt_iop_colorequal_node_t *curve, int *nodes, const float x, const float y)
1392{
1393 int selected = -1;
1394
1395 if(curve[0].x > x)
1396 selected = 0;
1397 else
1398 {
1399 for(int k = 1; k < *nodes; k++)
1400 if(curve[k].x > x)
1401 {
1402 selected = k;
1403 break;
1404 }
1405 }
1406
1407 if(selected == -1) selected = *nodes;
1408
1409 if((selected > 0 && x - curve[selected - 1].x <= DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1410 || (selected < *nodes && curve[selected].x - x <= DT_IOP_COLOREQUAL_MIN_X_DISTANCE))
1411 return -1;
1412
1413 for(int k = *nodes; k > selected; k--)
1414 {
1415 curve[k].x = curve[k - 1].x;
1416 curve[k].y = curve[k - 1].y;
1417 }
1418
1419 curve[selected].x = x;
1420 curve[selected].y = y;
1421 (*nodes)++;
1422 return selected;
1423}
1424
1426 const dt_iop_colorequal_channel_t channel, const int node, const float x,
1427 const float y)
1428{
1430 dt_iop_colorequal_params_t *p = &g->gui_params;
1431 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
1432 const int nodes = _curve_nodes_count_const(p, ring, channel);
1433
1434 float new_x = CLAMP(x, 0.f, 0.999f);
1435 const float new_y = CLAMP(y, 0.f, 1.f);
1436
1437 if(node == 0)
1438 {
1439 if(new_x + 1.f - curve[nodes - 1].x < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1440 new_x = curve[nodes - 1].x + DT_IOP_COLOREQUAL_MIN_X_DISTANCE - 1.f;
1441 }
1442 else if(node == nodes - 1)
1443 {
1444 if(curve[0].x + 1.f - new_x < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1445 new_x = curve[0].x + 1.f - DT_IOP_COLOREQUAL_MIN_X_DISTANCE;
1446 }
1447 else if((new_x - curve[node - 1].x) < DT_IOP_COLOREQUAL_MIN_X_DISTANCE
1448 || (curve[node + 1].x - new_x) < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1449 {
1450 return FALSE;
1451 }
1452
1453 curve[node].x = new_x;
1454 curve[node].y = new_y;
1455 return TRUE;
1456}
1457
1458static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1459{
1460 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1462 const dt_iop_colorequal_ring_t ring
1463 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1464 const dt_iop_colorequal_channel_t channel
1465 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1466 GtkAllocation allocation;
1467 gtk_widget_get_allocation(widget, &allocation);
1468
1469 const float graph_width = allocation.width - 2.f * DT_IOP_COLOREQUAL_GRAPH_INSET;
1470 const float graph_height
1472 const float mouse_x = CLAMP(event->x - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_width);
1473 const float mouse_y = CLAMP(event->y - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_height);
1474
1475 if(g->dragging[ring][channel] && g->selected[ring][channel] >= 0)
1476 {
1477 if(_move_selected_node(self, ring, channel, g->selected[ring][channel], mouse_x / graph_width,
1478 1.f - mouse_y / graph_height))
1479 {
1480 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1481 g->curve_cache_valid = FALSE;
1482 g->viewer_lut_dirty = TRUE;
1483 if(g->cursor_valid && g->has_focus)
1484 {
1487 }
1490 gtk_widget_queue_draw(widget);
1491 }
1492
1493 return TRUE;
1494 }
1495
1496 g->selected[ring][channel]
1497 = _find_selected_node(self, ring, channel, mouse_x, mouse_y, graph_width, graph_height);
1498 gtk_widget_queue_draw(widget);
1499 return TRUE;
1500}
1501
1502static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1503{
1504 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1506 const dt_iop_colorequal_ring_t ring
1507 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1508 const dt_iop_colorequal_channel_t channel
1509 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1510 dt_iop_colorequal_params_t *p = &g->gui_params;
1512 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
1513 dt_iop_colorequal_node_t *default_curve = _curve_nodes(d, ring, channel);
1514 int *nodes = _curve_nodes_count(p, ring, channel);
1515 const int default_nodes = _curve_nodes_count_const(d, ring, channel);
1516
1517 GtkAllocation allocation;
1518 gtk_widget_get_allocation(widget, &allocation);
1519 const float graph_width = allocation.width - 2.f * DT_IOP_COLOREQUAL_GRAPH_INSET;
1520 const float graph_height
1522 const float mouse_x = CLAMP(event->x - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_width) / graph_width;
1523 const float mouse_y = 1.f - CLAMP(event->y - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_height) / graph_height;
1524
1525 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1526 {
1527 *nodes = default_nodes;
1528 for(int k = 0; k < default_nodes; k++) curve[k] = default_curve[k];
1529
1530 g->selected[ring][channel] = -1;
1531 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1532 g->curve_cache_valid = FALSE;
1533 g->viewer_lut_dirty = TRUE;
1534 if(g->cursor_valid && g->has_focus)
1535 {
1538 }
1541 gtk_widget_queue_draw(widget);
1542 return TRUE;
1543 }
1544
1545 if(event->button == 1 && dt_modifier_is(event->state, GDK_CONTROL_MASK) && *nodes < DT_IOP_COLOREQUAL_MAXNODES)
1546 {
1547 const float y
1548 = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, *nodes, mouse_x);
1549 const int selected = _add_node(curve, nodes, mouse_x, y);
1550
1551 if(selected >= 0)
1552 {
1553 g->selected[ring][channel] = selected;
1554 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1555 g->curve_cache_valid = FALSE;
1556 g->viewer_lut_dirty = TRUE;
1557 if(g->cursor_valid && g->has_focus)
1558 {
1561 }
1564 gtk_widget_queue_draw(widget);
1565 }
1566
1567 return TRUE;
1568 }
1569
1570 if(event->button == 1)
1571 {
1572 g->selected[ring][channel] = _find_selected_node(self, ring, channel, mouse_x * graph_width,
1573 (1.f - mouse_y) * graph_height, graph_width, graph_height);
1574 g->dragging[ring][channel] = (g->selected[ring][channel] >= 0);
1575 return TRUE;
1576 }
1577
1578 if(event->button == 3 && g->selected[ring][channel] >= 0 && *nodes > 2)
1579 {
1580 for(int k = g->selected[ring][channel]; k < *nodes - 1; k++) curve[k] = curve[k + 1];
1581
1582 (*nodes)--;
1583 g->selected[ring][channel] = -1;
1584 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1585 g->curve_cache_valid = FALSE;
1586 g->viewer_lut_dirty = TRUE;
1587 if(g->cursor_valid && g->has_focus)
1588 {
1591 }
1594 gtk_widget_queue_draw(widget);
1595 return TRUE;
1596 }
1597
1598 return FALSE;
1599}
1600
1601static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1602{
1603 if(event->button == 1)
1604 {
1605 const dt_iop_colorequal_ring_t ring
1606 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1607 const dt_iop_colorequal_channel_t channel
1608 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1609 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1611 const gboolean was_dragging = g->dragging[ring][channel];
1612 g->dragging[ring][channel] = FALSE;
1613
1623 if(was_dragging)
1624 {
1627 dt_gui_throttle_cancel(&g->viewer_lut);
1629 }
1630 }
1631
1632 return TRUE;
1633}
1634
1635static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num,
1636 gpointer user_data)
1637{
1638 if(dt_gui_widgets_suppressed()) return;
1639
1640 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1642 const int source_ring = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(notebook), "colorequal-ring"));
1643 const dt_iop_colorequal_channel_t channel
1644 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(page), "colorequal-channel"));
1645 dt_conf_set_int("plugins/darkroom/colorequal/gui_channel_page", (int)page_num);
1646
1647 if(channel < DT_IOP_COLOREQUAL_NUM_CHANNELS)
1648 {
1650 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1651 if(ring != source_ring) gtk_notebook_set_current_page(g->channel_notebook[ring], (int)page_num);
1653
1654 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1655 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1656
1657 if(g->cursor_valid && g->has_focus) dt_control_queue_redraw_center();
1658 }
1659}
1660
1661static void _ring_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
1662{
1663 if(dt_gui_widgets_suppressed()) return;
1664
1665 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1667
1668 if(page_num < DT_IOP_COLOREQUAL_NUM_RINGS)
1669 {
1670 dt_conf_set_int("plugins/darkroom/colorequal/gui_ring_page", (int)page_num);
1671 GtkWidget *page_widget = gtk_notebook_get_nth_page(
1672 g->channel_notebook[page_num], gtk_notebook_get_current_page(g->channel_notebook[page_num]));
1673 if(page_widget)
1674 {
1675 const dt_iop_colorequal_channel_t channel = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(
1676 g_object_get_data(G_OBJECT(page_widget), "colorequal-channel"));
1677 if(channel < DT_IOP_COLOREQUAL_NUM_CHANNELS) gtk_widget_queue_draw(GTK_WIDGET(g->area[page_num][channel]));
1678 }
1679 }
1680
1681 if(g->cursor_valid && g->has_focus)
1682 {
1685 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1687 }
1688}
1689
1690int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
1691{
1693 dt_develop_t *dev = self ? self->dev : NULL;
1694 if(!g->has_focus) return 0;
1695
1697 {
1698 g->cursor_valid = FALSE;
1702 return 0;
1703 }
1704
1705 const int wd = dev->roi.preview_width;
1706 const int ht = dev->roi.preview_height;
1707 if(wd < 1 || ht < 1) return 0;
1708
1709 float point[2] = { (float)x, (float)y };
1712
1713 const int cursor_x = (int)point[0];
1714 const int cursor_y = (int)point[1];
1715 if(cursor_x >= 0 && cursor_x < wd && cursor_y >= 0 && cursor_y < ht)
1716 {
1717 g->cursor_valid = TRUE;
1718 g->cursor_pos_x = cursor_x;
1719 g->cursor_pos_y = cursor_y;
1720
1722 }
1723 else
1724 {
1725 g->cursor_valid = FALSE;
1727 }
1728
1730
1733 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1735 return g->cursor_valid ? 1 : 0;
1736}
1737
1739{
1741 g->cursor_valid = FALSE;
1744
1747 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1749 return 1;
1750}
1751
1752int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
1753 uint32_t state)
1754{
1756 if(!g->has_focus || which != 3 || !g->cursor_valid || !g->cursor_sample_valid
1758 return 0;
1759
1760 if(!self->enabled)
1761 {
1762 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
1763 return 1;
1764 }
1765
1768 dt_iop_colorequal_node_t *curve = _curve_nodes(&g->gui_params, ring, channel);
1769 int *nodes = _curve_nodes_count(&g->gui_params, ring, channel);
1770 if(*nodes >= DT_IOP_COLOREQUAL_MAXNODES) return 1;
1771
1772 const float curve_x = dt_colorrings_hue_to_curve_x(g->cursor_hue);
1773 const float curve_y
1774 = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, *nodes, curve_x);
1775 const int selected = _add_node(curve, nodes, curve_x, curve_y);
1776 if(selected < 0) return 1;
1777
1778 g->selected[ring][channel] = selected;
1779 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1780 g->curve_cache_valid = FALSE;
1781 g->viewer_lut_dirty = TRUE;
1783 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1787 return 1;
1788}
1789
1790int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
1791{
1793 if(!g->has_focus || !g->cursor_valid || !g->cursor_sample_valid || dt_iop_color_picker_is_visible(self->dev))
1794 return 0;
1795
1796 if(!self->enabled)
1797 {
1798 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
1799 return 1;
1800 }
1801
1804 dt_iop_colorequal_node_t *curve = _curve_nodes(&g->gui_params, ring, channel);
1805 const int nodes = _curve_nodes_count_const(&g->gui_params, ring, channel);
1806 if(nodes < 1) return 1;
1807
1808 const float direction = up ? 1.f : -1.f;
1809 const float curve_x = dt_colorrings_hue_to_curve_x(g->cursor_hue);
1811 const float sigma2 = 2.f * sigma * sigma;
1812 const float step = (channel == DT_IOP_COLOREQUAL_HUE)
1819
1820 for(int k = 0; k < nodes; k++)
1821 {
1822 const float distance = _curve_periodic_distance(curve[k].x, curve_x);
1823 const float weight = expf(-(distance * distance) / sigma2);
1824 float value = _channel_value_from_y(channel, curve[k].y);
1825 value += direction * step * weight;
1826
1827 switch(channel)
1828 {
1830 value = CLAMP(value, -M_PI_F, M_PI_F);
1831 break;
1834 default:
1835 value = CLAMP(value, 0.f, 2.f);
1836 break;
1837 }
1838
1839 curve[k].y = _channel_y_from_value(channel, value);
1840 }
1841
1842 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1843 g->curve_cache_valid = FALSE;
1844 g->viewer_lut_dirty = TRUE;
1846 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1850 return 1;
1851}
1852
1853void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
1854 int32_t pointerx, int32_t pointery)
1855{
1857 dt_develop_t *dev = self ? self->dev : NULL;
1858 if(IS_NULL_PTR(g) || IS_NULL_PTR(dev)) return;
1859 if(!g->has_focus || !self->enabled || !g->cursor_valid || dt_iop_color_picker_is_visible(dev))
1860 return;
1861
1862 if(!g->cursor_sample_valid)
1863 {
1864 if(!_refresh_preview_cursor_sample(self)) return;
1865 }
1866
1869 float curve_x = 0.f;
1870 float curve_y = 0.f;
1871 float offset_normalized = 0.f;
1872 if(!_cursor_curve_state(&g->gui_params, ring, channel, g->cursor_hue, &curve_x, &curve_y, &offset_normalized))
1873 return;
1874
1875 const float zoom_scale = dt_dev_get_overlay_scale(dev);
1876 dt_dev_rescale_roi(dev, cr, width, height);
1877
1878 const float pointer_x = g->cursor_pos_x;
1879 const float pointer_y = g->cursor_pos_y;
1880 const float outer_radius = DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS / zoom_scale;
1881 const float inner_radius = outer_radius * 0.55f;
1882 const float intensity_radius = outer_radius * 1.55f;
1883 const float crosshair_gap = outer_radius * 0.25f;
1884 const float crosshair_extent = intensity_radius + DT_PIXEL_APPLY_DPI(5.f) / zoom_scale;
1885
1886 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5f) / zoom_scale);
1887 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.35f);
1888 cairo_arc(cr, pointer_x, pointer_y, intensity_radius, 0.f, 2.f * M_PI_F);
1889 cairo_stroke(cr);
1890
1891 if(fabsf(offset_normalized) > 1e-4f)
1892 {
1893 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.f) / zoom_scale);
1894 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1895 0.95);
1896 if(offset_normalized > 0.f)
1897 cairo_arc(cr, pointer_x, pointer_y, intensity_radius, -M_PI_F / 2.f,
1898 -M_PI_F / 2.f + 2.f * M_PI_F * fabsf(offset_normalized));
1899 else
1900 cairo_arc_negative(cr, pointer_x, pointer_y, intensity_radius, -M_PI_F / 2.f,
1901 -M_PI_F / 2.f - 2.f * M_PI_F * fabsf(offset_normalized));
1902 cairo_stroke(cr);
1903 }
1904
1905 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f) / zoom_scale);
1906 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.6f);
1907 cairo_move_to(cr, pointer_x - crosshair_extent, pointer_y);
1908 cairo_line_to(cr, pointer_x - outer_radius - crosshair_gap, pointer_y);
1909 cairo_move_to(cr, pointer_x + outer_radius + crosshair_gap, pointer_y);
1910 cairo_line_to(cr, pointer_x + crosshair_extent, pointer_y);
1911 cairo_move_to(cr, pointer_x, pointer_y - crosshair_extent);
1912 cairo_line_to(cr, pointer_x, pointer_y - outer_radius - crosshair_gap);
1913 cairo_move_to(cr, pointer_x, pointer_y + outer_radius + crosshair_gap);
1914 cairo_line_to(cr, pointer_x, pointer_y + crosshair_extent);
1915 cairo_stroke(cr);
1916
1917 cairo_arc(cr, pointer_x, pointer_y, outer_radius, 0.f, 2.f * M_PI_F);
1918 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1919 0.95);
1920 cairo_fill_preserve(cr);
1921 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.9f);
1922 cairo_stroke(cr);
1923
1924 cairo_arc(cr, pointer_x, pointer_y, inner_radius, 0.f, 2.f * M_PI_F);
1925 cairo_set_source_rgba(cr, g->cursor_input_display[0], g->cursor_input_display[1], g->cursor_input_display[2], 0.95);
1926 cairo_fill_preserve(cr);
1927 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.8f);
1928 cairo_stroke(cr);
1929}
1930
1933{
1935 if(IS_NULL_PTR(work_profile)) return;
1936 dt_colorrings_profile_rgb_to_Ych(RGB, work_profile, Ych);
1937}
1938
1941{
1943 if(IS_NULL_PTR(work_profile)) return;
1945}
1946
1948{
1951
1952 if(!widget || !gtk_widget_get_window(widget)) return;
1953
1955
1956 if(!g->has_focus || dt_iop_color_picker_is_visible(self->dev))
1957 {
1959 return;
1960 }
1961
1962 if(g->cursor_valid && self->dev && self->dev->preview_pipe && self->dev->preview_pipe->processing)
1963 {
1965 return;
1966 }
1967
1968 if(g->cursor_valid && self->enabled)
1969 {
1972 _("scroll over image to adjust the selected color graph\n"
1973 "right-click to add a node at the sampled hue"));
1974 return;
1975 }
1976
1978}
1979
1981{
1983 dt_develop_t *dev = self ? self->dev : NULL;
1984 if(!self->enabled || !g->cursor_valid)
1985 {
1986 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-preview-inactive");
1988 return FALSE;
1989 }
1990
1992 const dt_dev_pixelpipe_iop_t *const previous_piece
1993 = piece ? dt_dev_pixelpipe_get_prev_enabled_piece(dev->preview_pipe, piece) : NULL;
1994 if(IS_NULL_PTR(piece) || IS_NULL_PTR(previous_piece) || previous_piece->dsc_out.datatype != TYPE_FLOAT || previous_piece->dsc_out.channels < 3)
1995 {
1996 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1997 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-preview-piece-missing");
1999 return FALSE;
2000 }
2001
2002 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2003 void *input = NULL;
2004 dt_pixel_cache_entry_t *input_entry = NULL;
2005 dt_dev_pixelpipe_cache_wait_set_owner(&g->preview_wait, "colorequal-preview-cursor", self);
2006 if(!dt_dev_pixelpipe_cache_peek_gui(dev->preview_pipe, previous_piece, &input, &input_entry,
2007 &g->preview_wait, _preview_cache_wait_restart, self)
2008 || IS_NULL_PTR(input) || IS_NULL_PTR(input_entry))
2009 {
2010 g->pending_preview_hash = previous_piece->global_hash;
2012 if(!g->cursor_sample_valid) _invalidate_preview_cursor(g);
2013 return FALSE;
2014 }
2015
2018
2019 const float point_preview[2] = { (float)g->cursor_pos_x, (float)g->cursor_pos_y };
2020 float point_image[2] = { point_preview[0], point_preview[1] };
2022
2023 const float scale_x = (previous_piece->buf_out.width > 0)
2024 ? (float)previous_piece->roi_out.width / (float)previous_piece->buf_out.width
2025 : 1.f;
2026 const float scale_y = (previous_piece->buf_out.height > 0)
2027 ? (float)previous_piece->roi_out.height / (float)previous_piece->buf_out.height
2028 : 1.f;
2029 const float sample_x
2030 = point_image[0] * (float)previous_piece->buf_out.width * scale_x - (float)previous_piece->roi_out.x;
2031 const float sample_y
2032 = point_image[1] * (float)previous_piece->buf_out.height * scale_y - (float)previous_piece->roi_out.y;
2033 const int xi = CLAMP((int)lroundf(sample_x), 0, previous_piece->roi_out.width - 1);
2034 const int yi = CLAMP((int)lroundf(sample_y), 0, previous_piece->roi_out.height - 1);
2035
2036 dt_aligned_pixel_t input_rgb = { 0.f };
2037 const float *const input_rgbf
2038 = (const float *)input + ((size_t)yi * (size_t)previous_piece->roi_out.width + (size_t)xi) * previous_piece->dsc_out.channels;
2039 input_rgb[0] = input_rgbf[0];
2040 input_rgb[1] = input_rgbf[1];
2041 input_rgb[2] = input_rgbf[2];
2042 input_rgb[3] = 0.f;
2043
2046
2048 const dt_iop_order_iccprofile_info_t *const lut_profile = g->viewer_lut.lut_profile;
2049 dt_aligned_pixel_t output_rgb = { input_rgb[0], input_rgb[1], input_rgb[2], 0.f };
2050 dt_colorrings_apply_rgb_lut(input_rgb, exp2f(g->gui_params.white_level), work_profile, lut_profile,
2051 g->viewer_lut.clut, g->viewer_lut.clut_level, NULL,
2052 (dt_lut3d_interpolation_t)g->gui_params.interpolation, output_rgb);
2053
2054 dt_aligned_pixel_t projected_rgb = { 0.f };
2055 const float white_level = fmaxf(exp2f(g->gui_params.white_level), 1e-6f);
2056 projected_rgb[0] = input_rgb[0] / white_level;
2057 projected_rgb[1] = input_rgb[1] / white_level;
2058 projected_rgb[2] = input_rgb[2] / white_level;
2059
2060 const float neutral = CLAMP((projected_rgb[0] + projected_rgb[1] + projected_rgb[2]) / 3.f, 0.f, 1.f);
2061 dt_aligned_pixel_t axis = { neutral, neutral, neutral, 0.f };
2062 dt_colorrings_project_to_cube_shell(axis, projected_rgb);
2063
2064 dt_aligned_pixel_t HSB = { 0.f };
2065 _pipe_rgb_to_dt_ucs_hsb(self, dev->preview_pipe, projected_rgb, HSB);
2066 if(!isfinite(HSB[0]))
2067 {
2069 return FALSE;
2070 }
2071
2072 g->cursor_hue = dt_colorrings_wrap_hue_pi(HSB[0]);
2073 _work_rgb_to_display_rgb(self, dev->preview_pipe, input_rgb, g->cursor_input_display);
2074 _work_rgb_to_display_rgb(self, dev->preview_pipe, output_rgb, g->cursor_output_display);
2075 g->cursor_sample_valid = TRUE;
2076 return TRUE;
2077}
2078
2085static void _format_picker_brightness_position(const float brightness, char *text, const size_t size)
2086{
2087 if(brightness <= 0.15f)
2088 {
2089 g_snprintf(text, size, "%d%% %s", 100, _("shadows"));
2090 }
2091 else if(brightness < 0.45f)
2092 {
2093 const float t = (brightness - 0.15f) / (0.45f - 0.15f);
2094 const int shadows = CLAMP((int)lroundf((1.f - t) * 100.f), 0, 100);
2095 g_snprintf(text, size, "%d%% %s, %d%% %s", shadows, _("shadows"), 100 - shadows, _("midtones"));
2096 }
2097 else if(brightness < 0.75f)
2098 {
2099 const float t = (brightness - 0.45f) / (0.75f - 0.45f);
2100 const int midtones = CLAMP((int)lroundf((1.f - t) * 100.f), 0, 100);
2101 g_snprintf(text, size, "%d%% %s, %d%% %s", midtones, _("midtones"), 100 - midtones, _("highlights"));
2102 }
2103 else
2104 {
2105 g_snprintf(text, size, "%d%% %s", 100, _("highlights"));
2106 }
2107}
2108
2111{
2114 const dt_iop_module_t *sampled_module = piece && piece->module ? piece->module : self;
2115
2116 if(picker == g->white_level)
2117 {
2118 dt_aligned_pixel_t max_Ych = { 0.f };
2119 _pipe_rgb_to_Ych(self, pipe, (const float *)sampled_module->picked_color_max, max_Ych);
2120
2122 p->white_level = log2f(max_Ych[0]);
2123 g->gui_params.white_level = p->white_level;
2124 dt_bauhaus_slider_set(g->white_level, p->white_level);
2126
2128 }
2129 else if(picker == g->module_picker)
2130 {
2131 if(sampled_module->picked_color_max[0] < sampled_module->picked_color_min[0])
2132 {
2133 g->picker_valid = FALSE;
2134 gtk_label_set_text(GTK_LABEL(g->picker_info), _("no sample"));
2135 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2136 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2137 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2138 return;
2139 }
2140
2141 dt_aligned_pixel_t HSB = { 0.f };
2142 dt_aligned_pixel_t RGB = { 0.f };
2143 dt_aligned_pixel_t axis = { 0.f };
2144 char text[128] = { 0 };
2145 const float white_level = fmaxf(exp2f(g->gui_params.white_level), 1e-6f);
2146
2147 RGB[0] = sampled_module->picked_color[0] / white_level;
2148 RGB[1] = sampled_module->picked_color[1] / white_level;
2149 RGB[2] = sampled_module->picked_color[2] / white_level;
2150
2158 const float neutral = CLAMP((RGB[0] + RGB[1] + RGB[2]) / 3.f, 0.f, 1.f);
2159 axis[0] = neutral;
2160 axis[1] = neutral;
2161 axis[2] = neutral;
2163 _pipe_rgb_to_dt_ucs_hsb(self, pipe, RGB, HSB);
2164
2165 g->picker_valid = TRUE;
2166 g->picker_hue = dt_colorrings_wrap_hue_pi(HSB[0]);
2167 g->picker_brightness = CLAMP(HSB[2], 0.f, 1.f);
2168 _format_picker_brightness_position(g->picker_brightness, text, sizeof(text));
2169 gtk_label_set_text(GTK_LABEL(g->picker_info), text);
2170
2171 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2172 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2173 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2174 }
2175}
2176
2177void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2178 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
2179{
2180 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
2181 if(IS_NULL_PTR(work_profile) || piece->dsc_in.channels != 4) return;
2182
2185 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2186 const float *const restrict in = (const float *)i;
2187 float max_Y = 0.0f;
2188
2189 __OMP_PARALLEL_FOR__(reduction(max:max_Y))
2190 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height * 4; k += 4)
2191 {
2192 dt_aligned_pixel_t Ych = { 0.f };
2193 dt_colorrings_profile_rgb_to_Ych(in + k, work_profile, Ych);
2194 if(isfinite(Ych[0]))
2195 max_Y = fmaxf(max_Y, Ych[0]);
2196 }
2197
2198 p->white_level = log2f(fmaxf(max_Y, 1e-6f));
2199 if(!IS_NULL_PTR(g))
2200 g->gui_params.white_level = p->white_level;
2201}
2202
2203void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2204{
2207 const gboolean curves_changed = !_curve_fields_equal(&g->gui_params, p);
2208 memcpy(&g->gui_params, self->params, sizeof(dt_iop_colorequal_params_t));
2209 if(curves_changed)
2210 {
2211 g->curve_cache_valid = FALSE;
2212 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2213 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2214 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2215
2216 if(g->cursor_valid && g->has_focus) dt_control_queue_redraw_center();
2217 }
2219}
2220
2222{
2225 memcpy(&g->gui_params, p, sizeof(dt_iop_colorequal_params_t));
2226 dt_bauhaus_slider_set(g->white_level, p->white_level);
2227 dt_bauhaus_slider_set(g->sigma_L, p->sigma_L);
2228 dt_bauhaus_slider_set(g->sigma_rho, p->sigma_rho);
2229 dt_bauhaus_slider_set(g->sigma_theta, p->sigma_theta);
2230 dt_bauhaus_slider_set(g->neutral_protection, p->neutral_protection);
2231 dt_bauhaus_combobox_set(g->interpolation, p->interpolation);
2232 gui_changed(self, NULL, NULL);
2233}
2234
2235void gui_focus(struct dt_iop_module_t *self, gboolean in)
2236{
2238 g->has_focus = in;
2239
2240 if(in)
2241 {
2242 if(!g->preview_signal_connected)
2243 {
2245 G_CALLBACK(_cacheline_ready_callback), self);
2246 g->preview_signal_connected = TRUE;
2247 }
2248
2249 if(g->cursor_valid && self->dev && self->dev->preview_pipe && !self->dev->preview_pipe->processing)
2251 }
2252 else if(g->preview_signal_connected)
2253 {
2255 g->preview_signal_connected = FALSE;
2256 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2257 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-focus-leave");
2258 }
2259
2262}
2263
2265{
2269
2270 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2271 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2272 {
2273 if(g->curve[ring][ch]) dt_draw_curve_destroy(g->curve[ring][ch]);
2274 g->curve[ring][ch] = NULL;
2275 if(g->background_surface[ring][ch]) cairo_surface_destroy(g->background_surface[ring][ch]);
2276 g->background_surface[ring][ch] = NULL;
2277 }
2278
2279 dt_free_align(g->viewer_lut.clut);
2280 g->viewer_lut.clut = NULL;
2281
2282 dt_lut_viewer_destroy(&g->viewer);
2283 if(g->preview_signal_connected)
2284 {
2286 g->preview_signal_connected = FALSE;
2287 }
2288 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-gui-cleanup");
2289
2290 const int current_primary = CLAMP(gtk_notebook_get_current_page(g->ring_notebook), 0, DT_IOP_COLOREQUAL_NUM_RINGS);
2291 dt_conf_set_int("plugins/darkroom/colorequal/gui_ring_page", current_primary);
2292 dt_conf_set_int("plugins/darkroom/colorequal/gui_channel_page",
2293 gtk_notebook_get_current_page(g->channel_notebook[0]));
2295}
2296
2298{
2300 memcpy(&g->gui_params, self->params, sizeof(dt_iop_colorequal_params_t));
2301 g->curve_cache_valid = FALSE;
2302 g->viewer_lut_dirty = TRUE;
2303 g->viewer_lut_valid = FALSE;
2304 g->preview_signal_connected = FALSE;
2305 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2306 g->has_focus = FALSE;
2307
2308 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2309
2310 GtkWidget *ring_tabs_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2311 gtk_box_pack_start(GTK_BOX(self->widget), ring_tabs_box, TRUE, TRUE, 0);
2312
2313 g->ring_notebook = GTK_NOTEBOOK(gtk_notebook_new());
2314 gtk_widget_set_name(GTK_WIDGET(g->ring_notebook), "colorequal-ring-tabs");
2315 gtk_notebook_set_show_border(g->ring_notebook, FALSE);
2316 g_signal_connect(G_OBJECT(g->ring_notebook), "switch_page", G_CALLBACK(_ring_tabs_switch_callback), self);
2317 gtk_box_pack_start(GTK_BOX(ring_tabs_box), GTK_WIDGET(g->ring_notebook), TRUE, TRUE, 0);
2318
2321 const char *channel_labels[DT_IOP_COLOREQUAL_NUM_CHANNELS] = { _("saturation"), _("brightness"), _("hue") };
2322
2323 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2324 {
2325 GtkWidget *ring_label = gtk_label_new(_ring_label((dt_iop_colorequal_ring_t)ring));
2326 dt_gui_add_class(ring_label, "dt_modulegroups_tab_label");
2327 GtkWidget *ring_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2328 gtk_notebook_append_page(g->ring_notebook, ring_page, ring_label);
2329 gtk_container_child_set(GTK_CONTAINER(g->ring_notebook), ring_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2330
2331 g->channel_notebook[ring] = GTK_NOTEBOOK(gtk_notebook_new());
2332 g_object_set_data(G_OBJECT(g->channel_notebook[ring]), "colorequal-ring", GINT_TO_POINTER(ring));
2333 g_signal_connect(G_OBJECT(g->channel_notebook[ring]), "switch_page", G_CALLBACK(_channel_tabs_switch_callback),
2334 self);
2335 gtk_box_pack_start(GTK_BOX(ring_page), GTK_WIDGET(g->channel_notebook[ring]), TRUE, TRUE, 0);
2336
2337 for(int order = 0; order < DT_IOP_COLOREQUAL_NUM_CHANNELS; order++)
2338 {
2339 const dt_iop_colorequal_channel_t ch = channel_order[order];
2340 GtkWidget *channel_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2341 g_object_set_data(G_OBJECT(channel_page), "colorequal-channel", GINT_TO_POINTER(ch));
2342 g->area[ring][ch] = GTK_DRAWING_AREA(gtk_drawing_area_new());
2343 gtk_widget_set_hexpand(GTK_WIDGET(g->area[ring][ch]), TRUE);
2344 gtk_widget_add_events(GTK_WIDGET(g->area[ring][ch]),
2345 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
2346 g_object_set_data(G_OBJECT(g->area[ring][ch]), "colorequal-ring", GINT_TO_POINTER(ring));
2347 g_object_set_data(G_OBJECT(g->area[ring][ch]), "colorequal-channel", GINT_TO_POINTER(ch));
2348 g_signal_connect(G_OBJECT(g->area[ring][ch]), "draw", G_CALLBACK(_draw_curve), self);
2349 g_signal_connect(G_OBJECT(g->area[ring][ch]), "motion-notify-event",
2350 G_CALLBACK(_area_motion_notify_callback), self);
2351 g_signal_connect(G_OBJECT(g->area[ring][ch]), "button-press-event", G_CALLBACK(_area_button_press_callback),
2352 self);
2353 g_signal_connect(G_OBJECT(g->area[ring][ch]), "button-release-event",
2354 G_CALLBACK(_area_button_release_callback), self);
2355 // All ring/channel graphs are alternate views of the same plot, so they share one height key.
2356 gtk_box_pack_start(GTK_BOX(channel_page),
2357 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area[ring][ch]),
2358 "plugins/darkroom/colorequal/graphheight", 220, 100),
2359 FALSE, FALSE, 0);
2360 gtk_notebook_append_page(g->channel_notebook[ring], channel_page, gtk_label_new(channel_labels[order]));
2361 gtk_container_child_set(GTK_CONTAINER(g->channel_notebook[ring]), channel_page, "tab-expand", TRUE,
2362 "tab-fill", TRUE, NULL);
2363 }
2364 }
2365
2366 GtkWidget *options_label = gtk_label_new(_("options"));
2367 dt_gui_add_class(options_label, "dt_modulegroups_tab_label");
2368 GtkWidget *options_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2369 gtk_notebook_append_page(g->ring_notebook, options_page, options_label);
2370 gtk_container_child_set(GTK_CONTAINER(g->ring_notebook), options_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2371
2372 GtkWidget *const module_root = self->widget;
2373 self->widget = options_page;
2374
2375 g->white_level
2377 dt_bauhaus_slider_set_soft_range(g->white_level, -2.f, 2.f);
2378 dt_bauhaus_slider_set_format(g->white_level, _(" EV"));
2379
2380 g->sigma_L = dt_bauhaus_slider_from_params(self, "sigma_L");
2381 dt_bauhaus_slider_set_soft_range(g->sigma_L, 1.f, 100.f);
2382 dt_bauhaus_slider_set_format(g->sigma_L, _(" %"));
2383
2384 g->sigma_rho = dt_bauhaus_slider_from_params(self, "sigma_rho");
2385 dt_bauhaus_slider_set_soft_range(g->sigma_rho, 0.1f, 1.5f);
2386
2387 g->sigma_theta = dt_bauhaus_slider_from_params(self, "sigma_theta");
2388 dt_bauhaus_slider_set_soft_range(g->sigma_theta, 0.05f, 0.8f);
2389
2390 g->neutral_protection = dt_bauhaus_slider_from_params(self, "neutral_protection");
2391 dt_bauhaus_slider_set_soft_range(g->neutral_protection, 0.f, 1.f);
2392
2393 g->interpolation = dt_bauhaus_combobox_from_params(self, "interpolation");
2394 gtk_widget_set_tooltip_text(g->interpolation, _("select the interpolation method"));
2395
2396 self->widget = module_root;
2397
2398 GtkWidget *picker_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2399 gtk_box_pack_start(GTK_BOX(ring_tabs_box), picker_box, FALSE, FALSE, 0);
2400
2401 g->module_picker = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_AREA, NULL, IOP_CS_RGB);
2402 gtk_box_pack_start(GTK_BOX(picker_box), g->module_picker, FALSE, FALSE, 0);
2403
2404 g->picker_info = gtk_label_new(_("no sample"));
2405 gtk_widget_set_hexpand(g->picker_info, TRUE);
2406 gtk_widget_set_halign(g->picker_info, GTK_ALIGN_START);
2407 gtk_label_set_xalign(GTK_LABEL(g->picker_info), 0.f);
2408 gtk_box_pack_start(GTK_BOX(picker_box), g->picker_info, TRUE, TRUE, 0);
2409
2410 const int active_ring = dt_conf_get_int("plugins/darkroom/colorequal/gui_ring_page");
2411 const int active_channel = dt_conf_get_int("plugins/darkroom/colorequal/gui_channel_page");
2412 const int current_ring_page = CLAMP(active_ring, 0, DT_IOP_COLOREQUAL_NUM_RINGS);
2413 const int current_channel_page = CLAMP(active_channel, 0, DT_IOP_COLOREQUAL_NUM_CHANNELS - 1);
2414
2416 gtk_notebook_set_current_page(g->ring_notebook, current_ring_page);
2417 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2418 gtk_notebook_set_current_page(g->channel_notebook[ring], current_channel_page);
2420
2421 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2422 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++) g->selected[ring][ch] = -1;
2423
2424 g->viewer_lut.clut = NULL;
2425 g->viewer_lut.clut_level = 0;
2426 memset(g->viewer_lut.reference_saturation, 0, sizeof(g->viewer_lut.reference_saturation));
2427 g->viewer_lut.lut_profile = NULL;
2428 g->viewer_lut.work_profile = NULL;
2429 g->viewer_control_node_count = 0;
2430 g->viewer = dt_lut_viewer_new(DT_GUI_MODULE(self));
2431 if(g->viewer)
2432 gtk_box_pack_start(GTK_BOX(GTK_BOX(self->widget)), dt_lut_viewer_get_widget(g->viewer), TRUE, TRUE, 0);
2433
2434 gtk_widget_show_all(self->widget);
2435}
2436
2438{
2439 dt_iop_colorequal_global_data_t *gd = malloc(sizeof(*gd));
2440 module->data = gd;
2441 gd->kernel_lut3d_tetrahedral = -1;
2442 gd->kernel_lut3d_trilinear = -1;
2443 gd->kernel_lut3d_pyramid = -1;
2444 gd->kernel_exposure = -1;
2445
2446#ifdef HAVE_OPENCL
2447 const int lut_program = 28; // lut3d.cl, from programs.conf
2448 const int basic_program = 2; // basic.cl, from programs.conf
2449 gd->kernel_lut3d_tetrahedral = dt_opencl_create_kernel(lut_program, "lut3d_tetrahedral");
2450 gd->kernel_lut3d_trilinear = dt_opencl_create_kernel(lut_program, "lut3d_trilinear");
2451 gd->kernel_lut3d_pyramid = dt_opencl_create_kernel(lut_program, "lut3d_pyramid");
2452 gd->kernel_exposure = dt_opencl_create_kernel(basic_program, "exposure");
2453#endif
2454}
2455
2467
2469{
2470 dt_iop_default_init(module);
2472 memcpy(module->params, module->default_params, module->params_size);
2473}
2474
2475// clang-format off
2476// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2477// vim: shiftwidth=2 expandtab tabstop=2 cindent
2478// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2479// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:164
static const char neutral[]
Definition basecurve.c:248
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1650
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3537
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2304
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3613
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:446
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:347
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ 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:68
#define DT_IOP_COLOREQUAL_SCROLL_SIGMA
Definition colorequal.c:77
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:756
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:314
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:457
const char ** description(struct dt_iop_module_t *self)
Definition colorequal.c:220
static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
int default_group()
Definition colorequal.c:227
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:81
static const char * _ring_label(const dt_iop_colorequal_ring_t ring)
Definition colorequal.c:273
#define DT_IOP_COLOREQUAL_GRAPH_INSET
Definition colorequal.c:75
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:76
static void _switch_preview_cursor(dt_iop_module_t *self)
#define DT_IOP_COLOREQUAL_CLUT_LEVEL
Definition colorequal.c:71
static float _curve_periodic_distance(const float x0, const float x1)
Definition colorequal.c:347
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:464
#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_FINE
Definition colorequal.c:82
const char * aliases()
Definition colorequal.c:215
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:391
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:375
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:287
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:546
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:307
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:795
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:63
const char * name()
Definition colorequal.c:210
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:95
@ DT_IOP_COLOREQUAL_RING_DARK
Definition colorequal.c:96
@ DT_IOP_COLOREQUAL_RING_LIGHT
Definition colorequal.c:98
@ DT_IOP_COLOREQUAL_RING_MID
Definition colorequal.c:97
#define DT_IOP_COLOREQUAL_SCROLL_STEP_COARSE
Definition colorequal.c:80
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:62
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:426
#define DT_IOP_COLOREQUAL_MAXNODES
Definition colorequal.c:64
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:294
static dt_iop_colorequal_channel_t _channel_from_page(const int page)
Definition colorequal.c:353
void gui_cleanup(dt_iop_module_t *self)
#define DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS
Definition colorequal.c:72
static void _update_gui_lut_cache(dt_iop_module_t *self)
Definition colorequal.c:948
void cleanup_global(dt_iop_module_so_t *module)
static void _invalidate_preview_cursor(dt_iop_colorequal_gui_data_t *g)
Definition colorequal.c:383
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition colorequal.c:237
static void _format_picker_brightness_position(const float brightness, char *text, const size_t size)
int flags()
Definition colorequal.c:232
#define DT_IOP_COLOREQUAL_VIEWER_CONTROL_NODES
Definition colorequal.c:69
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:340
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:301
#define DT_IOP_COLOREQUAL_GRAPH_RES
Definition colorequal.c:66
static gboolean _curve_fields_equal(const dt_iop_colorequal_params_t *const a, const dt_iop_colorequal_params_t *const b)
Definition colorequal.c:333
dt_iop_colorequal_channel_t
Definition colorequal.c:88
@ DT_IOP_COLOREQUAL_BRIGHTNESS
Definition colorequal.c:91
@ DT_IOP_COLOREQUAL_SATURATION
Definition colorequal.c:89
@ DT_IOP_COLOREQUAL_HUE
Definition colorequal.c:90
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition colorequal.c:809
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:434
static void _init_default_curves(dt_iop_colorequal_params_t *p)
Definition colorequal.c:326
#define DT_IOP_COLOREQUAL_GRAPH_GRADIENTS
Definition colorequal.c:67
#define DT_IOP_COLOREQUAL_MIN_X_DISTANCE
Definition colorequal.c:73
#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_COARSE
Definition colorequal.c:83
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:896
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:686
#define DT_IOP_COLOREQUAL_DEFAULT_NODES
Definition colorequal.c:65
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:819
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:79
static float _channel_value_from_y(const dt_iop_colorequal_channel_t channel, const float y)
Definition colorequal.c:247
#define DT_IOP_COLOREQUAL_SCROLL_STEP
Definition colorequal.c:78
#define DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS
Definition colorequal.c:74
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:260
dt_iop_colorequal_interpolation_t
Definition colorequal.c:102
@ DT_IOP_COLOREQUAL_PYRAMID
Definition colorequal.c:105
@ DT_IOP_COLOREQUAL_TRILINEAR
Definition colorequal.c:104
@ DT_IOP_COLOREQUAL_TETRAHEDRAL
Definition colorequal.c:103
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:484
static dt_iop_colorequal_ring_t _active_ring_from_gui(const dt_iop_colorequal_gui_data_t *g)
Definition colorequal.c:367
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_INTENT_PERCEPTUAL
Definition colorspaces.h:64
@ DT_COLORSPACE_HLG_REC2020
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static dt_aligned_pixel_t RGB
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
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.
int type
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:934
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:398
#define dt_control_set_cursor_visible(visible)
Definition control.h:148
#define MONOTONE_HERMITE
Definition curve_tools.h:35
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define dt_free_align(ptr)
Definition darktable.h:503
static void * dt_calloc_align(size_t size)
Definition darktable.h:510
@ DT_DEBUG_OPENCL
Definition darktable.h:744
@ DT_DEBUG_PERF
Definition darktable.h:741
#define dt_gui_freeze_begin()
Definition darktable.h:900
#define dt_gui_freeze_end()
Definition darktable.h:901
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:516
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
#define M_PI_F
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:42
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(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)
void dt_dev_coordinates_preview_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1212
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1783
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1197
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:1895
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1056
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:143
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:282
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:336
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:288
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:364
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:266
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
const dt_collection_filter_flag_t colors[6]
Definition filter.c:295
@ TYPE_FLOAT
Definition format.h:46
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.
Definition gtk.c:3068
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:322
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
#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:87
void dt_iop_throttled_history_update(gpointer data)
Definition imageop.c:3214
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:321
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3220
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:226
#define IOP_GUI_FREE
Definition imageop.h:641
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:196
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ IOP_GROUP_COLOR
Definition imageop.h:169
#define IOP_GUI_ALLOC(module)
Definition imageop.h:638
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
static float kernel(const float *x, const float *y)
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_add_profile_info_to_list(struct dt_develop_t *dev, const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
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_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)
static const float x
const int t
static float mix(const float a, const float b, const float t)
Definition liquify.c:705
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
dt_lut3d_interpolation_t
Definition lut3d.h:25
@ DT_LUT3D_INTERP_PYRAMID
Definition lut3d.h:28
@ DT_LUT3D_INTERP_TETRAHEDRAL
Definition lut3d.h:26
@ DT_LUT3D_INTERP_TRILINEAR
Definition lut3d.h:27
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)
size_t size
Definition mipmap_cache.c:3
float dt_aligned_pixel_t[4]
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2164
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2058
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2360
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2101
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2155
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
void dt_dev_pixelpipe_cache_ref_count_entry(dt_dev_pixelpipe_cache_t *cache, 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(dt_dev_pixelpipe_cache_t *cache, 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
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
@ 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:185
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float sigma
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:818
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
int32_t unmuted
Definition darktable.h:788
struct dt_develop_t * develop
Definition darktable.h:798
struct dt_control_t * control
Definition darktable.h:801
GdkRGBA graph_bg
Definition bauhaus.h:281
GdkRGBA graph_border
Definition bauhaus.h:281
GdkRGBA graph_fg
Definition bauhaus.h:281
dt_iop_buffer_dsc_t dsc_out
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t preview_height
Definition develop.h:213
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
struct dt_develop_t::@17 roi
int32_t preview_width
Definition develop.h:213
dt_ui_t * ui
Definition gtk.h:165
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
dt_lut3d_interpolation_t interpolation
Definition colorequal.c:135
dt_iop_colorequal_params_t built_params
Definition colorequal.c:139
dt_iop_order_iccprofile_info_t * work_profile
Definition colorequal.c:134
dt_iop_order_iccprofile_info_t * lut_profile
Definition colorequal.c:133
dt_iop_colorequal_data_t viewer_lut
Definition colorequal.c:172
dt_lut_viewer_t * viewer
Definition colorequal.c:171
dt_lut_viewer_control_node_t viewer_control_nodes[(3 *64)]
Definition colorequal.c:198
dt_iop_colorequal_params_t gui_params
Definition colorequal.c:174
dt_aligned_pixel_t cursor_input_display
Definition colorequal.c:196
dt_aligned_pixel_t cursor_output_display
Definition colorequal.c:197
GtkNotebook * channel_notebook[3]
Definition colorequal.c:162
dt_dev_pixelpipe_cache_wait_t preview_wait
Definition colorequal.c:186
cairo_surface_t * background_surface[3][3]
Definition colorequal.c:207
GtkDrawingArea * area[3][3]
Definition colorequal.c:160
dt_draw_curve_t * curve[3][3]
Definition colorequal.c:176
const dt_iop_order_iccprofile_info_t * cached_display_profile[3][3]
Definition colorequal.c:206
dt_iop_colorequal_params_t viewer_lut_params
Definition colorequal.c:173
float cached_reference_saturation[3][3]
Definition colorequal.c:202
dt_iop_colorequal_params_t cached_curve_params
Definition colorequal.c:175
dt_iop_colorequal_node_t curve[3][3][20]
Definition colorequal.c:123
dt_iop_colorequal_interpolation_t interpolation
Definition colorequal.c:121
dt_iop_global_data_t * data
Definition imageop.h:263
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:294
GtkDarktableToggleButton * off
Definition imageop.h:376
dt_iop_params_t * default_params
Definition imageop.h:344
GtkWidget * widget
Definition imageop.h:374
struct dt_develop_t * dev
Definition imageop.h:333
dt_iop_gui_data_t * gui_data
Definition imageop.h:348
dt_iop_global_data_t * global_data
Definition imageop.h:351
gboolean enabled
Definition imageop.h:335
dt_aligned_pixel_t picked_color_min
Definition imageop.h:309
dt_aligned_pixel_t picked_color_max
Definition imageop.h:309
int32_t params_size
Definition imageop.h:346
dt_aligned_pixel_t picked_color
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:344
Region of interest passed through the pixelpipe.
Definition imageop.h:72
GtkWidget * notebook