Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
color_picker_proxy.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2018-2021 Pascal Obry.
4 Copyright (C) 2019-2021 Diederik Ter Rahe.
5 Copyright (C) 2019 Edgardo Hoszowski.
6 Copyright (C) 2019 Ulrich Pegelow.
7 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
8 Copyright (C) 2020 Hubert Kowalski.
9 Copyright (C) 2020 Marco.
10 Copyright (C) 2021 Dan Torop.
11 Copyright (C) 2021 Paolo DePetrillo.
12 Copyright (C) 2021 Ralf Brown.
13 Copyright (C) 2022 Aldric Renaudin.
14 Copyright (C) 2022 Martin Bařinka.
15 Copyright (C) 2022 Philipp Lutz.
16 Copyright (C) 2023 Alynx Zhou.
17 Copyright (C) 2023, 2025-2026 Aurélien PIERRE.
18
19 darktable is free software: you can redistribute it and/or modify
20 it under the terms of the GNU Lesser General Public License as published by
21 the Free Software Foundation, either version 3 of the License, or
22 (at your option) any later version.
23
24 darktable is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU Lesser General Public License for more details.
28
29 You should have received a copy of the GNU Lesser General Public License
30 along with darktable. If not, see <http://www.gnu.org/licenses/>.
31*/
33#include "develop/iop_profile.h"
34#include "widgets/bauhaus.h"
36#include "common/color_picker.h"
37#include "control/signal.h"
38#include "control/redraw.h"
41#include "libs/colorpicker.h"
42#include "libs/lib.h"
43
44#include <inttypes.h>
45#include <math.h>
46#include <string.h>
48
49/*
50 The color_picker_proxy code is an interface which links the UI
51 colorpicker buttons in iops (and the colorpicker lib) with the rest
52 of the implementation (selecting/drawing colorpicker area in center
53 view, reading color value from preview pipe, and displaying results
54 in the colorpicker lib).
55
56 From the iop (or lib) POV, all that is necessary is to instantiate
57 color picker(s) via dt_color_picker_new() or
58 dt_color_picker_new_with_cst(), then subscribe to
59 DT_SIGNAL_CONTROL_PICKERDATA_READY and resolve the current ready sample
60 through dt_iop_color_picker_get_ready_data().
61
62 This code will initialize new pickers with a default area, then
63 remember the last area of the picker and use that when the picker is
64 reactivated.
65
66 The actual work of cache lookup and sampling happens here on the GUI
67 thread. The drawing & mouse-sensitivity of the picker overlay in the
68 center view happens in darkroom.c. The display of current sample
69 values occurs via libs/colorpicker.c, which uses this code to
70 activate its own picker.
71
72 The sample position is potentially stored in two places:
73
74 1. For each sampler widget, in dt_iop_color_picker_t.
75 2. For the active iop, the primary, and the live samples in
76 dt_colorpicker_sample_t.
77
78 There will be at most one editable sample, with one active picker, at
79 one time in the center view.
80*/
81
82
83// FIXME: should this be here or perhaps lib.c?
85{
86 const gboolean module_picker = dev->gui_module
87 && dev->gui_module->enabled
88 && dev->color_picker.enabled
89 && dev->color_picker.module == dev->gui_module;
90
91 const gboolean primary_picker = dev && dev->color_picker.enabled && !dev->color_picker.module;
92
93 return module_picker || primary_picker;
94}
95
97{
98 return module && module->dev
99 && module->dev->color_picker.enabled
100 && module->dev->color_picker.module == module;
101}
102
116{
117 dt_develop_t *const dev = dt_dev_get_global();
118 const dt_colorpicker_sample_t *const sample = dev ? dev->color_picker.primary_sample : NULL;
119 gboolean changed = FALSE;
120 if(self && sample)
121 {
123 {
124 for(int k = 0; k < 2; k++)
125 {
126 if(self->pick_pos[k] != sample->point[k])
127 {
128 self->pick_pos[k] = sample->point[k];
129 changed = TRUE;
130 }
131 }
132 self->geometry_is_raw = TRUE;
133 }
134 else if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
135 {
136 for(int k = 0; k < 4; k++)
137 {
138 if(self->pick_box[k] != sample->box[k])
139 {
140 self->pick_box[k] = sample->box[k];
141 changed = TRUE;
142 }
143 }
144 self->geometry_is_raw = TRUE;
145 }
146 }
147 return changed;
148}
149
155
162
163static void _refresh_active_picker(dt_develop_t *dev);
165static void _restart_picker_cache_wait(gpointer user_data);
166
167static inline void _picker_raw_point_to_image_norm(const dt_develop_t *dev, const float raw_point[2],
168 float image_point[2])
169{
170 image_point[0] = raw_point[0];
171 image_point[1] = raw_point[1];
173}
174
175static inline void _picker_raw_box_to_image_norm(const dt_develop_t *dev, const float raw_box[4],
176 float image_box[4])
177{
178 memcpy(image_box, raw_box, sizeof(float) * 4);
180}
181
183 const dt_iop_module_t *active_module,
184 float bounds[4])
185{
186 bounds[0] = 0.0f;
187 bounds[1] = 0.0f;
188 bounds[2] = 1.0f;
189 bounds[3] = 1.0f;
190
191 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe) || IS_NULL_PTR(active_module)) return;
193 const float processed_width = geometry.processed_width;
194 const float processed_height = geometry.processed_height;
195 if(processed_width <= 0.0f || processed_height <= 0.0f) return;
196
198 (dt_iop_module_t *)active_module);
199 if(IS_NULL_PTR(piece)) return;
200
201 float quad[8] = {
202 0.0f, 0.0f,
203 (float)piece->buf_out.width, 0.0f,
204 (float)piece->buf_out.width, (float)piece->buf_out.height,
205 0.0f, (float)piece->buf_out.height
206 };
209
210 float min_x = fminf(fminf(quad[0], quad[2]), fminf(quad[4], quad[6]));
211 float min_y = fminf(fminf(quad[1], quad[3]), fminf(quad[5], quad[7]));
212 float max_x = fmaxf(fmaxf(quad[0], quad[2]), fmaxf(quad[4], quad[6]));
213 float max_y = fmaxf(fmaxf(quad[1], quad[3]), fmaxf(quad[5], quad[7]));
214
215 min_x = CLAMP(min_x / processed_width, 0.0f, 1.0f);
216 min_y = CLAMP(min_y / processed_height, 0.0f, 1.0f);
217 max_x = CLAMP(max_x / processed_width, 0.0f, 1.0f);
218 max_y = CLAMP(max_y / processed_height, 0.0f, 1.0f);
219 bounds[0] = fminf(min_x, max_x);
220 bounds[1] = fminf(min_y, max_y);
221 bounds[2] = fmaxf(min_x, max_x);
222 bounds[3] = fmaxf(min_y, max_y);
223}
224
226{
227 if(IS_NULL_PTR(picker) || IS_NULL_PTR(dev) || picker->geometry_is_raw) return;
228
229 float bounds[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
230 _picker_get_module_bounds_image_norm(dev, picker->module, bounds);
231
233 const float processed_width = geometry.processed_width;
234 const float processed_height = geometry.processed_height;
235 if(processed_width <= 0.0f || processed_height <= 0.0f) return;
236 // Fixed border inset in scale-1 image pixels, then converted to image-norm.
237 // Keep this explicit here so the caller directly controls default picker coverage.
238 const float inset_pixels = 64.0f;
239 const float inset_x = inset_pixels / processed_width;
240 const float inset_y = inset_pixels / processed_height;
241 const float width = fmaxf(bounds[2] - bounds[0], 0.0f);
242 const float height = fmaxf(bounds[3] - bounds[1], 0.0f);
243 picker->pick_pos[0] = 0.5f * (bounds[0] + bounds[2]);
244 picker->pick_pos[1] = 0.5f * (bounds[1] + bounds[3]);
245 picker->pick_box[0] = bounds[0] + fminf(inset_x, 0.5f * width);
246 picker->pick_box[1] = bounds[1] + fminf(inset_y, 0.5f * height);
247 picker->pick_box[2] = bounds[2] - fminf(inset_x, 0.5f * width);
248 picker->pick_box[3] = bounds[3] - fminf(inset_y, 0.5f * height);
249
250 picker->pick_pos[0] = CLAMP(picker->pick_pos[0], bounds[0], bounds[2]);
251 picker->pick_pos[1] = CLAMP(picker->pick_pos[1], bounds[1], bounds[3]);
252 picker->pick_box[0] = CLAMP(picker->pick_box[0], bounds[0], bounds[2]);
253 picker->pick_box[1] = CLAMP(picker->pick_box[1], bounds[1], bounds[3]);
254 picker->pick_box[2] = CLAMP(picker->pick_box[2], bounds[0], bounds[2]);
255 picker->pick_box[3] = CLAMP(picker->pick_box[3], bounds[1], bounds[3]);
256 if(picker->pick_box[0] > picker->pick_box[2])
257 {
258 const float center = 0.5f * (picker->pick_box[0] + picker->pick_box[2]);
259 picker->pick_box[0] = center;
260 picker->pick_box[2] = center;
261 }
262 if(picker->pick_box[1] > picker->pick_box[3])
263 {
264 const float center = 0.5f * (picker->pick_box[1] + picker->pick_box[3]);
265 picker->pick_box[1] = center;
266 picker->pick_box[3] = center;
267 }
268
271 picker->geometry_is_raw = TRUE;
272}
273
276{
277 dt_develop_t *const dev = dt_dev_get_global();
278 const dt_colorpicker_sample_t *const sample = dev ? dev->color_picker.primary_sample : NULL;
279 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module) || IS_NULL_PTR(roi) || IS_NULL_PTR(sample)) return 1;
280
281 dt_boundingbox_t fbox = { 0.0f };
282
283 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
284 {
287 }
288 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
289 {
292 fbox[2] = fbox[0];
293 fbox[3] = fbox[1];
294 }
295
300 fbox, 2);
301
302 const float roi_scale = roi->scale;
303 if(roi_scale != 1.0f)
304 {
305 fbox[0] *= roi_scale;
306 fbox[1] *= roi_scale;
307 fbox[2] *= roi_scale;
308 fbox[3] *= roi_scale;
309 }
310
311 fbox[0] -= roi->x;
312 fbox[1] -= roi->y;
313 fbox[2] -= roi->x;
314 fbox[3] -= roi->y;
315
316 box[0] = fminf(fbox[0], fbox[2]);
317 box[1] = fminf(fbox[1], fbox[3]);
318 box[2] = fmaxf(fbox[0], fbox[2]);
319 box[3] = fmaxf(fbox[1], fbox[3]);
320
322 {
323 box[2] += 1;
324 box[3] += 1;
325 }
326
327 if(box[0] >= roi->width || box[1] >= roi->height || box[2] < 0 || box[3] < 0) return 1;
328
329 box[0] = MIN(roi->width - 1, MAX(0, box[0]));
330 box[1] = MIN(roi->height - 1, MAX(0, box[1]));
331 box[2] = MIN(roi->width - 1, MAX(0, box[2]));
332 box[3] = MIN(roi->height - 1, MAX(0, box[3]));
333
334 if(box[2] <= box[0] || box[3] <= box[1]) return 1;
335
336 return 0;
337}
338
340 const dt_iop_buffer_dsc_t *dsc, const dt_iop_roi_t *roi,
341 const float *pixel, dt_aligned_pixel_t avg_out,
344{
345 int box[4];
346 if(_picker_sample_box(module, roi, picker_source, box)) return FALSE;
347
348 dt_aligned_pixel_t avg = { 0.0f };
351
354 dt_color_picker_helper(dsc, pixel, roi, box, avg, min, max, dsc->cst, picker_cst, profile);
355
356 for(int k = 0; k < 4; k++)
357 {
358 avg_out[k] = avg[k];
359 min_out[k] = min[k];
360 max_out[k] = max[k];
361 }
362
363 return TRUE;
364}
365
367 dt_dev_pixelpipe_t **pipe,
368 const dt_dev_pixelpipe_iop_t **piece)
369{
370 dt_develop_t *const dev = dt_dev_get_global();
372 return 1;
373
376
377 /* We walk the current preview graph and look for the current piece matching the sampled hash.
378 Piece pointers cannot cross the signal boundary because the pipe may have been rebuilt. */
380 {
381 const dt_dev_pixelpipe_iop_t *const current = pieces->data;
382 if(current->module == module && current->global_hash == dev->color_picker.piece_hash)
383 {
384 current_piece = current;
385 break;
386 }
387 }
388
390 {
392 "[picker] ready-data miss module=%s pending_hash=%" PRIu64 " pipe=%p\n",
393 module->op, dev->color_picker.piece_hash, (void *)current_pipe);
394 return 1;
395 }
396
397 if(picker) *picker = dev->color_picker.widget;
398 if(pipe) *pipe = current_pipe;
399 if(piece) *piece = current_piece;
401 "[picker] ready-data module=%s hash=%" PRIu64 " pipe=%p picker=%p\n",
402 module->op, current_piece->global_hash, (void *)current_pipe, (void *)dev->color_picker.widget);
403 return 0;
404}
405
407{
409 || !dev->color_picker.module || !dev->gui_module || dev->color_picker.module != dev->gui_module
410 || !dev->gui_module->enabled)
411 {
413 "[picker] guard bailout picker=%p enabled=%d picker_module=%s gui_module=%s "
414 "same_module=%d gui_module_enabled=%d\n",
415 (void *)(dev ? dev->color_picker.picker : NULL),
416 dev ? dev->color_picker.enabled : -1,
417 (dev && dev->color_picker.module) ? dev->color_picker.module->op : "-",
418 (dev && dev->gui_module) ? dev->gui_module->op : "-",
419 (dev && dev->color_picker.module && dev->gui_module)
420 ? (dev->color_picker.module == dev->gui_module)
421 : -1,
422 (dev && dev->gui_module) ? dev->gui_module->enabled : -1);
424 }
425
426 dt_dev_pixelpipe_t *const pipe = dev->preview_pipe;
431 {
432 dt_print(DT_DEBUG_DEV, "[picker] sample retry module=%s piece=%p prev=%p\n",
433 dev->color_picker.module ? dev->color_picker.module->op : "-", (void *)piece, (void *)previous_piece);
435 }
436
439 {
441 "[picker] invalid hash module=%s piece=%" PRIu64 " prev=%" PRIu64 "\n",
442 piece->module->op, piece->global_hash, previous_piece->global_hash);
444 }
445
446 /* Check the module's own OUTPUT before its input. Computing a module's output requires computing
447 * its input first (process_rec always recurses upstream before producing the requesting node), so
448 * requesting the output as the MODULE cache target makes a single recompute satisfy both this and
449 * the input wait below. Checking input first (as before) would bail out on an input miss without
450 * ever touching output, costing a full separate recompute round-trip for output afterwards even
451 * though computing it would have produced the input as a side effect anyway -- the common case
452 * right after focusing a module whose input/output were never sampled on this image (e.g. right
453 * after loading, or a module that stayed collapsed since darkroom opened). Output is skipped here
454 * only when it can structurally never resolve (bypass_cache/no_cache/realtime), since then nothing
455 * would ever recurse through for us and we must fall back to requesting the input on its own. */
456 void *output = NULL;
458 const gboolean output_cache_blocked_by_policy
459 = piece->bypass_cache || pipe->bypass_cache || pipe->no_cache || dt_dev_pixelpipe_get_realtime(pipe);
460 dt_dev_pixelpipe_cache_wait_set_owner(&dev->color_picker.output_wait, "color-picker-output", dev->color_picker.module);
461 const gboolean have_output = dt_dev_pixelpipe_cache_peek_gui(pipe, piece, &output, &output_entry,
465 {
468 "[picker] output cache miss module=%s hash=%" PRIu64 " (recompute will also satisfy input)\n",
469 piece->module->op, piece->global_hash);
471 }
472
473 void *input = NULL;
475 dt_dev_pixelpipe_cache_wait_set_owner(&dev->color_picker.input_wait, "color-picker-input", dev->color_picker.module);
478 {
479 dev->color_picker.wait_input_hash = previous_piece->global_hash;
480 dt_print(DT_DEBUG_DEV, "[picker] input cache miss module=%s prev_hash=%" PRIu64 "\n",
481 piece->module->op, previous_piece->global_hash);
483 }
484
485 if(!have_output)
486 {
487 /* Only reachable when output_cache_blocked_by_policy: module GUIs such as color equalizer only
488 consume the module-input sample, and some pipe modes (bypass_cache/no_cache/realtime) can never
489 publish a module output cacheline at all. Keep output statistics explicitly invalid so
490 output-dependent consumers can detect the missing sample, but still publish the ready input
491 sample instead of blocking picker feedback forever. */
492 dt_print(DT_DEBUG_DEV, "[picker] output cache blocked by policy module=%s hash=%" PRIu64 "\n",
493 piece->module->op, piece->global_hash);
495
496 for(int k = 0; k < 4; k++)
497 {
498 piece->module->picked_output_color[k] = 0.0f;
499 piece->module->picked_output_color_min[k] = 666.0f;
500 piece->module->picked_output_color_max[k] = -666.0f;
501 }
502 }
503
504 if(previous_piece->dsc_out.datatype != TYPE_FLOAT || (have_output && piece->dsc_out.datatype != TYPE_FLOAT))
505 {
507 "[picker] non-float buffers module=%s input_type=%d output_type=%d\n",
508 piece->module->op, previous_piece->dsc_out.datatype, piece->dsc_out.datatype);
510 }
511
512 /* Unlike histogram/global backbuffers, module color-pickers do not publish a dedicated long-lived buffer.
513 * They reopen the current module input/output cachelines by immutable `global_hash`, then take a temporary
514 * ref plus read lock only for the duration of the sampling pass so concurrent cache recycling cannot free
515 * the payload mid-read. */
518 if(have_output)
519 {
522 }
523
524 const gboolean sampled_input
525 = _sample_picker_buffer(pipe, piece->module, &previous_piece->dsc_out, &previous_piece->roi_out,
526 input, piece->module->picked_color, piece->module->picked_color_min,
527 piece->module->picked_color_max, PIXELPIPE_PICKER_INPUT);
528 const gboolean sampled_output
530 ? _sample_picker_buffer(pipe, piece->module, &piece->dsc_out, &piece->roi_out, output,
531 piece->module->picked_output_color, piece->module->picked_output_color_min,
532 piece->module->picked_output_color_max, PIXELPIPE_PICKER_OUTPUT)
533 : FALSE;
534
536 {
537 // Keep GUI picker feedback alive whenever module output is temporarily
538 // unavailable: mirror input sample statistics to output slots. If output
539 // cache becomes available later, subsequent refreshes overwrite these with
540 // true output samples.
541 for(int k = 0; k < 4; k++)
542 {
543 piece->module->picked_output_color[k] = piece->module->picked_color[k];
544 piece->module->picked_output_color_min[k] = piece->module->picked_color_min[k];
545 piece->module->picked_output_color_max[k] = piece->module->picked_color_max[k];
546 }
547 }
548
549 if(have_output)
550 {
553 }
556
557 if(!sampled_input)
558 {
559 dt_print(DT_DEBUG_DEV, "[picker] sample failed module=%s input=%d output=%d\n",
560 piece->module->op, sampled_input, sampled_output);
561 dev->color_picker.picker->update_pending = FALSE;
564 }
565
567 "[picker] sampled module=%s hash=%" PRIu64 " avg=(%g,%g,%g) min=(%g,%g,%g) max=(%g,%g,%g)\n",
568 piece->module->op, piece->global_hash,
569 piece->module->picked_color[0], piece->module->picked_color[1], piece->module->picked_color[2],
570 piece->module->picked_color_min[0], piece->module->picked_color_min[1], piece->module->picked_color_min[2],
571 piece->module->picked_color_max[0], piece->module->picked_color_max[1], piece->module->picked_color_max[2]);
572
573 dev->color_picker.piece_hash = piece->global_hash;
574 dev->color_picker.pending_module = piece->module;
575 dev->color_picker.pending_pipe = pipe;
578 dev->color_picker.picker->update_pending = FALSE;
580
586}
587
588static gboolean _refresh_active_picker_idle(gpointer user_data)
589{
590 dt_develop_t *const dev = (dt_develop_t *)user_data;
591 if(dev) dev->color_picker.refresh_idle_source = 0;
593 return G_SOURCE_REMOVE;
594}
595
602
603static void _restart_picker_cache_wait(gpointer user_data)
604{
605 dt_develop_t *const dev = (dt_develop_t *)user_data;
607}
608
610{
611 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->color_picker.picker) || !dev->color_picker.enabled) return;
612 if(!dev->color_picker.picker->update_pending && !dev->color_picker.update_pending) return;
613
615 "[picker] refresh module=%s update_pending=%d widget_pending=%d processing=%d\n",
616 dev->color_picker.module ? dev->color_picker.module->op : "-",
617 dev->color_picker.update_pending, dev->color_picker.picker->update_pending,
618 dev->preview_pipe ? dev->preview_pipe->processing : -1);
619
621
622 /* A picker update is satisfied either from the current preview cache or from the next completed
623 preview run. If preview is already processing, re-dirtying it here only feeds TOP_CHANGED
624 loops and prevents the current run from ever publishing the cacheline we are waiting for. */
626 {
627 // Make sure CACHELINE_READY can wake us as soon as this in-flight run
628 // publishes input/output cachelines for the active picker.
630 return;
631 }
632
633 if(IS_NULL_PTR(dev->color_picker.module))
634 {
635 /* The global picker already samples from histogram backbuffers published by preview updates.
636 Refresh it directly from that GUI-owned cache when possible so dragging the picker updates
637 the histogram labels immediately without waiting for another preview completion. */
641 {
642 dev->color_picker.picker->update_pending = FALSE;
645 return;
646 }
647 }
648
649 if(dev->preview_pipe)
650 {
653 return;
654 }
655}
656
658{
659 if(picker)
660 {
661 if(picker->module) dt_iop_set_cache_bypass(picker->module, FALSE);
662
664
667 else
669
671 }
672}
673
675{
676 (void)widget;
677 dt_develop_t *const dev = dt_dev_get_global();
678 if(IS_NULL_PTR(dev) || dev->color_picker.picker != picker) return;
679
680 // Mirror dt_iop_color_picker_reset()'s cleanup: the picker's own button widget is the one being
681 // destroyed here (e.g. a module GUI panel rebuild), so there is no widget left to toggle off, but
682 // every other piece of dev-wide picker state must still be cleared the same way. Leaving
683 // dev->color_picker.enabled/.module stale keeps dt_iop_color_picker_is_visible() reporting a
684 // picker "active" for a module whose picker widget no longer exists -- observed as the custom
685 // cursor in iop/toneequal.c getting stuck, fixed only by fully reloading the image (issue #1028).
686 dt_iop_module_t *const module = dev->color_picker.module;
687
688 dt_dev_pixelpipe_cache_wait_cleanup(&dev->color_picker.input_wait, "picker-widget-destroyed-input");
689 dt_dev_pixelpipe_cache_wait_cleanup(&dev->color_picker.output_wait, "picker-widget-destroyed-output");
690
691 dev->color_picker.picker = NULL;
692 dev->color_picker.widget = NULL;
693 dev->color_picker.module = NULL;
698 if(module) module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
699}
700
702{
703 dt_develop_t *const dev = dt_dev_get_global();
704 dt_iop_color_picker_t *picker = dev ? dev->color_picker.picker : NULL;
705 if(picker && picker->module == module)
706 {
707 if(!keep)
708 {
711 _color_picker_reset(picker);
712 dev->color_picker.picker = NULL;
713 dev->color_picker.widget = NULL;
714 dev->color_picker.module = NULL;
719 if(module) module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
720 }
721 }
722}
723
726{
727 // module is NULL if primary colorpicker
728 picker->module = module;
729 picker->kind = kind;
730 picker->picker_cst = module ? module->default_colorspace(module, NULL, NULL) : IOP_CS_NONE;
731 picker->colorpick = button;
732 picker->update_pending = FALSE;
733 picker->geometry_is_raw = FALSE;
734
735 // default values
736 const float middle = 0.5f;
737 const float area = 0.975f;
738 picker->pick_pos[0] = picker->pick_pos[1] = middle;
739 picker->pick_box[0] = (1.0f - area);
740 picker->pick_box[1] = (1.0f - area);
741 picker->pick_box[2] = area;
742 picker->pick_box[3] = area;
743
744 _color_picker_reset(picker);
745}
746
748{
749 // module is NULL if primary colorpicker
750 dt_iop_module_t *module = self->module;
751 dt_develop_t *const dev = dt_dev_get_global();
752
754 {
755 dt_print(DT_DEBUG_DEV, "[picker] click ignored: widgets suppressed module=%s\n",
756 module ? module->op : "global");
757 return FALSE;
758 }
759
760 dt_iop_color_picker_t *prior_picker = dev ? dev->color_picker.picker : NULL;
761 if(prior_picker && prior_picker != self)
762 {
764 if(prior_picker->module) prior_picker->module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
765 }
766
769
770 const GdkModifierType state = !IS_NULL_PTR(e) ? e->state : dt_key_modifier_state();
771 const gboolean ctrl_key_pressed = dt_modifier_is(state, DT_PRIMARY_MASK) || (!IS_NULL_PTR(e) && e->button == 3);
773
774 if(prior_picker != self || (kind == DT_COLOR_PICKER_POINT_AREA &&
775 (ctrl_key_pressed ^ (dev->color_picker.primary_sample->size == DT_LIB_COLORPICKER_SIZE_BOX))))
776 {
777 dev->color_picker.picker = self;
778 dev->color_picker.widget = self->colorpick;
779 dev->color_picker.module = module;
780 dev->color_picker.kind = kind;
781 dev->color_picker.picker_cst = self->picker_cst;
782 dev->color_picker.enabled = TRUE;
783
784 if(module) module->request_color_pick = DT_REQUEST_COLORPICK_MODULE;
785
787 {
789 }
791
793 {
794 dt_boundingbox_t image_box = { 0.0f };
797 }
798 else if(kind == DT_COLOR_PICKER_POINT)
799 {
800 float image_point[2] = { 0.0f };
803 }
804 else
806
807 // important to have set up state before toggling button and
808 // triggering more callbacks
812 else
815
816 dt_iop_module_t *const gui_module_before_focus = dev->gui_module;
817 if(module)
819
821 "[picker] activate module=%s picker=%p widget=%p kind=%d cst=%d gui_module_before=%s "
822 "gui_module_after=%s widgets_suppressed=%d\n",
823 module ? module->op : "global", (void *)self, (void *)self->colorpick, kind, self->picker_cst,
825 dev->gui_module ? dev->gui_module->op : "-", dt_gui_widgets_suppressed());
826
827 }
828 else
829 {
830 dt_dev_pixelpipe_cache_wait_cleanup(&dev->color_picker.input_wait, "picker-deactivate-input");
831 dt_dev_pixelpipe_cache_wait_cleanup(&dev->color_picker.output_wait, "picker-deactivate-output");
832 dev->color_picker.picker = NULL;
833 dev->color_picker.widget = NULL;
834 dev->color_picker.module = NULL;
835 dev->color_picker.kind = DT_COLOR_PICKER_POINT;
836 dev->color_picker.picker_cst = IOP_CS_NONE;
837 dev->color_picker.enabled = FALSE;
838 dev->color_picker.update_pending = FALSE;
840 if(module)
841 {
842 module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
843 }
844 dt_print(DT_DEBUG_DEV, "[picker] deactivate module=%s picker=%p widget=%p\n",
845 module ? module->op : "global", (void *)self, (void *)self->colorpick);
846 }
847
848 // Draw picker geometry immediately; data sampling update can complete later.
850 if(dev->color_picker.enabled)
852
853 return TRUE;
854}
855
860
862{
863 dt_develop_t *const dev = dt_dev_get_global();
864 dt_iop_color_picker_t *const picker = dev ? dev->color_picker.picker : NULL;
865 if(picker && picker->module == module && picker->picker_cst != picker_cst)
866 {
867 picker->picker_cst = picker_cst;
869 }
870}
871
873{
874 dt_develop_t *const dev = dt_dev_get_global();
875 dt_iop_color_picker_t *picker = dev ? dev->color_picker.picker : NULL;
876 if(picker && picker->module == module)
877 return picker->picker_cst;
878 else
879 return IOP_CS_NONE;
880}
881
883{
884 dt_develop_t *const dev = dt_dev_get_global();
885 dt_iop_color_picker_t *picker = dev ? dev->color_picker.picker : NULL;
886 if(picker) picker->update_pending = TRUE;
887 if(dev)
888 {
890 }
891 if(dev)
892 dt_print(DT_DEBUG_DEV, "[picker] request update module=%s picker=%p widget=%p\n",
893 dev->color_picker.module ? dev->color_picker.module->op : "-",
894 (void *)dev->color_picker.picker, (void *)dev->color_picker.widget);
896}
897
899{
900 if(IS_NULL_PTR(dev))
901 return;
902
905
907 || !dev->color_picker.module || !dev->gui_module || dev->color_picker.module != dev->gui_module
908 || !dev->gui_module->enabled)
909 return;
910
912 dev->color_picker.module);
915 if(piece) dev->color_picker.wait_output_hash = piece->global_hash;
917}
918
919static void _iop_color_picker_history_resync_callback(gpointer instance, gpointer user_data)
920{
921 (void)instance;
922 (void)user_data;
923 dt_develop_t *const dev = dt_dev_get_global();
926}
927
928static void _iop_color_picker_cacheline_ready_callback(gpointer instance, const guint64 hash,
929 const guint64 producer_node_key, gpointer user_data)
930{
931 (void)instance;
932 (void)producer_node_key; // the shared cache-wait manager serves the picker's input/output
933 // waits by producer node; this direct path stays exact-hash only.
934 (void)user_data;
935
936 dt_develop_t *const dev = dt_dev_get_global();
937 if(IS_NULL_PTR(dev)) return;
938
939 gboolean matched = FALSE;
940 if(dev->color_picker.wait_input_hash == hash)
941 {
943 matched = TRUE;
944 }
945 if(dev->color_picker.wait_output_hash == hash)
946 {
948 matched = TRUE;
949 }
950
952}
953
973static void _iop_color_picker_pipe_finished_callback(gpointer instance, gpointer user_data)
974{
975 (void)instance;
976 (void)user_data;
977 dt_develop_t *const dev = dt_dev_get_global();
978 if(IS_NULL_PTR(dev)) return;
980}
981
990static void _iop_color_picker_notebook_tab_changed_callback(gpointer instance, gpointer owner, gpointer user_data)
991{
992 (void)instance;
993 (void)user_data;
994 if(IS_NULL_PTR(owner)) return;
996}
997
1009
1021
1023 const gboolean init_cst, const dt_iop_colorspace_type_t cst)
1024{
1026
1027 if(IS_NULL_PTR(w) || GTK_IS_BOX(w))
1028 {
1030 _init_picker(color_picker, module, kind, button);
1031 if(init_cst)
1032 color_picker->picker_cst = cst;
1033 g_signal_connect_data(G_OBJECT(button), "button-press-event",
1035 g_signal_connect(G_OBJECT(button), "destroy", G_CALLBACK(_color_picker_widget_destroy), color_picker);
1036 if(w) gtk_box_pack_start(GTK_BOX(w), button, FALSE, FALSE, 0);
1037
1038 dt_develop_t *const dev = dt_dev_get_global();
1039 if(dev && dev->color_picker.enabled && dev->color_picker.module == module
1041 && dev->color_picker.kind == kind
1042 && dev->color_picker.picker_cst == color_picker->picker_cst)
1043 {
1044 dev->color_picker.picker = color_picker;
1045 dev->color_picker.widget = button;
1046
1050 }
1051
1052 return button;
1053 }
1054 else
1055 {
1058 _init_picker(color_picker, module, kind, w);
1059 if(init_cst)
1060 color_picker->picker_cst = cst;
1061 g_signal_connect_data(G_OBJECT(w), "quad-pressed",
1064
1065 dt_develop_t *const dev = dt_dev_get_global();
1066 if(dev && dev->color_picker.enabled && dev->color_picker.module == module
1068 && dev->color_picker.kind == kind
1069 && dev->color_picker.picker_cst == color_picker->picker_cst)
1070 {
1071 dev->color_picker.picker = color_picker;
1072 dev->color_picker.widget = w;
1073
1077 }
1078
1079 return w;
1080 }
1081}
1082
1087
1093
1094// clang-format off
1095// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1096// vim: shiftwidth=2 expandtab tabstop=2 cindent
1097// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1098// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1571
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1577
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1553
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const dt_adaptation_t kind
dt_iop_colorspace_type_t
@ IOP_CS_NONE
void dt_color_picker_helper(const dt_iop_buffer_dsc_t *dsc, const float *const pixel, const dt_iop_roi_t *roi, const int *const box, dt_aligned_pixel_t picked_color, dt_aligned_pixel_t picked_color_min, dt_aligned_pixel_t picked_color_max, const dt_iop_colorspace_type_t image_cst, const dt_iop_colorspace_type_t picker_cst, const dt_iop_order_iccprofile_info_t *const profile)
static void _restart_picker_cache_wait(gpointer user_data)
static void _iop_color_picker_cacheline_ready_callback(gpointer instance, const guint64 hash, const guint64 producer_node_key, gpointer user_data)
static void _picker_raw_box_to_image_norm(const dt_develop_t *dev, const float raw_box[4], float image_box[4])
static void _color_picker_widget_destroy(GtkWidget *widget, dt_iop_color_picker_t *picker)
static void _queue_refresh_active_picker(dt_develop_t *dev)
void dt_iop_color_picker_init(void)
static void _refresh_active_picker(dt_develop_t *dev)
static int _picker_sample_box(const dt_iop_module_t *module, const dt_iop_roi_t *roi, const dt_pixelpipe_picker_source_t picker_source, int *box)
void dt_iop_color_picker_cleanup(void)
static void _iop_color_picker_notebook_tab_changed_callback(gpointer instance, gpointer owner, gpointer user_data)
static gboolean _record_point_area(dt_iop_color_picker_t *self)
Synchronize one picker cached geometry with the primary sample.
static gboolean _sample_picker_buffer(dt_dev_pixelpipe_t *pipe, dt_iop_module_t *module, const dt_iop_buffer_dsc_t *dsc, const dt_iop_roi_t *roi, const float *pixel, dt_aligned_pixel_t avg_out, dt_aligned_pixel_t min_out, dt_aligned_pixel_t max_out, const dt_pixelpipe_picker_source_t picker_source)
static void _iop_color_picker_pipe_finished_callback(gpointer instance, gpointer user_data)
void dt_iop_color_picker_request_update(void)
static void _picker_get_module_bounds_image_norm(const dt_develop_t *dev, const dt_iop_module_t *active_module, float bounds[4])
static void _track_active_picker_hashes(dt_develop_t *dev)
static gboolean _refresh_active_picker_idle(gpointer user_data)
static void _iop_color_picker_history_resync_callback(gpointer instance, gpointer user_data)
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
int dt_iop_color_picker_get_ready_data(const dt_iop_module_t *module, GtkWidget **picker, dt_dev_pixelpipe_t **pipe, const dt_dev_pixelpipe_iop_t **piece)
dt_pixelpipe_picker_source_t
@ PIXELPIPE_PICKER_INPUT
@ PIXELPIPE_PICKER_OUTPUT
static gboolean _color_picker_callback_button_press(GtkWidget *button, GdkEventButton *e, dt_iop_color_picker_t *self)
void dt_iop_color_picker_set_cst(dt_iop_module_t *module, const dt_iop_colorspace_type_t picker_cst)
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)
static void _color_picker_reset(dt_iop_color_picker_t *picker)
static dt_color_picker_resample_status_t _sample_picker_from_cache(dt_develop_t *dev)
gboolean dt_iop_color_picker_is_active_module(const dt_iop_module_t *module)
Tell whether one module currently owns the active darkroom picker.
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
static void _picker_raw_point_to_image_norm(const dt_develop_t *dev, const float raw_point[2], float image_point[2])
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
static void _color_picker_callback(GtkWidget *button, dt_iop_color_picker_t *self)
dt_color_picker_resample_status_t
@ DT_COLOR_PICKER_RESAMPLE_RETRY
@ DT_COLOR_PICKER_RESAMPLE_CONSUMED
@ DT_COLOR_PICKER_RESAMPLE_EMITTED
static void _init_picker(dt_iop_color_picker_t *picker, dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *button)
static GtkWidget * _color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w, const gboolean init_cst, const dt_iop_colorspace_type_t cst)
dt_iop_colorspace_type_t dt_iop_color_picker_get_active_cst(dt_iop_module_t *module)
static void _picker_initialize_geometry_raw(dt_iop_color_picker_t *picker, dt_develop_t *dev)
enum _iop_color_picker_kind_t dt_iop_color_picker_kind_t
@ DT_COLOR_PICKER_AREA
@ DT_COLOR_PICKER_POINT
@ DT_COLOR_PICKER_POINT_AREA
@ DT_LIB_COLORPICKER_SIZE_POINT
Definition colorpicker.h:38
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:39
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:928
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:527
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:620
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
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)
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)
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1805
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1278
void dt_dev_coordinates_image_norm_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1271
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1203
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1834
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:108
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
GtkWidget * geometry
its size, under the preview
@ TYPE_FLOAT
Definition format.h:56
float dt_boundingbox_t[4]
Definition image.h:83
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1563
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:215
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
GtkWidget * dt_iop_gui_get_off(dt_iop_module_t *module)
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1229
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1251
@ DT_DEBUG_DEV
Definition logging.h:53
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define dt_unreachable_codepath()
Mark a branch as impossible.
Definition macros.h:141
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
gboolean dt_dev_pixelpipe_get_realtime(const dt_dev_pixelpipe_t *pipe)
Asking the interface to repaint itself.
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:403
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:382
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:615
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:177
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:212
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:188
@ DT_SIGNAL_CONTROL_NOTEBOOK_TAB_CHANGED
This signal is raised when a GtkNotebook created/registered via dt_ui_notebook_set_picker_owner() swi...
Definition signal.h:305
@ DT_SIGNAL_CONTROL_PICKERDATA_READY
This signal is raised when new color picker data are available in darkroom. no param,...
Definition signal.h:296
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:392
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
dt_lib_colorpicker_size_t size
Definition colorpicker.h:64
dt_boundingbox_t box
Definition colorpicker.h:63
Objective facts about the image a dev is working on.
dt_iop_buffer_dsc_t dsc_out
struct dt_iop_module_t *void * data
int picker_cst
Definition develop.h:369
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:374
gboolean update_pending
Definition develop.h:371
uint64_t wait_output_hash
Definition develop.h:386
GtkWidget * widget
Definition develop.h:367
gboolean(* refresh_global_picker)(struct dt_lib_module_t *self)
Definition develop.h:382
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
struct dt_iop_module_t * pending_module
Definition develop.h:390
struct dt_iop_module_t * gui_module
Definition develop.h:170
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
dt_dev_pixelpipe_cache_wait_t output_wait
Definition develop.h:388
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:366
uint64_t wait_input_hash
Definition develop.h:385
guint refresh_idle_source
Definition develop.h:372
struct dt_dev_pixelpipe_t * pending_pipe
Definition develop.h:391
struct dt_lib_module_t * histogram_module
Definition develop.h:381
gboolean enabled
Definition develop.h:370
uint64_t piece_hash
Definition develop.h:384
dt_dev_pixelpipe_cache_wait_t input_wait
Definition develop.h:387
dt_iop_buffer_type_t datatype
Definition format.h:85
dt_iop_module_t *dt_iop_color_picker_kind_t kind
dt_boundingbox_t pick_box
dt_iop_colorspace_type_t picker_cst
gboolean enabled
Definition imageop.h:313
dt_dev_operation_t op
Definition imageop.h:259
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_IS_TOGGLEBUTTON(obj)
GdkModifierType dt_key_modifier_state()
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)