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
137
150
152{
154 GtkNotebook *ring_notebook;
180 gboolean has_focus;
181 gboolean picker_valid;
182 gboolean cursor_valid;
202
203const char *name()
204{
205 return _("color equalizer");
206}
207
208const char *aliases()
209{
210 return _("color zones");
211}
212
213const char **description(struct dt_iop_module_t *self)
214{
216 self, _("stretch RGB colors around the achromatic axis from editable dt UCS hue nodes"), _("creative"),
217 _("linear, RGB, display-referred"), _("linear, RGB"), _("linear, RGB, display-referred"));
218}
219
221{
222 return IOP_GROUP_COLOR;
223}
224
229
231{
232 return IOP_CS_RGB;
233}
234
235static void _update_gui_lut_cache(dt_iop_module_t *self);
236static void _switch_preview_cursor(dt_iop_module_t *self);
238static void _preview_cache_wait_restart(gpointer user_data);
239
240static inline float _channel_value_from_y(const dt_iop_colorequal_channel_t channel, const float y)
241{
242 switch(channel)
243 {
245 return (y - 0.5f) * 2.f * M_PI_F;
248 default:
249 return CLAMP(y * 2.f, 0.f, 2.f);
250 }
251}
252
253static inline float _channel_y_from_value(const dt_iop_colorequal_channel_t channel, const float value)
254{
255 switch(channel)
256 {
258 return CLAMP(value / (2.f * M_PI_F) + 0.5f, 0.f, 1.f);
261 default:
262 return CLAMP(value * 0.5f, 0.f, 1.f);
263 }
264}
265
266static inline const char *_ring_label(const dt_iop_colorequal_ring_t ring)
267{
268 switch(ring)
269 {
271 return _("shadows");
273 return _("highlights");
275 default:
276 return _("midtones");
277 }
278}
279
281 const dt_iop_colorequal_ring_t ring,
282 const dt_iop_colorequal_channel_t channel)
283{
284 return p->curve[ring][channel];
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_channel_t channel)
296{
297 return &p->curve_num_nodes[ring][channel];
298}
299
301 const dt_iop_colorequal_ring_t ring,
302 const dt_iop_colorequal_channel_t channel)
303{
304 return p->curve_num_nodes[ring][channel];
305}
306
308 const dt_iop_colorequal_channel_t channel)
309{
311 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
312 for(int k = 0; k < DT_IOP_COLOREQUAL_DEFAULT_NODES; k++)
313 {
314 curve[k].x = (float)k / (float)DT_IOP_COLOREQUAL_DEFAULT_NODES;
315 curve[k].y = 0.5f;
316 }
317}
318
325
326static inline gboolean _curve_fields_equal(const dt_iop_colorequal_params_t *const a,
327 const dt_iop_colorequal_params_t *const b)
328{
329 return !memcmp(a->curve_num_nodes, b->curve_num_nodes, sizeof(a->curve_num_nodes))
330 && !memcmp(a->curve, b->curve, sizeof(a->curve));
331}
332
333static inline gboolean _lut_fields_equal(const dt_iop_colorequal_params_t *const a,
334 const dt_iop_colorequal_params_t *const b)
335{
336 return _curve_fields_equal(a, b) && a->sigma_L == b->sigma_L && a->sigma_rho == b->sigma_rho
337 && a->sigma_theta == b->sigma_theta && a->neutral_protection == b->neutral_protection;
338}
339
340static inline float _curve_periodic_distance(const float x0, const float x1)
341{
342 const float distance = fabsf(x0 - x1);
343 return fminf(distance, 1.f - distance);
344}
345
347{
348 switch(CLAMP(page, 0, DT_IOP_COLOREQUAL_NUM_CHANNELS - 1))
349 {
350 case 1:
352 case 2:
354 case 0:
355 default:
357 }
358}
359
361{
362 const int page = (g && g->ring_notebook) ? gtk_notebook_get_current_page(g->ring_notebook) : -1;
363 if(page >= 0 && page < DT_IOP_COLOREQUAL_NUM_RINGS) return (dt_iop_colorequal_ring_t)page;
364 return (dt_iop_colorequal_ring_t)CLAMP(dt_conf_get_int("plugins/darkroom/colorequal/gui_ring_page"), 0,
366}
367
369 const dt_iop_colorequal_ring_t ring)
370{
371 const int page = (g && g->channel_notebook[ring]) ? gtk_notebook_get_current_page(g->channel_notebook[ring]) : -1;
372 if(page >= 0 && page < DT_IOP_COLOREQUAL_NUM_CHANNELS) return _channel_from_page(page);
373 return _channel_from_page(dt_conf_get_int("plugins/darkroom/colorequal/gui_channel_page"));
374}
375
377{
378 g->cursor_sample_valid = FALSE;
379 g->cursor_hue = 0.f;
380 memset(g->cursor_input_display, 0, sizeof(g->cursor_input_display));
381 memset(g->cursor_output_display, 0, sizeof(g->cursor_output_display));
382}
383
385 const dt_iop_colorequal_channel_t channel, const float hue,
386 float *curve_x, float *curve_y, float *offset_normalized)
387{
388 if(IS_NULL_PTR(p) || !isfinite(hue)) return FALSE;
389
390 const int nodes = _curve_nodes_count_const(p, ring, channel);
391 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
392 if(nodes < 1 || IS_NULL_PTR(curve)) return FALSE;
393
394 const float x = dt_colorrings_hue_to_curve_x(hue);
395 const float y = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, nodes, x);
396 const float value = _channel_value_from_y(channel, y);
397
398 if(!IS_NULL_PTR(curve_x)) *curve_x = x;
399 if(!IS_NULL_PTR(curve_y)) *curve_y = y;
400
401 if(!IS_NULL_PTR(offset_normalized))
402 {
403 switch(channel)
404 {
406 *offset_normalized = CLAMP(value / M_PI_F, -1.f, 1.f);
407 break;
410 default:
411 *offset_normalized = CLAMP(value - 1.f, -1.f, 1.f);
412 break;
413 }
414 }
415
416 return TRUE;
417}
418
420{
421 RGB[0] = CLAMP(RGB[0], 0.f, 1.f);
422 RGB[1] = CLAMP(RGB[1], 0.f, 1.f);
423 RGB[2] = CLAMP(RGB[2], 0.f, 1.f);
424 RGB[3] = 0.f;
425}
426
428 dt_aligned_pixel_t display_rgb)
429{
430 if(IS_NULL_PTR(display_rgb)) return;
431
432 memcpy(display_rgb, work_rgb, sizeof(dt_aligned_pixel_t));
433 _clamp_display_rgb(display_rgb);
434
435 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe)) return;
436
437 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
439 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(display_profile)) return;
440
441 float in[4] = { work_rgb[0], work_rgb[1], work_rgb[2], 0.f };
442 float out[4] = { work_rgb[0], work_rgb[1], work_rgb[2], 0.f };
443 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, work_profile, display_profile, "colorequal swatch");
444 display_rgb[0] = out[0];
445 display_rgb[1] = out[1];
446 display_rgb[2] = out[2];
447 _clamp_display_rgb(display_rgb);
448}
449
450static inline void _mix_rgb_anchors(const dt_aligned_pixel_t low, const dt_aligned_pixel_t high, const float mix,
452{
453 for(int c = 0; c < 3; c++) RGB[c] = low[c] * (1.f - mix) + high[c] * mix;
454}
455
456static inline void
458 const int ring, const float hue_position, dt_aligned_pixel_t RGB)
459{
460 const float hue = dt_colorrings_wrap_hue_2pi(hue_position * 2.f * M_PI_F) / (2.f * M_PI_F)
462 const int hue0 = ((int)floorf(hue)) % DT_IOP_COLOREQUAL_HUE_SAMPLES;
463 const int hue1 = (hue0 + 1) % DT_IOP_COLOREQUAL_HUE_SAMPLES;
464 const float mix = hue - floorf(hue);
465
466 for(int c = 0; c < 3; c++)
467 RGB[c] = ring_surface[ring][hue0][c] * (1.f - mix) + ring_surface[ring][hue1][c] * mix;
468}
469
476static inline void
478 const float brightness, const float hue_position, const float white,
480{
481 const float anchor_positions[DT_IOP_COLOREQUAL_NUM_RINGS + 2]
482 = { 0.f,
486 1.f };
487 int segment = 0;
488
489 while(segment < DT_IOP_COLOREQUAL_NUM_RINGS + 1 && brightness > anchor_positions[segment + 1]) segment++;
490
491 const float low_position = anchor_positions[segment];
492 const float high_position = anchor_positions[segment + 1];
493 const float mix
494 = (high_position > low_position) ? (brightness - low_position) / (high_position - low_position) : 0.f;
495 dt_aligned_pixel_t low = { 0.f };
496 dt_aligned_pixel_t high = { 0.f };
497
498 if(segment == 0)
499 {
500 dt_colorrings_brightness_to_axis_rgb(0.f, white, profile, low);
501 _sample_ring_hue(ring_surface, 0, hue_position, high);
502 }
503 else if(segment == DT_IOP_COLOREQUAL_NUM_RINGS)
504 {
505 _sample_ring_hue(ring_surface, DT_IOP_COLOREQUAL_NUM_RINGS - 1, hue_position, low);
506 dt_colorrings_brightness_to_axis_rgb(1.f, white, profile, high);
507 }
508 else
509 {
510 _sample_ring_hue(ring_surface, segment - 1, hue_position, low);
511 _sample_ring_hue(ring_surface, segment, hue_position, high);
512 }
513
514 _mix_rgb_anchors(low, high, CLAMP(mix, 0.f, 1.f), RGB);
515}
516
540 const dt_iop_order_iccprofile_info_t *lut_profile)
541{
542 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
543 const double start = log_perf ? dt_get_wtime() : 0.0;
544 const float white = dt_colorrings_graph_white();
545 const size_t clut_size
547
548 if(IS_NULL_PTR(d->clut)) d->clut = dt_alloc_align_float(clut_size);
549 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
550
551 dt_colorrings_compute_reference_saturations(white, d->reference_saturation);
558
559 for(int ring = 0; ring < DT_IOP_COLOREQUAL_LOCAL_FIELD_RINGS; ring++)
560 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
561 chroma_scale[ring][hue_sample] = 1.f;
562
568 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
569 {
570 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
571 const float brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)current_ring);
572 const float reference_saturation = d->reference_saturation[ring];
573 dt_aligned_pixel_t neutral_rgb = { 0.f };
574 dt_colorrings_brightness_to_axis_rgb(brightness, white, lut_profile, neutral_rgb);
575
576 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
577 {
578 const float x = (float)hue_sample / (float)DT_IOP_COLOREQUAL_HUE_SAMPLES;
579 const float hue = dt_colorrings_curve_x_to_hue(x);
580 const float hue_shift = _channel_value_from_y(
585 const float sat_gain = _channel_value_from_y(
590 const float bright_gain = _channel_value_from_y(
595
596 const dt_aligned_pixel_t before_hsb = { hue, reference_saturation, brightness, 0.f };
597 const dt_aligned_pixel_t after_hsb
598 = { dt_colorrings_wrap_hue_pi(hue + hue_shift), CLAMP(reference_saturation * sat_gain, 0.f, 1.f),
599 CLAMP(brightness * bright_gain, 0.f, 1.f), 0.f };
600
601 dt_aligned_pixel_t before_rgb = { 0.f };
602 dt_aligned_pixel_t after_rgb = { 0.f };
603 dt_colorrings_hsb_to_profile_rgb(before_hsb, white, lut_profile, before_rgb);
604 dt_colorrings_hsb_to_profile_rgb(after_hsb, white, lut_profile, after_rgb);
605 dt_colorrings_project_to_cube_shell(neutral_rgb, before_rgb);
606 dt_colorrings_project_to_cube_shell(neutral_rgb, after_rgb);
607
608 float Lp, rhop, thetap;
609 float La, rhoa;
610 float unused_theta;
611 dt_colorrings_rgb_to_gray_cyl(before_rgb, &Lp, &rhop, &thetap);
612 dt_colorrings_rgb_to_gray_cyl(after_rgb, &La, &rhoa, &unused_theta);
613
627 const float requested_scale = sat_gain;
628 const float projected_scale = (rhop > 1e-6f) ? (rhoa / rhop) : 1.f;
629 const float effective_scale
630 = (requested_scale <= 1.f) ? requested_scale : fminf(requested_scale, projected_scale);
631
632 anchor_L[ring][hue_sample] = Lp;
633 anchor_rho[ring][hue_sample] = rhop;
634 anchor_theta[ring][hue_sample] = thetap;
635 delta_L[ring][hue_sample] = La - Lp;
636 chroma_scale[ring][hue_sample] = effective_scale;
637 delta_theta[ring][hue_sample] = dt_colorrings_wrap_pi(hue_shift);
638 }
639 }
640
641 // Fill the achromatic locus after the hue rings
642 for(int sample = 0; sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; sample++)
643 {
644 const float value = (float)sample / (float)(DT_IOP_COLOREQUAL_HUE_SAMPLES - 1);
645 anchor_L[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = value * 1.7320508075688772f;
646 anchor_rho[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
647 anchor_theta[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
648 delta_L[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
649 chroma_scale[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 1.f;
650 delta_theta[DT_IOP_COLOREQUAL_NUM_RINGS][sample] = 0.f;
651 }
652
664 const float sigma_L = fmaxf(p->sigma_L * 0.01f, 1e-6f);
665 const float sigma_rho = fmaxf(p->sigma_rho, 1e-6f);
666 const float sigma_theta = fmaxf(p->sigma_theta, 1e-6f);
667 const float neutral_protection = fmaxf(p->neutral_protection, 0.f);
668 dt_colorrings_fill_lut_local_field(d->clut, DT_IOP_COLOREQUAL_CLUT_LEVEL, anchor_L, anchor_rho, anchor_theta,
669 delta_L, chroma_scale, delta_theta, 1.f / sigma_L, 1.f / sigma_rho,
670 1.f / sigma_theta, neutral_protection * sigma_rho);
671
672 d->clut_level = DT_IOP_COLOREQUAL_CLUT_LEVEL;
673
674 if(log_perf)
675 dt_print(DT_DEBUG_PERF, "[colorequal] build_clut level=%u total=%.3fms\n", d->clut_level,
676 1000.0 * (dt_get_wtime() - start));
677}
678
680 const dt_iop_order_iccprofile_info_t *lut_profile,
681 dt_lut_viewer_control_node_t *control_nodes)
682{
683 if(IS_NULL_PTR(p) || IS_NULL_PTR(lut_profile) || IS_NULL_PTR(control_nodes)) return 0;
684
685 const float white = dt_colorrings_graph_white();
686 float reference_saturation[DT_IOP_COLOREQUAL_NUM_RINGS] = { 0.f };
687 size_t count = 0;
688
689 dt_colorrings_compute_reference_saturations(white, reference_saturation);
690
697 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
698 {
699 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
700 const float brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)current_ring);
701 const float saturation = reference_saturation[ring];
702 dt_aligned_pixel_t neutral_rgb = { 0.f };
703
704 dt_colorrings_brightness_to_axis_rgb(brightness, white, lut_profile, neutral_rgb);
705
706 for(int hue_sample = 0; hue_sample < DT_IOP_COLOREQUAL_HUE_SAMPLES; hue_sample++)
707 {
708 const float x = (float)hue_sample / (float)DT_IOP_COLOREQUAL_HUE_SAMPLES;
709 const float hue = dt_colorrings_curve_x_to_hue(x);
710 const float hue_shift = _channel_value_from_y(
715 const float sat_gain = _channel_value_from_y(
720 const float bright_gain = _channel_value_from_y(
725 const dt_aligned_pixel_t before_hsb = { hue, saturation, brightness, 0.f };
726 const dt_aligned_pixel_t after_hsb
727 = { dt_colorrings_wrap_hue_pi(hue + hue_shift), CLAMP(saturation * sat_gain, 0.f, 1.f),
728 CLAMP(brightness * bright_gain, 0.f, 1.f), 0.f };
729 dt_aligned_pixel_t before_rgb = { 0.f };
730 dt_aligned_pixel_t after_rgb = { 0.f };
731
732 dt_colorrings_hsb_to_profile_rgb(before_hsb, white, lut_profile, before_rgb);
733 dt_colorrings_hsb_to_profile_rgb(after_hsb, white, lut_profile, after_rgb);
734 dt_colorrings_project_to_cube_shell(neutral_rgb, before_rgb);
735 dt_colorrings_project_to_cube_shell(neutral_rgb, after_rgb);
736
737 for(int c = 0; c < 3; c++)
738 {
739 control_nodes[count].input_rgb[c] = before_rgb[c];
740 control_nodes[count].output_rgb[c] = after_rgb[c];
741 }
742 count++;
743 }
744 }
745
746 return count;
747}
748
751{
755 const dt_iop_order_iccprofile_info_t *lut_profile
757 : NULL;
759
760 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(lut_profile))
761 {
762 d->clut = NULL;
763 d->clut_level = 0;
764 d->lut_profile = NULL;
765 d->work_profile = NULL;
766 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
767 return;
768 }
769
771 if(!gd->cache_valid || !_lut_fields_equal(&gd->params, p))
772 {
773 _build_clut(&gd->cache, p, lut_profile);
774 memcpy(&gd->params, p, sizeof(*p));
775 gd->cache_valid = TRUE;
776 gd->cache_generation++;
777 }
778
779 d->clut = gd->cache.clut;
780 d->clut_level = gd->cache.clut_level;
781 d->white_level = exp2f(p->white_level);
782 d->lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
783 d->work_profile = (dt_iop_order_iccprofile_info_t *)work_profile;
784 d->interpolation = (dt_lut3d_interpolation_t)p->interpolation;
785 memcpy(d->reference_saturation, gd->cache.reference_saturation, sizeof(d->reference_saturation));
787
790}
791
793{
795 piece->data = d;
796 piece->data_size = sizeof(dt_iop_colorequal_data_t);
797 d->clut = NULL;
798 d->clut_level = 0;
799 d->white_level = 1.f;
800 d->lut_profile = NULL;
801 memset(d->reference_saturation, 0, sizeof(d->reference_saturation));
802 d->work_profile = NULL;
803 d->interpolation = DT_LUT3D_INTERP_TETRAHEDRAL;
804}
805
807{
809 d->clut = NULL;
810 dt_free_align(piece->data);
811 piece->data = NULL;
812}
813
814#ifdef HAVE_OPENCL
815int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
816 cl_mem dev_in, cl_mem dev_out)
817{
820 const int width = piece->roi_in.width;
821 const int height = piece->roi_in.height;
822 const int devid = pipe->devid;
823 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
824 const float white_level = fmaxf(d->white_level, 1e-6f);
825 const float normalize = 1.f / white_level;
826 const float denormalize = white_level;
827 const float black = 0.f;
828 cl_mem clut_cl = NULL;
829 cl_int err = CL_SUCCESS;
830 const int kernel = (d->interpolation == DT_LUT3D_INTERP_TRILINEAR) ? gd->kernel_lut3d_trilinear
831 : (d->interpolation == DT_LUT3D_INTERP_PYRAMID) ? gd->kernel_lut3d_pyramid
833
834 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile)) return FALSE;
835
836 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_in);
837 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
838 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
839 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
840 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
841 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &normalize);
842 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
843 if(err != CL_SUCCESS) goto cleanup;
844
845 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->work_profile,
846 d->lut_profile, "colorequal work to HLG Rec2020"))
847 {
848 err = CL_INVALID_OPERATION;
849 goto cleanup;
850 }
851
853 clut_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3 * d->clut_level * d->clut_level * d->clut_level,
854 gd->cache.clut);
856 if(IS_NULL_PTR(clut_cl))
857 {
858 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
859 goto cleanup;
860 }
861
862 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dev_out);
863 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &dev_out);
864 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &width);
865 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &height);
866 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &clut_cl);
867 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &d->clut_level);
868 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
869 if(err != CL_SUCCESS) goto cleanup;
870
871 if(!dt_ioppr_transform_image_colorspace_rgb_cl(devid, dev_out, dev_out, width, height, d->lut_profile,
872 d->work_profile, "colorequal HLG Rec2020 to work"))
873 {
874 err = CL_INVALID_OPERATION;
875 goto cleanup;
876 }
877
878 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 0, sizeof(cl_mem), &dev_out);
879 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 1, sizeof(cl_mem), &dev_out);
880 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 2, sizeof(int), &width);
881 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 3, sizeof(int), &height);
882 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 4, sizeof(float), &black);
883 dt_opencl_set_kernel_arg(devid, gd->kernel_exposure, 5, sizeof(float), &denormalize);
884 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_exposure, sizes);
885
886cleanup:
888 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_colorequal] couldn't enqueue kernel! %d\n", err);
889 return err == CL_SUCCESS;
890}
891#endif
892
894int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
895 const void *const ibuf, void *const obuf)
896{
899 const int width = piece->roi_in.width;
900 const int height = piece->roi_in.height;
901 const int ch = piece->dsc_in.channels;
902
903 if(IS_NULL_PTR(d->clut) || d->clut_level == 0 || IS_NULL_PTR(d->lut_profile) || IS_NULL_PTR(d->work_profile))
904 {
906 return 0;
907 }
908
909 const float white_level = fmaxf(d->white_level, 1e-6f);
911 for(size_t k = 0; k < (size_t)width * height; k++)
912 {
913 const float *const in = (const float *)ibuf + k * ch;
914 float *const out = (float *)obuf + k * ch;
915 out[0] = in[0] / white_level;
916 out[1] = in[1] / white_level;
917 out[2] = in[2] / white_level;
918 if(ch > 3) out[3] = in[3];
919 }
920
921 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->work_profile,
922 d->lut_profile, "colorequal work to HLG Rec2020");
923
925 dt_lut3d_apply((float *)obuf, (float *)obuf, (size_t)width * height, d->clut, d->clut_level, 1.f,
926 d->interpolation);
928
929 dt_ioppr_transform_image_colorspace_rgb((float *)obuf, (float *)obuf, width, height, d->lut_profile, d->work_profile,
930 "colorequal HLG Rec2020 to work");
932 for(size_t k = 0; k < (size_t)width * height; k++)
933 {
934 float *const out = (float *)obuf + k * ch;
935 out[0] *= white_level;
936 out[1] *= white_level;
937 out[2] *= white_level;
938 }
939 return 0;
940}
941
949{
950 if(!self->enabled) return;
953 const dt_iop_colorequal_params_t *p = g ? &g->gui_params : (const dt_iop_colorequal_params_t *)self->params;
954 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
955 const double start = log_perf ? dt_get_wtime() : 0.0;
956 uint64_t cache_generation = 0;
957
958 if(IS_NULL_PTR(g->viewer)) return;
959
961 cache_generation = gd->cache_generation;
963
964 if(!g->viewer_lut_dirty && g->viewer_lut_valid && g->viewer_lut_generation == cache_generation) return;
965
966 const dt_iop_order_iccprofile_info_t *lut_profile
968 : NULL;
969 const dt_iop_order_iccprofile_info_t *display_profile
971 : NULL;
972
973 if(IS_NULL_PTR(lut_profile))
974 {
975 dt_lut_viewer_set_lut(g->viewer, NULL, 0, NULL, NULL, NULL);
976 dt_lut_viewer_set_control_nodes(g->viewer, NULL, 0);
977 g->viewer_lut_dirty = FALSE;
978 g->viewer_lut_valid = FALSE;
979 g->viewer_lut_generation = 0;
980 g->viewer_control_node_count = 0;
981 return;
982 }
983
985 if(!gd->cache_valid || !_lut_fields_equal(&gd->params, p) || IS_NULL_PTR((&gd->cache)->clut))
986 {
987 _build_clut(&gd->cache, p, lut_profile);
988 memcpy(&gd->params, p, sizeof(*p));
989 gd->cache_valid = TRUE;
990 gd->cache_generation++;
991 }
992 cache_generation = gd->cache_generation;
993
994 g->viewer_lut.clut = gd->cache.clut;
995 g->viewer_lut.clut_level = gd->cache.clut_level;
996 g->viewer_lut.lut_profile = (dt_iop_order_iccprofile_info_t *)lut_profile;
997 memcpy(g->viewer_lut.reference_saturation, gd->cache.reference_saturation, sizeof(g->viewer_lut.reference_saturation));
998 g->viewer_control_node_count = _build_viewer_control_nodes(p, lut_profile, g->viewer_control_nodes);
999 dt_lut_viewer_set_lut(g->viewer, g->viewer_lut.clut, g->viewer_lut.clut_level, &gd->lock,
1000 g->viewer_lut.lut_profile, display_profile);
1001 dt_lut_viewer_set_control_nodes(g->viewer, g->viewer_control_nodes, g->viewer_control_node_count);
1003 g->viewer_lut_dirty = FALSE;
1004 g->viewer_lut_valid = TRUE;
1005 g->viewer_lut_generation = cache_generation;
1006
1007 dt_lut_viewer_queue_draw(g->viewer);
1008
1009 if(log_perf)
1010 dt_print(DT_DEBUG_PERF, "[colorequal] gui LUT cache sync level=%u total=%.3fms\n", g->viewer_lut.clut_level,
1011 1000.0 * (dt_get_wtime() - start));
1012}
1013
1015{
1016 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
1017 const double start = log_perf ? dt_get_wtime() : 0.0;
1018 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1019 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
1020 {
1021 const dt_iop_colorequal_ring_t current_ring = (dt_iop_colorequal_ring_t)ring;
1023 const int nodes = _curve_nodes_count_const(p, current_ring, channel);
1024 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, current_ring, channel);
1025
1026 if(!g->curve[ring][ch] || g->curve_nodes[ring][ch] != nodes || g->curve[ring][ch]->c.m_numAnchors != nodes)
1027 {
1028 if(g->curve[ring][ch]) dt_draw_curve_destroy(g->curve[ring][ch]);
1029 g->curve[ring][ch] = dt_draw_curve_new(0.f, 1.f, MONOTONE_HERMITE);
1030 g->curve_nodes[ring][ch] = nodes;
1031
1032 for(int k = 0; k < nodes; k++) dt_draw_curve_add_point(g->curve[ring][ch], curve[k].x, curve[k].y);
1033 }
1034 else
1035 {
1036 for(int k = 0; k < nodes; k++) dt_draw_curve_set_point(g->curve[ring][ch], k, curve[k].x, curve[k].y);
1037 }
1038
1039 dt_draw_curve_calc_values_V2(g->curve[ring][ch], 0.f, 1.f, DT_IOP_COLOREQUAL_GRAPH_RES, NULL,
1040 g->draw_ys[ring][ch], TRUE);
1041 }
1042
1043 memcpy(&g->cached_curve_params, p, sizeof(*p));
1044 g->curve_cache_valid = TRUE;
1045
1046 if(log_perf)
1047 dt_print(DT_DEBUG_PERF, "[colorequal] gui curve cache rebuild total=%.3fms\n",
1048 1000.0 * (dt_get_wtime() - start));
1049}
1050
1051static inline void _graph_background_hsb(const dt_iop_colorequal_channel_t channel, const float x, const float y,
1052 const float ring_brightness, const float reference_saturation,
1054{
1055 const float hue = dt_colorrings_curve_x_to_hue(x);
1056
1057 switch(channel)
1058 {
1060 HSB[0] = dt_colorrings_wrap_hue_pi(hue + _channel_value_from_y(channel, y));
1061 HSB[1] = reference_saturation;
1062 HSB[2] = ring_brightness;
1063 break;
1065 HSB[0] = hue;
1066 HSB[1] = reference_saturation;
1067 HSB[2] = CLAMP(ring_brightness * _channel_value_from_y(channel, y), 0.f, 1.f);
1068 break;
1070 default:
1071 HSB[0] = hue;
1072 HSB[1] = CLAMP(reference_saturation * _channel_value_from_y(channel, y), 0.f, 1.f);
1073 HSB[2] = ring_brightness;
1074 break;
1075 }
1076}
1077
1078static void _draw_graph_background(cairo_t *cr, const dt_iop_colorequal_channel_t channel,
1079 const dt_iop_colorequal_ring_t ring, const float graph_width,
1080 const float graph_height, const float white, const float reference_saturation,
1081 const dt_iop_order_iccprofile_info_t *display_profile)
1082{
1083 const float ring_brightness = dt_colorrings_ring_brightness((dt_colorrings_ring_t)ring);
1085
1086 // Fast loop
1088 for(int slice = 0; slice < DT_IOP_COLOREQUAL_GRAPH_GRADIENTS; slice++)
1089 {
1090 const float y = (float)(DT_IOP_COLOREQUAL_GRAPH_GRADIENTS - slice) / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS;
1091 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1092 {
1093 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1094 float *const colors = bg + (slice * DT_IOP_COLOREQUAL_GRAPH_RES + k) * 4;
1095 dt_aligned_pixel_t HSB = { 0.f };
1096 _graph_background_hsb(channel, x, y, ring_brightness, reference_saturation, HSB);
1097 dt_colorrings_hsb_to_display_rgb(HSB, white, display_profile, colors);
1098 }
1099 }
1100
1101 // Slow loop
1102 for(int slice = 0; slice < DT_IOP_COLOREQUAL_GRAPH_GRADIENTS; slice++)
1103 {
1104 cairo_pattern_t *gradient = cairo_pattern_create_linear(0.0, 0.0, graph_width, 0.0);
1105 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1106 {
1107 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1108 float *const colors = bg + (slice * DT_IOP_COLOREQUAL_GRAPH_RES + k) * 4;
1109 cairo_pattern_add_color_stop_rgba(gradient, x, colors[0], colors[1], colors[2], 1.0);
1110 }
1111
1112 cairo_rectangle(cr, 0.f, graph_height / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS * (float)slice, graph_width,
1113 graph_height / (float)DT_IOP_COLOREQUAL_GRAPH_GRADIENTS);
1114 cairo_set_source(cr, gradient);
1115 cairo_fill(cr);
1116 cairo_pattern_destroy(gradient);
1117 }
1118 dt_free_align(bg);
1119}
1120
1121static gboolean _draw_curve(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1122{
1123 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1125 const dt_iop_colorequal_params_t *p = &g->gui_params;
1126 const dt_iop_colorequal_ring_t ring
1127 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1128 const dt_iop_colorequal_channel_t channel
1129 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1130
1131 if(!g->curve_cache_valid || !_curve_fields_equal(&g->cached_curve_params, p)) _update_curve_cache(g, p);
1132
1133 GtkAllocation allocation;
1134 gtk_widget_get_allocation(widget, &allocation);
1135 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1136
1137 const float inset = DT_IOP_COLOREQUAL_GRAPH_INSET;
1138 const float graph_width = allocation.width - 2.f * inset;
1139 const float graph_height = allocation.height - DT_IOP_COLOREQUAL_AXIS_HEIGHT - 2.f * inset;
1140 const float white = dt_colorrings_graph_white();
1141 const dt_iop_order_iccprofile_info_t *const display_profile
1143 : NULL;
1144 dt_colorrings_compute_reference_saturations(white, g->reference_saturation);
1152 const float background_saturation
1153 = (channel == DT_IOP_COLOREQUAL_HUE) ? g->reference_saturation[ring]
1154 : CLAMP(g->reference_saturation[ring] * 1.35f, 0.f, 1.f);
1155
1156 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1157 cairo_t *cr = cairo_create(cst);
1158
1159 if(!g->background_surface[ring][channel] || g->cached_width[ring][channel] != allocation.width
1160 || g->cached_height[ring][channel] != allocation.height
1161 || fabsf(g->cached_white[ring][channel] - white) > 1e-6f
1162 || fabsf(g->cached_reference_saturation[ring][channel] - background_saturation) > 1e-6f
1163 || g->cached_display_profile[ring][channel] != display_profile)
1164 {
1165 if(g->background_surface[ring][channel]) cairo_surface_destroy(g->background_surface[ring][channel]);
1166 g->background_surface[ring][channel]
1167 = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1168 cairo_t *background_cr = cairo_create(g->background_surface[ring][channel]);
1169
1170 gtk_render_background(context, background_cr, 0, 0, allocation.width, allocation.height);
1171 cairo_translate(background_cr, inset, inset);
1172 _draw_graph_background(background_cr, channel, ring, graph_width, graph_height, white, background_saturation,
1173 display_profile);
1174
1175 cairo_rectangle(background_cr, 0.f, 0.f, graph_width, graph_height);
1176 cairo_clip(background_cr);
1177
1178 cairo_set_line_width(background_cr, DT_PIXEL_APPLY_DPI(0.5f));
1179 set_color(background_cr, darktable.bauhaus->graph_border);
1180 dt_draw_grid(background_cr, 8, 0, 0, graph_width, graph_height);
1181
1182 set_color(background_cr, darktable.bauhaus->graph_fg);
1183 cairo_set_line_width(background_cr, DT_PIXEL_APPLY_DPI(1.f));
1184 cairo_move_to(background_cr, 0.f, 0.5f * graph_height);
1185 cairo_line_to(background_cr, graph_width, 0.5f * graph_height);
1186 cairo_stroke(background_cr);
1187
1188 cairo_reset_clip(background_cr);
1189 cairo_translate(background_cr, 0.f, graph_height);
1190 cairo_pattern_t *axis = cairo_pattern_create_linear(0.0, 0.0, graph_width, 0.0);
1191
1192 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1193 {
1194 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1);
1195 dt_aligned_pixel_t RGB = { 0.f };
1197 (dt_aligned_pixel_t){ dt_colorrings_curve_x_to_hue(x), background_saturation,
1199 white, display_profile, RGB);
1200 cairo_pattern_add_color_stop_rgba(axis, x, RGB[0], RGB[1], RGB[2], 1.0);
1201 }
1202
1203 cairo_rectangle(background_cr, 0.f, 0.f, graph_width, DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1204 cairo_set_source(background_cr, axis);
1205 cairo_fill(background_cr);
1206 cairo_pattern_destroy(axis);
1207 cairo_destroy(background_cr);
1208
1209 g->cached_width[ring][channel] = allocation.width;
1210 g->cached_height[ring][channel] = allocation.height;
1211 g->cached_white[ring][channel] = white;
1212 g->cached_reference_saturation[ring][channel] = background_saturation;
1213 g->cached_display_profile[ring][channel] = display_profile;
1214 }
1215
1216 cairo_set_source_surface(cr, g->background_surface[ring][channel], 0, 0);
1217 cairo_paint(cr);
1218
1219 cairo_translate(cr, inset, inset);
1220
1221 if(g->picker_valid)
1222 {
1223 const float picker_x = dt_colorrings_hue_to_curve_x(g->picker_hue) * graph_width;
1224 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5f));
1226 cairo_move_to(cr, picker_x, 0.f);
1227 cairo_line_to(cr, picker_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1228 cairo_stroke(cr);
1229 }
1230
1232 const dt_iop_colorequal_channel_t active_channel = _active_channel_from_gui(g, active_ring);
1233 if(g->cursor_sample_valid && ring == active_ring && channel == active_channel)
1234 {
1235 const float cursor_x = dt_colorrings_hue_to_curve_x(g->cursor_hue) * graph_width;
1236 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.5f));
1237 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.75f);
1238 cairo_move_to(cr, cursor_x, 0.f);
1239 cairo_line_to(cr, cursor_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1240 cairo_stroke(cr);
1241
1242 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f));
1243 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1244 0.95);
1245 cairo_move_to(cr, cursor_x, 0.f);
1246 cairo_line_to(cr, cursor_x, graph_height + DT_IOP_COLOREQUAL_AXIS_HEIGHT);
1247 cairo_stroke(cr);
1248 }
1249
1250 cairo_rectangle(cr, 0.f, 0.f, graph_width, graph_height);
1251 cairo_clip(cr);
1252
1253 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
1255
1256 for(int k = 0; k < DT_IOP_COLOREQUAL_GRAPH_RES; k++)
1257 {
1258 const float x = (float)k / (float)(DT_IOP_COLOREQUAL_GRAPH_RES - 1) * graph_width;
1259 const float y = (1.f - g->draw_ys[ring][channel][k]) * graph_height;
1260
1261 if(k == 0)
1262 cairo_move_to(cr, x, y);
1263 else
1264 cairo_line_to(cr, x, y);
1265 }
1266
1267 cairo_stroke(cr);
1268
1269 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
1270 const int nodes = _curve_nodes_count_const(p, ring, channel);
1271
1272 for(int k = 0; k < nodes; k++)
1273 {
1274 const float x = curve[k].x * graph_width;
1275 const float y = (1.f - curve[k].y) * graph_height;
1276 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.f));
1277 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);
1279 cairo_stroke_preserve(cr);
1281 cairo_fill(cr);
1282 }
1283
1284 if(g->cursor_sample_valid && ring == active_ring && channel == active_channel)
1285 {
1286 float curve_x = 0.f;
1287 float curve_y = 0.f;
1288 float offset_normalized = 0.f;
1289 if(_cursor_curve_state(p, ring, channel, g->cursor_hue, &curve_x, &curve_y, &offset_normalized))
1290 {
1291 const float marker_x = curve_x * graph_width;
1292 const float marker_y = (1.f - curve_y) * graph_height;
1293 const float outer_radius = DT_PIXEL_APPLY_DPI(7.f);
1294 const float inner_radius = DT_PIXEL_APPLY_DPI(4.f);
1295 const float intensity_radius = DT_PIXEL_APPLY_DPI(11.f);
1296
1297 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f));
1298 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.4f);
1299 cairo_arc(cr, marker_x, marker_y, intensity_radius, 0.f, 2.f * M_PI_F);
1300 cairo_stroke(cr);
1301
1302 if(fabsf(offset_normalized) > 1e-4f)
1303 {
1304 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.25f));
1305 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1],
1306 g->cursor_output_display[2], 0.95);
1307 if(offset_normalized > 0.f)
1308 cairo_arc(cr, marker_x, marker_y, intensity_radius, -M_PI_F / 2.f,
1309 -M_PI_F / 2.f + 2.f * M_PI_F * fabsf(offset_normalized));
1310 else
1311 cairo_arc_negative(cr, marker_x, marker_y, intensity_radius, -M_PI_F / 2.f,
1312 -M_PI_F / 2.f - 2.f * M_PI_F * fabsf(offset_normalized));
1313 cairo_stroke(cr);
1314 }
1315
1316 cairo_arc(cr, marker_x, marker_y, outer_radius, 0.f, 2.f * M_PI_F);
1317 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1],
1318 g->cursor_output_display[2], 0.95);
1319 cairo_fill_preserve(cr);
1320 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.9f);
1321 cairo_stroke(cr);
1322
1323 cairo_arc(cr, marker_x, marker_y, inner_radius, 0.f, 2.f * M_PI_F);
1324 cairo_set_source_rgba(cr, g->cursor_input_display[0], g->cursor_input_display[1], g->cursor_input_display[2],
1325 0.95);
1326 cairo_fill_preserve(cr);
1327 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.8f);
1328 cairo_stroke(cr);
1329 }
1330 }
1331
1332 cairo_reset_clip(cr);
1333
1334 cairo_destroy(cr);
1335 cairo_set_source_surface(crf, cst, 0, 0);
1336 cairo_paint(crf);
1337 cairo_surface_destroy(cst);
1338 return TRUE;
1339}
1340
1341static void _cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
1342{
1343 (void)instance;
1344 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1346 if(g->pending_preview_hash != hash || !_refresh_preview_cursor_sample(self)) return;
1347
1350 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1352}
1353
1354static void _preview_cache_wait_restart(gpointer user_data)
1355{
1356 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1357 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->gui_data)) return;
1358
1360 if(IS_NULL_PTR(g) || !g->has_focus || !_refresh_preview_cursor_sample(self)) return;
1361
1364 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1366}
1367
1369 const dt_iop_colorequal_channel_t channel, const float mouse_x, const float mouse_y,
1370 const float graph_width, const float graph_height)
1371{
1373 const dt_iop_colorequal_params_t *p = &g->gui_params;
1374 int selected = -1;
1375 float best = DT_PIXEL_APPLY_DPI(10.f) * DT_PIXEL_APPLY_DPI(10.f);
1376 const dt_iop_colorequal_node_t *curve = _curve_nodes_const(p, ring, channel);
1377 const int nodes = _curve_nodes_count_const(p, ring, channel);
1378
1379 for(int k = 0; k < nodes; k++)
1380 {
1381 const float node_x = curve[k].x * graph_width;
1382 const float node_y = (1.f - curve[k].y) * graph_height;
1383 const float dx = mouse_x - node_x;
1384 const float dy = mouse_y - node_y;
1385 const float distance = dx * dx + dy * dy;
1386
1387 if(distance < best)
1388 {
1389 best = distance;
1390 selected = k;
1391 }
1392 }
1393
1394 return selected;
1395}
1396
1397static int _add_node(dt_iop_colorequal_node_t *curve, int *nodes, const float x, const float y)
1398{
1399 int selected = -1;
1400
1401 if(curve[0].x > x)
1402 selected = 0;
1403 else
1404 {
1405 for(int k = 1; k < *nodes; k++)
1406 if(curve[k].x > x)
1407 {
1408 selected = k;
1409 break;
1410 }
1411 }
1412
1413 if(selected == -1) selected = *nodes;
1414
1415 if((selected > 0 && x - curve[selected - 1].x <= DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1416 || (selected < *nodes && curve[selected].x - x <= DT_IOP_COLOREQUAL_MIN_X_DISTANCE))
1417 return -1;
1418
1419 for(int k = *nodes; k > selected; k--)
1420 {
1421 curve[k].x = curve[k - 1].x;
1422 curve[k].y = curve[k - 1].y;
1423 }
1424
1425 curve[selected].x = x;
1426 curve[selected].y = y;
1427 (*nodes)++;
1428 return selected;
1429}
1430
1432 const dt_iop_colorequal_channel_t channel, const int node, const float x,
1433 const float y)
1434{
1436 dt_iop_colorequal_params_t *p = &g->gui_params;
1437 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
1438 const int nodes = _curve_nodes_count_const(p, ring, channel);
1439
1440 float new_x = CLAMP(x, 0.f, 0.999f);
1441 const float new_y = CLAMP(y, 0.f, 1.f);
1442
1443 if(node == 0)
1444 {
1445 if(new_x + 1.f - curve[nodes - 1].x < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1446 new_x = curve[nodes - 1].x + DT_IOP_COLOREQUAL_MIN_X_DISTANCE - 1.f;
1447 }
1448 else if(node == nodes - 1)
1449 {
1450 if(curve[0].x + 1.f - new_x < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1451 new_x = curve[0].x + 1.f - DT_IOP_COLOREQUAL_MIN_X_DISTANCE;
1452 }
1453 else if((new_x - curve[node - 1].x) < DT_IOP_COLOREQUAL_MIN_X_DISTANCE
1454 || (curve[node + 1].x - new_x) < DT_IOP_COLOREQUAL_MIN_X_DISTANCE)
1455 {
1456 return FALSE;
1457 }
1458
1459 curve[node].x = new_x;
1460 curve[node].y = new_y;
1461 return TRUE;
1462}
1463
1464static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1465{
1466 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1468 const dt_iop_colorequal_ring_t ring
1469 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1470 const dt_iop_colorequal_channel_t channel
1471 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1472 GtkAllocation allocation;
1473 gtk_widget_get_allocation(widget, &allocation);
1474
1475 const float graph_width = allocation.width - 2.f * DT_IOP_COLOREQUAL_GRAPH_INSET;
1476 const float graph_height
1478 const float mouse_x = CLAMP(event->x - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_width);
1479 const float mouse_y = CLAMP(event->y - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_height);
1480
1481 if(g->dragging[ring][channel] && g->selected[ring][channel] >= 0)
1482 {
1483 if(_move_selected_node(self, ring, channel, g->selected[ring][channel], mouse_x / graph_width,
1484 1.f - mouse_y / graph_height))
1485 {
1486 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1487 g->curve_cache_valid = FALSE;
1488 g->viewer_lut_dirty = TRUE;
1489 if(g->cursor_valid && g->has_focus)
1490 {
1493 }
1495 gtk_widget_queue_draw(widget);
1496 }
1497
1498 return TRUE;
1499 }
1500
1501 g->selected[ring][channel]
1502 = _find_selected_node(self, ring, channel, mouse_x, mouse_y, graph_width, graph_height);
1503 gtk_widget_queue_draw(widget);
1504 return TRUE;
1505}
1506
1507static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1508{
1509 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1511 const dt_iop_colorequal_ring_t ring
1512 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1513 const dt_iop_colorequal_channel_t channel
1514 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1515 dt_iop_colorequal_params_t *p = &g->gui_params;
1517 dt_iop_colorequal_node_t *curve = _curve_nodes(p, ring, channel);
1518 dt_iop_colorequal_node_t *default_curve = _curve_nodes(d, ring, channel);
1519 int *nodes = _curve_nodes_count(p, ring, channel);
1520 const int default_nodes = _curve_nodes_count_const(d, ring, channel);
1521
1522 GtkAllocation allocation;
1523 gtk_widget_get_allocation(widget, &allocation);
1524 const float graph_width = allocation.width - 2.f * DT_IOP_COLOREQUAL_GRAPH_INSET;
1525 const float graph_height
1527 const float mouse_x = CLAMP(event->x - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_width) / graph_width;
1528 const float mouse_y = 1.f - CLAMP(event->y - DT_IOP_COLOREQUAL_GRAPH_INSET, 0.f, graph_height) / graph_height;
1529
1530 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1531 {
1532 *nodes = default_nodes;
1533 for(int k = 0; k < default_nodes; k++) curve[k] = default_curve[k];
1534
1535 g->selected[ring][channel] = -1;
1536 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1537 g->curve_cache_valid = FALSE;
1538 g->viewer_lut_dirty = TRUE;
1539 if(g->cursor_valid && g->has_focus)
1540 {
1543 }
1545 gtk_widget_queue_draw(widget);
1546 return TRUE;
1547 }
1548
1549 if(event->button == 1 && dt_modifier_is(event->state, GDK_CONTROL_MASK) && *nodes < DT_IOP_COLOREQUAL_MAXNODES)
1550 {
1551 const float y
1552 = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, *nodes, mouse_x);
1553 const int selected = _add_node(curve, nodes, mouse_x, y);
1554
1555 if(selected >= 0)
1556 {
1557 g->selected[ring][channel] = selected;
1558 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1559 g->curve_cache_valid = FALSE;
1560 g->viewer_lut_dirty = TRUE;
1561 if(g->cursor_valid && g->has_focus)
1562 {
1565 }
1567 gtk_widget_queue_draw(widget);
1568 }
1569
1570 return TRUE;
1571 }
1572
1573 if(event->button == 1)
1574 {
1575 g->selected[ring][channel] = _find_selected_node(self, ring, channel, mouse_x * graph_width,
1576 (1.f - mouse_y) * graph_height, graph_width, graph_height);
1577 g->dragging[ring][channel] = (g->selected[ring][channel] >= 0);
1578 return TRUE;
1579 }
1580
1581 if(event->button == 3 && g->selected[ring][channel] >= 0 && *nodes > 2)
1582 {
1583 for(int k = g->selected[ring][channel]; k < *nodes - 1; k++) curve[k] = curve[k + 1];
1584
1585 (*nodes)--;
1586 g->selected[ring][channel] = -1;
1587 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1588 g->curve_cache_valid = FALSE;
1589 g->viewer_lut_dirty = TRUE;
1590 if(g->cursor_valid && g->has_focus)
1591 {
1594 }
1596 gtk_widget_queue_draw(widget);
1597 return TRUE;
1598 }
1599
1600 return FALSE;
1601}
1602
1603static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1604{
1605 if(event->button == 1)
1606 {
1607 const dt_iop_colorequal_ring_t ring
1608 = (dt_iop_colorequal_ring_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-ring"));
1609 const dt_iop_colorequal_channel_t channel
1610 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "colorequal-channel"));
1611 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1613 const gboolean was_dragging = g->dragging[ring][channel];
1614 g->dragging[ring][channel] = FALSE;
1615
1622 if(was_dragging)
1623 {
1626 }
1627 }
1628
1629 return TRUE;
1630}
1631
1632static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num,
1633 gpointer user_data)
1634{
1635 if(darktable.gui->reset) return;
1636
1637 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1639 const int source_ring = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(notebook), "colorequal-ring"));
1640 const dt_iop_colorequal_channel_t channel
1641 = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(page), "colorequal-channel"));
1642 dt_conf_set_int("plugins/darkroom/colorequal/gui_channel_page", (int)page_num);
1643
1644 if(channel < DT_IOP_COLOREQUAL_NUM_CHANNELS)
1645 {
1646 ++darktable.gui->reset;
1647 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1648 if(ring != source_ring) gtk_notebook_set_current_page(g->channel_notebook[ring], (int)page_num);
1649 --darktable.gui->reset;
1650
1651 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
1652 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1653
1654 if(g->cursor_valid && g->has_focus) dt_control_queue_redraw_center();
1655 }
1656}
1657
1658static void _ring_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
1659{
1660 if(darktable.gui->reset) return;
1661
1662 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1664
1665 if(page_num < DT_IOP_COLOREQUAL_NUM_RINGS)
1666 {
1667 dt_conf_set_int("plugins/darkroom/colorequal/gui_ring_page", (int)page_num);
1668 GtkWidget *page_widget = gtk_notebook_get_nth_page(
1669 g->channel_notebook[page_num], gtk_notebook_get_current_page(g->channel_notebook[page_num]));
1670 if(page_widget)
1671 {
1672 const dt_iop_colorequal_channel_t channel = (dt_iop_colorequal_channel_t)GPOINTER_TO_INT(
1673 g_object_get_data(G_OBJECT(page_widget), "colorequal-channel"));
1674 if(channel < DT_IOP_COLOREQUAL_NUM_CHANNELS) gtk_widget_queue_draw(GTK_WIDGET(g->area[page_num][channel]));
1675 }
1676 }
1677
1678 if(g->cursor_valid && g->has_focus)
1679 {
1682 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1684 }
1685}
1686
1687int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
1688{
1690 dt_develop_t *dev = self ? self->dev : NULL;
1691 if(!g->has_focus) return 0;
1692
1694 {
1695 g->cursor_valid = FALSE;
1699 return 0;
1700 }
1701
1702 const int wd = dev->roi.preview_width;
1703 const int ht = dev->roi.preview_height;
1704 if(wd < 1 || ht < 1) return 0;
1705
1706 float point[2] = { (float)x, (float)y };
1709
1710 const int cursor_x = (int)point[0];
1711 const int cursor_y = (int)point[1];
1712 if(cursor_x >= 0 && cursor_x < wd && cursor_y >= 0 && cursor_y < ht)
1713 {
1714 g->cursor_valid = TRUE;
1715 g->cursor_pos_x = cursor_x;
1716 g->cursor_pos_y = cursor_y;
1717
1719 }
1720 else
1721 {
1722 g->cursor_valid = FALSE;
1724 }
1725
1727
1730 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1732 return g->cursor_valid ? 1 : 0;
1733}
1734
1736{
1738 g->cursor_valid = FALSE;
1741
1744 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1746 return 1;
1747}
1748
1749int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
1750 uint32_t state)
1751{
1753 if(!g->has_focus || which != 3 || !g->cursor_valid || !g->cursor_sample_valid
1755 return 0;
1756
1757 if(!self->enabled)
1758 {
1759 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
1760 return 1;
1761 }
1762
1765 dt_iop_colorequal_node_t *curve = _curve_nodes(&g->gui_params, ring, channel);
1766 int *nodes = _curve_nodes_count(&g->gui_params, ring, channel);
1767 if(*nodes >= DT_IOP_COLOREQUAL_MAXNODES) return 1;
1768
1769 const float curve_x = dt_colorrings_hue_to_curve_x(g->cursor_hue);
1770 const float curve_y
1771 = dt_colorrings_curve_periodic_sample((const dt_colorrings_node_t *)curve, *nodes, curve_x);
1772 const int selected = _add_node(curve, nodes, curve_x, curve_y);
1773 if(selected < 0) return 1;
1774
1775 g->selected[ring][channel] = selected;
1776 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1777 g->curve_cache_valid = FALSE;
1778 g->viewer_lut_dirty = TRUE;
1780 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1783 return 1;
1784}
1785
1786int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
1787{
1789 if(!g->has_focus || !g->cursor_valid || !g->cursor_sample_valid || dt_iop_color_picker_is_visible(self->dev))
1790 return 0;
1791
1792 if(!self->enabled)
1793 {
1794 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
1795 return 1;
1796 }
1797
1800 dt_iop_colorequal_node_t *curve = _curve_nodes(&g->gui_params, ring, channel);
1801 const int nodes = _curve_nodes_count_const(&g->gui_params, ring, channel);
1802 if(nodes < 1) return 1;
1803
1804 const float direction = up ? 1.f : -1.f;
1805 const float curve_x = dt_colorrings_hue_to_curve_x(g->cursor_hue);
1807 const float sigma2 = 2.f * sigma * sigma;
1808 const float step = (channel == DT_IOP_COLOREQUAL_HUE)
1815
1816 for(int k = 0; k < nodes; k++)
1817 {
1818 const float distance = _curve_periodic_distance(curve[k].x, curve_x);
1819 const float weight = expf(-(distance * distance) / sigma2);
1820 float value = _channel_value_from_y(channel, curve[k].y);
1821 value += direction * step * weight;
1822
1823 switch(channel)
1824 {
1826 value = CLAMP(value, -M_PI_F, M_PI_F);
1827 break;
1830 default:
1831 value = CLAMP(value, 0.f, 2.f);
1832 break;
1833 }
1834
1835 curve[k].y = _channel_y_from_value(channel, value);
1836 }
1837
1838 memcpy(self->params, &g->gui_params, sizeof(dt_iop_colorequal_params_t));
1839 g->curve_cache_valid = FALSE;
1840 g->viewer_lut_dirty = TRUE;
1842 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][channel]));
1845 return 1;
1846}
1847
1848void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
1849 int32_t pointerx, int32_t pointery)
1850{
1852 dt_develop_t *dev = self ? self->dev : NULL;
1853 if(IS_NULL_PTR(g) || IS_NULL_PTR(dev)) return;
1854 if(!g->has_focus || !self->enabled || !g->cursor_valid || dt_iop_color_picker_is_visible(dev))
1855 return;
1856
1857 if((dev->preview_pipe && dev->preview_pipe->processing) || !g->cursor_sample_valid)
1858 {
1859 if(!_refresh_preview_cursor_sample(self)) return;
1860 }
1861
1864 float curve_x = 0.f;
1865 float curve_y = 0.f;
1866 float offset_normalized = 0.f;
1867 if(!_cursor_curve_state(&g->gui_params, ring, channel, g->cursor_hue, &curve_x, &curve_y, &offset_normalized))
1868 return;
1869
1870 const float zoom_scale = dt_dev_get_overlay_scale(dev);
1871 dt_dev_rescale_roi(dev, cr, width, height);
1872
1873 const float pointer_x = g->cursor_pos_x;
1874 const float pointer_y = g->cursor_pos_y;
1875 const float outer_radius = DT_IOP_COLOREQUAL_PREVIEW_CURSOR_RADIUS / zoom_scale;
1876 const float inner_radius = outer_radius * 0.55f;
1877 const float intensity_radius = outer_radius * 1.55f;
1878 const float crosshair_gap = outer_radius * 0.25f;
1879 const float crosshair_extent = intensity_radius + DT_PIXEL_APPLY_DPI(5.f) / zoom_scale;
1880
1881 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5f) / zoom_scale);
1882 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.35f);
1883 cairo_arc(cr, pointer_x, pointer_y, intensity_radius, 0.f, 2.f * M_PI_F);
1884 cairo_stroke(cr);
1885
1886 if(fabsf(offset_normalized) > 1e-4f)
1887 {
1888 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.f) / zoom_scale);
1889 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1890 0.95);
1891 if(offset_normalized > 0.f)
1892 cairo_arc(cr, pointer_x, pointer_y, intensity_radius, -M_PI_F / 2.f,
1893 -M_PI_F / 2.f + 2.f * M_PI_F * fabsf(offset_normalized));
1894 else
1895 cairo_arc_negative(cr, pointer_x, pointer_y, intensity_radius, -M_PI_F / 2.f,
1896 -M_PI_F / 2.f - 2.f * M_PI_F * fabsf(offset_normalized));
1897 cairo_stroke(cr);
1898 }
1899
1900 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.25f) / zoom_scale);
1901 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.6f);
1902 cairo_move_to(cr, pointer_x - crosshair_extent, pointer_y);
1903 cairo_line_to(cr, pointer_x - outer_radius - crosshair_gap, pointer_y);
1904 cairo_move_to(cr, pointer_x + outer_radius + crosshair_gap, pointer_y);
1905 cairo_line_to(cr, pointer_x + crosshair_extent, pointer_y);
1906 cairo_move_to(cr, pointer_x, pointer_y - crosshair_extent);
1907 cairo_line_to(cr, pointer_x, pointer_y - outer_radius - crosshair_gap);
1908 cairo_move_to(cr, pointer_x, pointer_y + outer_radius + crosshair_gap);
1909 cairo_line_to(cr, pointer_x, pointer_y + crosshair_extent);
1910 cairo_stroke(cr);
1911
1912 cairo_arc(cr, pointer_x, pointer_y, outer_radius, 0.f, 2.f * M_PI_F);
1913 cairo_set_source_rgba(cr, g->cursor_output_display[0], g->cursor_output_display[1], g->cursor_output_display[2],
1914 0.95);
1915 cairo_fill_preserve(cr);
1916 cairo_set_source_rgba(cr, 0.f, 0.f, 0.f, 0.9f);
1917 cairo_stroke(cr);
1918
1919 cairo_arc(cr, pointer_x, pointer_y, inner_radius, 0.f, 2.f * M_PI_F);
1920 cairo_set_source_rgba(cr, g->cursor_input_display[0], g->cursor_input_display[1], g->cursor_input_display[2], 0.95);
1921 cairo_fill_preserve(cr);
1922 cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.8f);
1923 cairo_stroke(cr);
1924}
1925
1928{
1930 if(IS_NULL_PTR(work_profile)) return;
1931 dt_colorrings_profile_rgb_to_Ych(RGB, work_profile, Ych);
1932}
1933
1936{
1938 if(IS_NULL_PTR(work_profile)) return;
1940}
1941
1943{
1946
1947 if(!widget || !gtk_widget_get_window(widget)) return;
1948
1950
1951 if(!g->has_focus || dt_iop_color_picker_is_visible(self->dev))
1952 {
1954 return;
1955 }
1956
1957 if(g->cursor_valid && self->dev && self->dev->preview_pipe && self->dev->preview_pipe->processing)
1958 {
1960 return;
1961 }
1962
1963 if(g->cursor_valid && self->enabled)
1964 {
1967 _("scroll over image to adjust the selected color graph\n"
1968 "right-click to add a node at the sampled hue"));
1969 return;
1970 }
1971
1973}
1974
1976{
1979 dt_develop_t *dev = self ? self->dev : NULL;
1980 if(!self->enabled || !g->cursor_valid)
1981 {
1982 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-preview-inactive");
1984 return FALSE;
1985 }
1986
1988 const dt_dev_pixelpipe_iop_t *const previous_piece
1989 = piece ? dt_dev_pixelpipe_get_prev_enabled_piece(dev->preview_pipe, piece) : NULL;
1990 if(IS_NULL_PTR(piece) || IS_NULL_PTR(previous_piece) || previous_piece->dsc_out.datatype != TYPE_FLOAT || previous_piece->dsc_out.channels < 3)
1991 {
1992 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1993 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-preview-piece-missing");
1995 return FALSE;
1996 }
1997
1998 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1999 void *input = NULL;
2000 dt_pixel_cache_entry_t *input_entry = NULL;
2001 dt_dev_pixelpipe_cache_wait_set_owner(&g->preview_wait, "colorequal-preview-cursor", self);
2002 if(!dt_dev_pixelpipe_cache_peek_gui(dev->preview_pipe, previous_piece, &input, &input_entry,
2003 &g->preview_wait, _preview_cache_wait_restart, self)
2004 || IS_NULL_PTR(input) || IS_NULL_PTR(input_entry))
2005 {
2006 g->pending_preview_hash = previous_piece->global_hash;
2009 return FALSE;
2010 }
2011
2014
2015 const float point_preview[2] = { (float)g->cursor_pos_x, (float)g->cursor_pos_y };
2016 float point_image[2] = { point_preview[0], point_preview[1] };
2018
2019 const float scale_x = (previous_piece->buf_out.width > 0)
2020 ? (float)previous_piece->roi_out.width / (float)previous_piece->buf_out.width
2021 : 1.f;
2022 const float scale_y = (previous_piece->buf_out.height > 0)
2023 ? (float)previous_piece->roi_out.height / (float)previous_piece->buf_out.height
2024 : 1.f;
2025 const float sample_x
2026 = point_image[0] * (float)previous_piece->buf_out.width * scale_x - (float)previous_piece->roi_out.x;
2027 const float sample_y
2028 = point_image[1] * (float)previous_piece->buf_out.height * scale_y - (float)previous_piece->roi_out.y;
2029 const int xi = CLAMP((int)lroundf(sample_x), 0, previous_piece->roi_out.width - 1);
2030 const int yi = CLAMP((int)lroundf(sample_y), 0, previous_piece->roi_out.height - 1);
2031
2032 dt_aligned_pixel_t input_rgb = { 0.f };
2033 const float *const input_rgbf
2034 = (const float *)input + ((size_t)yi * (size_t)previous_piece->roi_out.width + (size_t)xi) * previous_piece->dsc_out.channels;
2035 input_rgb[0] = input_rgbf[0];
2036 input_rgb[1] = input_rgbf[1];
2037 input_rgb[2] = input_rgbf[2];
2038 input_rgb[3] = 0.f;
2039
2042
2044 const dt_iop_order_iccprofile_info_t *const lut_profile = g->viewer_lut.lut_profile;
2045 dt_aligned_pixel_t output_rgb = { input_rgb[0], input_rgb[1], input_rgb[2], 0.f };
2046 dt_colorrings_apply_rgb_lut(input_rgb, exp2f(g->gui_params.white_level), work_profile, lut_profile,
2047 g->viewer_lut.clut, g->viewer_lut.clut_level, &gd->lock,
2048 (dt_lut3d_interpolation_t)g->gui_params.interpolation, output_rgb);
2049
2050 dt_aligned_pixel_t projected_rgb = { 0.f };
2051 const float white_level = fmaxf(exp2f(g->gui_params.white_level), 1e-6f);
2052 projected_rgb[0] = input_rgb[0] / white_level;
2053 projected_rgb[1] = input_rgb[1] / white_level;
2054 projected_rgb[2] = input_rgb[2] / white_level;
2055
2056 const float neutral = CLAMP((projected_rgb[0] + projected_rgb[1] + projected_rgb[2]) / 3.f, 0.f, 1.f);
2057 dt_aligned_pixel_t axis = { neutral, neutral, neutral, 0.f };
2058 dt_colorrings_project_to_cube_shell(axis, projected_rgb);
2059
2060 dt_aligned_pixel_t HSB = { 0.f };
2061 _pipe_rgb_to_dt_ucs_hsb(self, dev->preview_pipe, projected_rgb, HSB);
2062 if(!isfinite(HSB[0]))
2063 {
2065 return FALSE;
2066 }
2067
2068 g->cursor_hue = dt_colorrings_wrap_hue_pi(HSB[0]);
2069 _work_rgb_to_display_rgb(self, dev->preview_pipe, input_rgb, g->cursor_input_display);
2070 _work_rgb_to_display_rgb(self, dev->preview_pipe, output_rgb, g->cursor_output_display);
2071 g->cursor_sample_valid = TRUE;
2072 return TRUE;
2073}
2074
2081static void _format_picker_brightness_position(const float brightness, char *text, const size_t size)
2082{
2083 if(brightness <= 0.15f)
2084 {
2085 g_snprintf(text, size, "%d%% %s", 100, _("shadows"));
2086 }
2087 else if(brightness < 0.45f)
2088 {
2089 const float t = (brightness - 0.15f) / (0.45f - 0.15f);
2090 const int shadows = CLAMP((int)lroundf((1.f - t) * 100.f), 0, 100);
2091 g_snprintf(text, size, "%d%% %s, %d%% %s", shadows, _("shadows"), 100 - shadows, _("midtones"));
2092 }
2093 else if(brightness < 0.75f)
2094 {
2095 const float t = (brightness - 0.45f) / (0.75f - 0.45f);
2096 const int midtones = CLAMP((int)lroundf((1.f - t) * 100.f), 0, 100);
2097 g_snprintf(text, size, "%d%% %s, %d%% %s", midtones, _("midtones"), 100 - midtones, _("highlights"));
2098 }
2099 else
2100 {
2101 g_snprintf(text, size, "%d%% %s", 100, _("highlights"));
2102 }
2103}
2104
2107{
2110 const dt_iop_module_t *sampled_module = piece && piece->module ? piece->module : self;
2111
2112 if(picker == g->white_level)
2113 {
2114 dt_aligned_pixel_t max_Ych = { 0.f };
2115 _pipe_rgb_to_Ych(self, pipe, (const float *)sampled_module->picked_color_max, max_Ych);
2116
2117 ++darktable.gui->reset;
2118 p->white_level = log2f(max_Ych[0]);
2119 g->gui_params.white_level = p->white_level;
2120 dt_bauhaus_slider_set(g->white_level, p->white_level);
2121 --darktable.gui->reset;
2122
2124 }
2125 else if(picker == g->module_picker)
2126 {
2127 if(sampled_module->picked_color_max[0] < sampled_module->picked_color_min[0])
2128 {
2129 g->picker_valid = FALSE;
2130 gtk_label_set_text(GTK_LABEL(g->picker_info), _("no sample"));
2131 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2132 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2133 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2134 return;
2135 }
2136
2137 dt_aligned_pixel_t HSB = { 0.f };
2138 dt_aligned_pixel_t RGB = { 0.f };
2139 dt_aligned_pixel_t axis = { 0.f };
2140 char text[128] = { 0 };
2141 const float white_level = fmaxf(exp2f(g->gui_params.white_level), 1e-6f);
2142
2143 RGB[0] = sampled_module->picked_color[0] / white_level;
2144 RGB[1] = sampled_module->picked_color[1] / white_level;
2145 RGB[2] = sampled_module->picked_color[2] / white_level;
2146
2154 const float neutral = CLAMP((RGB[0] + RGB[1] + RGB[2]) / 3.f, 0.f, 1.f);
2155 axis[0] = neutral;
2156 axis[1] = neutral;
2157 axis[2] = neutral;
2159 _pipe_rgb_to_dt_ucs_hsb(self, pipe, RGB, HSB);
2160
2161 g->picker_valid = TRUE;
2162 g->picker_hue = dt_colorrings_wrap_hue_pi(HSB[0]);
2163 g->picker_brightness = CLAMP(HSB[2], 0.f, 1.f);
2164 _format_picker_brightness_position(g->picker_brightness, text, sizeof(text));
2165 gtk_label_set_text(GTK_LABEL(g->picker_info), text);
2166
2167 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2168 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2169 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2170 }
2171}
2172
2173void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2174 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
2175{
2176 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
2177 if(IS_NULL_PTR(work_profile) || piece->dsc_in.channels != 4) return;
2178
2181 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2182 const float *const restrict in = (const float *)i;
2183 float max_Y = 0.0f;
2184
2185 __OMP_PARALLEL_FOR__(reduction(max:max_Y))
2186 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height * 4; k += 4)
2187 {
2188 dt_aligned_pixel_t Ych = { 0.f };
2189 dt_colorrings_profile_rgb_to_Ych(in + k, work_profile, Ych);
2190 if(isfinite(Ych[0]))
2191 max_Y = fmaxf(max_Y, Ych[0]);
2192 }
2193
2194 p->white_level = log2f(fmaxf(max_Y, 1e-6f));
2195 if(!IS_NULL_PTR(g))
2196 g->gui_params.white_level = p->white_level;
2197}
2198
2199void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2200{
2203 const gboolean curves_changed = !_curve_fields_equal(&g->gui_params, p);
2204 memcpy(&g->gui_params, self->params, sizeof(dt_iop_colorequal_params_t));
2205 if(curves_changed)
2206 {
2207 g->curve_cache_valid = FALSE;
2208 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2209 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2210 gtk_widget_queue_draw(GTK_WIDGET(g->area[ring][ch]));
2211
2212 if(g->cursor_valid && g->has_focus) dt_control_queue_redraw_center();
2213 }
2214}
2215
2217{
2220 memcpy(&g->gui_params, p, sizeof(dt_iop_colorequal_params_t));
2221 dt_bauhaus_slider_set(g->white_level, p->white_level);
2222 dt_bauhaus_slider_set(g->sigma_L, p->sigma_L);
2223 dt_bauhaus_slider_set(g->sigma_rho, p->sigma_rho);
2224 dt_bauhaus_slider_set(g->sigma_theta, p->sigma_theta);
2225 dt_bauhaus_slider_set(g->neutral_protection, p->neutral_protection);
2226 dt_bauhaus_combobox_set(g->interpolation, p->interpolation);
2227 gui_changed(self, NULL, NULL);
2228}
2229
2230void gui_focus(struct dt_iop_module_t *self, gboolean in)
2231{
2233 g->has_focus = in;
2234
2235 if(in)
2236 {
2237 if(!g->preview_signal_connected)
2238 {
2240 G_CALLBACK(_cacheline_ready_callback), self);
2241 g->preview_signal_connected = TRUE;
2242 }
2243
2244 if(g->cursor_valid && self->dev && self->dev->preview_pipe && !self->dev->preview_pipe->processing)
2246 }
2247 else if(g->preview_signal_connected)
2248 {
2250 g->preview_signal_connected = FALSE;
2251 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2252 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-focus-leave");
2253 }
2254
2257}
2258
2260{
2264
2265 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2266 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++)
2267 {
2268 if(g->curve[ring][ch]) dt_draw_curve_destroy(g->curve[ring][ch]);
2269 g->curve[ring][ch] = NULL;
2270 if(g->background_surface[ring][ch]) cairo_surface_destroy(g->background_surface[ring][ch]);
2271 g->background_surface[ring][ch] = NULL;
2272 }
2273
2274 g->viewer_lut.clut = NULL;
2275
2276 dt_lut_viewer_destroy(&g->viewer);
2277 if(g->preview_signal_connected)
2278 {
2280 g->preview_signal_connected = FALSE;
2281 }
2282 dt_dev_pixelpipe_cache_wait_cleanup(&g->preview_wait, "colorequal-gui-cleanup");
2283
2284 const int current_primary = CLAMP(gtk_notebook_get_current_page(g->ring_notebook), 0, DT_IOP_COLOREQUAL_NUM_RINGS);
2285 dt_conf_set_int("plugins/darkroom/colorequal/gui_ring_page", current_primary);
2286 dt_conf_set_int("plugins/darkroom/colorequal/gui_channel_page",
2287 gtk_notebook_get_current_page(g->channel_notebook[0]));
2289}
2290
2292{
2294 memcpy(&g->gui_params, self->params, sizeof(dt_iop_colorequal_params_t));
2295 g->curve_cache_valid = FALSE;
2296 g->viewer_lut_dirty = TRUE;
2297 g->viewer_lut_valid = FALSE;
2298 g->viewer_lut_generation = 0;
2299 g->preview_signal_connected = FALSE;
2300 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2301 g->has_focus = FALSE;
2302
2303 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2304
2305 GtkWidget *ring_tabs_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2306 gtk_box_pack_start(GTK_BOX(self->widget), ring_tabs_box, TRUE, TRUE, 0);
2307
2308 g->ring_notebook = GTK_NOTEBOOK(gtk_notebook_new());
2309 gtk_widget_set_name(GTK_WIDGET(g->ring_notebook), "colorequal-ring-tabs");
2310 gtk_notebook_set_show_border(g->ring_notebook, FALSE);
2311 g_signal_connect(G_OBJECT(g->ring_notebook), "switch_page", G_CALLBACK(_ring_tabs_switch_callback), self);
2312 gtk_box_pack_start(GTK_BOX(ring_tabs_box), GTK_WIDGET(g->ring_notebook), TRUE, TRUE, 0);
2313
2316 const char *channel_labels[DT_IOP_COLOREQUAL_NUM_CHANNELS] = { _("saturation"), _("brightness"), _("hue") };
2317
2318 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2319 {
2320 GtkWidget *ring_label = gtk_label_new(_ring_label((dt_iop_colorequal_ring_t)ring));
2321 dt_gui_add_class(ring_label, "dt_modulegroups_tab_label");
2322 GtkWidget *ring_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2323 gtk_notebook_append_page(g->ring_notebook, ring_page, ring_label);
2324 gtk_container_child_set(GTK_CONTAINER(g->ring_notebook), ring_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2325
2326 g->channel_notebook[ring] = GTK_NOTEBOOK(gtk_notebook_new());
2327 g_object_set_data(G_OBJECT(g->channel_notebook[ring]), "colorequal-ring", GINT_TO_POINTER(ring));
2328 g_signal_connect(G_OBJECT(g->channel_notebook[ring]), "switch_page", G_CALLBACK(_channel_tabs_switch_callback),
2329 self);
2330 gtk_box_pack_start(GTK_BOX(ring_page), GTK_WIDGET(g->channel_notebook[ring]), TRUE, TRUE, 0);
2331
2332 for(int order = 0; order < DT_IOP_COLOREQUAL_NUM_CHANNELS; order++)
2333 {
2334 const dt_iop_colorequal_channel_t ch = channel_order[order];
2335 GtkWidget *channel_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2336 g_object_set_data(G_OBJECT(channel_page), "colorequal-channel", GINT_TO_POINTER(ch));
2337 g->area[ring][ch] = GTK_DRAWING_AREA(gtk_drawing_area_new());
2338 gtk_widget_set_hexpand(GTK_WIDGET(g->area[ring][ch]), TRUE);
2339 gtk_widget_add_events(GTK_WIDGET(g->area[ring][ch]),
2340 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
2341 g_object_set_data(G_OBJECT(g->area[ring][ch]), "colorequal-ring", GINT_TO_POINTER(ring));
2342 g_object_set_data(G_OBJECT(g->area[ring][ch]), "colorequal-channel", GINT_TO_POINTER(ch));
2343 g_signal_connect(G_OBJECT(g->area[ring][ch]), "draw", G_CALLBACK(_draw_curve), self);
2344 g_signal_connect(G_OBJECT(g->area[ring][ch]), "motion-notify-event",
2345 G_CALLBACK(_area_motion_notify_callback), self);
2346 g_signal_connect(G_OBJECT(g->area[ring][ch]), "button-press-event", G_CALLBACK(_area_button_press_callback),
2347 self);
2348 g_signal_connect(G_OBJECT(g->area[ring][ch]), "button-release-event",
2349 G_CALLBACK(_area_button_release_callback), self);
2350 // All ring/channel graphs are alternate views of the same plot, so they share one height key.
2351 gtk_box_pack_start(GTK_BOX(channel_page),
2352 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area[ring][ch]),
2353 "plugins/darkroom/colorequal/graphheight", 220, 100),
2354 FALSE, FALSE, 0);
2355 gtk_notebook_append_page(g->channel_notebook[ring], channel_page, gtk_label_new(channel_labels[order]));
2356 gtk_container_child_set(GTK_CONTAINER(g->channel_notebook[ring]), channel_page, "tab-expand", TRUE,
2357 "tab-fill", TRUE, NULL);
2358 }
2359 }
2360
2361 GtkWidget *options_label = gtk_label_new(_("options"));
2362 dt_gui_add_class(options_label, "dt_modulegroups_tab_label");
2363 GtkWidget *options_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2364 gtk_notebook_append_page(g->ring_notebook, options_page, options_label);
2365 gtk_container_child_set(GTK_CONTAINER(g->ring_notebook), options_page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2366
2367 GtkWidget *const module_root = self->widget;
2368 self->widget = options_page;
2369
2370 g->white_level
2372 dt_bauhaus_slider_set_soft_range(g->white_level, -2.f, 2.f);
2373 dt_bauhaus_slider_set_format(g->white_level, _(" EV"));
2374
2375 g->sigma_L = dt_bauhaus_slider_from_params(self, "sigma_L");
2376 dt_bauhaus_slider_set_soft_range(g->sigma_L, 1.f, 100.f);
2377 dt_bauhaus_slider_set_format(g->sigma_L, _(" %"));
2378
2379 g->sigma_rho = dt_bauhaus_slider_from_params(self, "sigma_rho");
2380 dt_bauhaus_slider_set_soft_range(g->sigma_rho, 0.1f, 1.5f);
2381
2382 g->sigma_theta = dt_bauhaus_slider_from_params(self, "sigma_theta");
2383 dt_bauhaus_slider_set_soft_range(g->sigma_theta, 0.05f, 0.8f);
2384
2385 g->neutral_protection = dt_bauhaus_slider_from_params(self, "neutral_protection");
2386 dt_bauhaus_slider_set_soft_range(g->neutral_protection, 0.f, 1.f);
2387
2388 g->interpolation = dt_bauhaus_combobox_from_params(self, "interpolation");
2389 gtk_widget_set_tooltip_text(g->interpolation, _("select the interpolation method"));
2390
2391 self->widget = module_root;
2392
2393 GtkWidget *picker_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2394 gtk_box_pack_start(GTK_BOX(ring_tabs_box), picker_box, FALSE, FALSE, 0);
2395
2396 g->module_picker = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_AREA, NULL, IOP_CS_RGB);
2397 gtk_box_pack_start(GTK_BOX(picker_box), g->module_picker, FALSE, FALSE, 0);
2398
2399 g->picker_info = gtk_label_new(_("no sample"));
2400 gtk_widget_set_hexpand(g->picker_info, TRUE);
2401 gtk_widget_set_halign(g->picker_info, GTK_ALIGN_START);
2402 gtk_label_set_xalign(GTK_LABEL(g->picker_info), 0.f);
2403 gtk_box_pack_start(GTK_BOX(picker_box), g->picker_info, TRUE, TRUE, 0);
2404
2405 const int active_ring = dt_conf_get_int("plugins/darkroom/colorequal/gui_ring_page");
2406 const int active_channel = dt_conf_get_int("plugins/darkroom/colorequal/gui_channel_page");
2407 const int current_ring_page = CLAMP(active_ring, 0, DT_IOP_COLOREQUAL_NUM_RINGS);
2408 const int current_channel_page = CLAMP(active_channel, 0, DT_IOP_COLOREQUAL_NUM_CHANNELS - 1);
2409
2410 ++darktable.gui->reset;
2411 gtk_notebook_set_current_page(g->ring_notebook, current_ring_page);
2412 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2413 gtk_notebook_set_current_page(g->channel_notebook[ring], current_channel_page);
2414 --darktable.gui->reset;
2415
2416 for(int ring = 0; ring < DT_IOP_COLOREQUAL_NUM_RINGS; ring++)
2417 for(int ch = 0; ch < DT_IOP_COLOREQUAL_NUM_CHANNELS; ch++) g->selected[ring][ch] = -1;
2418
2419 g->viewer_lut.clut = NULL;
2420 g->viewer_lut.clut_level = 0;
2421 memset(g->viewer_lut.reference_saturation, 0, sizeof(g->viewer_lut.reference_saturation));
2422 g->viewer_lut.lut_profile = NULL;
2423 g->viewer_lut.work_profile = NULL;
2424 g->viewer_control_node_count = 0;
2425 g->viewer = dt_lut_viewer_new(DT_GUI_MODULE(self));
2426 if(g->viewer)
2427 gtk_box_pack_start(GTK_BOX(GTK_BOX(self->widget)), dt_lut_viewer_get_widget(g->viewer), TRUE, TRUE, 0);
2428
2429 gtk_widget_show_all(self->widget);
2430}
2431
2433{
2434 dt_iop_colorequal_global_data_t *gd = malloc(sizeof(*gd));
2435 module->data = gd;
2436 dt_pthread_rwlock_init(&gd->lock, NULL);
2437 gd->cache.clut = NULL;
2438 gd->cache.clut_level = 0;
2439 gd->cache.white_level = 1.f;
2440 memset(gd->cache.reference_saturation, 0, sizeof(gd->cache.reference_saturation));
2441 gd->cache.lut_profile = NULL;
2442 gd->cache.work_profile = NULL;
2443 memset(&gd->params, 0, sizeof(gd->params));
2444 gd->cache_valid = FALSE;
2445 gd->cache_generation = 0;
2446 gd->kernel_lut3d_tetrahedral = -1;
2447 gd->kernel_lut3d_trilinear = -1;
2448 gd->kernel_lut3d_pyramid = -1;
2449 gd->kernel_exposure = -1;
2450
2451#ifdef HAVE_OPENCL
2452 const int lut_program = 28; // lut3d.cl, from programs.conf
2453 const int basic_program = 2; // basic.cl, from programs.conf
2454 gd->kernel_lut3d_tetrahedral = dt_opencl_create_kernel(lut_program, "lut3d_tetrahedral");
2455 gd->kernel_lut3d_trilinear = dt_opencl_create_kernel(lut_program, "lut3d_trilinear");
2456 gd->kernel_lut3d_pyramid = dt_opencl_create_kernel(lut_program, "lut3d_pyramid");
2457 gd->kernel_exposure = dt_opencl_create_kernel(basic_program, "exposure");
2458#endif
2459}
2460
2475
2477{
2478 dt_iop_default_init(module);
2480 memcpy(module->params, module->default_params, module->params_size);
2481}
2482
2483// clang-format off
2484// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2485// vim: shiftwidth=2 expandtab tabstop=2 cindent
2486// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2487// 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:1647
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
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
#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:749
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:307
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:450
const char ** description(struct dt_iop_module_t *self)
Definition colorequal.c:213
static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
int default_group()
Definition colorequal.c:220
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:266
#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:340
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:457
#define DT_IOP_COLOREQUAL_SCROLL_HUE_STEP_FINE
Definition colorequal.c:82
const char * aliases()
Definition colorequal.c:208
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:384
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:368
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:280
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:539
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:300
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:792
#define DT_IOP_COLOREQUAL_NUM_RINGS
Definition colorequal.c:63
const char * name()
Definition colorequal.c:203
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:419
#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:287
static dt_iop_colorequal_channel_t _channel_from_page(const int page)
Definition colorequal.c:346
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 _cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
static void _invalidate_preview_cursor(dt_iop_colorequal_gui_data_t *g)
Definition colorequal.c:376
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition colorequal.c:230
static void _format_picker_brightness_position(const float brightness, char *text, const size_t size)
int flags()
Definition colorequal.c:225
#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:333
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:294
#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:326
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:806
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:427
static void _init_default_curves(dt_iop_colorequal_params_t *p)
Definition colorequal.c:319
#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:894
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:679
#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:815
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:240
#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:253
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:477
static dt_iop_colorequal_ring_t _active_ring_from_gui(const dt_iop_colorequal_gui_data_t *g)
Definition colorequal.c:360
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:861
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:918
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:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
@ DT_DEBUG_OPENCL
Definition darktable.h:722
@ DT_DEBUG_PERF
Definition darktable.h:719
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:494
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static double dt_get_wtime(void)
Definition darktable.h:914
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#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:281
#define M_PI_F
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
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:1159
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1712
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1144
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:1824
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:1003
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
#define dt_pthread_rwlock_destroy
Definition dtpthread.h:391
#define dt_pthread_rwlock_wrlock
Definition dtpthread.h:394
#define dt_pthread_rwlock_t
Definition dtpthread.h:389
#define dt_pthread_rwlock_init
Definition dtpthread.h:390
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
#define dt_pthread_rwlock_rdlock
Definition dtpthread.h:393
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:2836
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:316
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:3135
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:316
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:3141
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:196
#define IOP_GUI_FREE
Definition imageop.h:602
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_GROUP_COLOR
Definition imageop.h:139
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
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:2136
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
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:2127
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#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:368
@ 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:357
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:790
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
int32_t unmuted
Definition darktable.h:760
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_control_t * control
Definition darktable.h:773
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
int32_t reset
Definition gtk.h:172
dt_ui_t * ui
Definition gtk.h:164
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_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 cache
Definition colorequal.c:141
dt_iop_colorequal_params_t params
Definition colorequal.c:142
dt_iop_colorequal_data_t viewer_lut
Definition colorequal.c:165
dt_lut_viewer_t * viewer
Definition colorequal.c:164
dt_lut_viewer_control_node_t viewer_control_nodes[(3 *64)]
Definition colorequal.c:191
dt_iop_colorequal_params_t gui_params
Definition colorequal.c:167
dt_aligned_pixel_t cursor_input_display
Definition colorequal.c:189
dt_aligned_pixel_t cursor_output_display
Definition colorequal.c:190
GtkNotebook * channel_notebook[3]
Definition colorequal.c:155
dt_dev_pixelpipe_cache_wait_t preview_wait
Definition colorequal.c:179
cairo_surface_t * background_surface[3][3]
Definition colorequal.c:200
GtkDrawingArea * area[3][3]
Definition colorequal.c:153
dt_draw_curve_t * curve[3][3]
Definition colorequal.c:169
const dt_iop_order_iccprofile_info_t * cached_display_profile[3][3]
Definition colorequal.c:199
float cached_reference_saturation[3][3]
Definition colorequal.c:195
dt_iop_colorequal_params_t cached_curve_params
Definition colorequal.c:168
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:233
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:264
GtkDarktableToggleButton * off
Definition imageop.h:339
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
gboolean enabled
Definition imageop.h:298
dt_aligned_pixel_t picked_color_min
Definition imageop.h:272
dt_aligned_pixel_t picked_color_max
Definition imageop.h:272
int32_t params_size
Definition imageop.h:309
dt_aligned_pixel_t picked_color
Definition imageop.h:272
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72