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 "bauhaus/bauhaus.h"
34#include "common/color_picker.h"
35#include "control/signal.h"
36#include "control/control.h"
39#include "gui/gtk.h"
40#include "libs/colorpicker.h"
41#include "libs/lib.h"
42
43#include <inttypes.h>
44#include <math.h>
45#include <string.h>
46
47/*
48 The color_picker_proxy code is an interface which links the UI
49 colorpicker buttons in iops (and the colorpicker lib) with the rest
50 of the implementation (selecting/drawing colorpicker area in center
51 view, reading color value from preview pipe, and displaying results
52 in the colorpicker lib).
53
54 From the iop (or lib) POV, all that is necessary is to instantiate
55 color picker(s) via dt_color_picker_new() or
56 dt_color_picker_new_with_cst(), then subscribe to
57 DT_SIGNAL_CONTROL_PICKERDATA_READY and resolve the current ready sample
58 through dt_iop_color_picker_get_ready_data().
59
60 This code will initialize new pickers with a default area, then
61 remember the last area of the picker and use that when the picker is
62 reactivated.
63
64 The actual work of cache lookup and sampling happens here on the GUI
65 thread. The drawing & mouse-sensitivity of the picker overlay in the
66 center view happens in darkroom.c. The display of current sample
67 values occurs via libs/colorpicker.c, which uses this code to
68 activate its own picker.
69
70 The sample position is potentially stored in two places:
71
72 1. For each sampler widget, in dt_iop_color_picker_t.
73 2. For the active iop, the primary, and the live samples in
74 dt_colorpicker_sample_t.
75
76 There will be at most one editable sample, with one active picker, at
77 one time in the center view.
78*/
79
80
81// FIXME: should this be here or perhaps lib.c?
83{
84 const gboolean module_picker = dev->gui_module
85 && dev->gui_module->enabled
86 && dev->color_picker.enabled
87 && dev->color_picker.module == dev->gui_module;
88
89 const gboolean primary_picker = dev && dev->color_picker.enabled && !dev->color_picker.module;
90
91 return module_picker || primary_picker;
92}
93
95{
96 return module && module->dev
97 && module->dev->color_picker.enabled
98 && module->dev->color_picker.module == module;
99}
100
114{
115 dt_develop_t *const dev = darktable.develop;
116 const dt_colorpicker_sample_t *const sample = dev ? dev->color_picker.primary_sample : NULL;
117 gboolean changed = FALSE;
118 if(self && sample)
119 {
121 {
122 for(int k = 0; k < 2; k++)
123 {
124 if(self->pick_pos[k] != sample->point[k])
125 {
126 self->pick_pos[k] = sample->point[k];
127 changed = TRUE;
128 }
129 }
130 self->geometry_is_raw = TRUE;
131 }
132 else if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
133 {
134 for(int k = 0; k < 4; k++)
135 {
136 if(self->pick_box[k] != sample->box[k])
137 {
138 self->pick_box[k] = sample->box[k];
139 changed = TRUE;
140 }
141 }
142 self->geometry_is_raw = TRUE;
143 }
144 }
145 return changed;
146}
147
153
160
161static void _refresh_active_picker(dt_develop_t *dev);
163static void _restart_picker_cache_wait(gpointer user_data);
164
165static inline void _picker_raw_point_to_image_norm(const dt_develop_t *dev, const float raw_point[2],
166 float image_point[2])
167{
168 image_point[0] = raw_point[0];
169 image_point[1] = raw_point[1];
171}
172
173static inline void _picker_raw_box_to_image_norm(const dt_develop_t *dev, const float raw_box[4],
174 float image_box[4])
175{
176 memcpy(image_box, raw_box, sizeof(float) * 4);
178}
179
181 const dt_iop_module_t *active_module,
182 float bounds[4])
183{
184 bounds[0] = 0.0f;
185 bounds[1] = 0.0f;
186 bounds[2] = 1.0f;
187 bounds[3] = 1.0f;
188
189 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe) || IS_NULL_PTR(active_module)) return;
190 const float processed_width = dev->roi.processed_width;
191 const float processed_height = dev->roi.processed_height;
192 if(processed_width <= 0.0f || processed_height <= 0.0f) return;
193
195 (dt_iop_module_t *)active_module);
196 if(IS_NULL_PTR(piece)) return;
197
198 float quad[8] = {
199 0.0f, 0.0f,
200 (float)piece->buf_out.width, 0.0f,
201 (float)piece->buf_out.width, (float)piece->buf_out.height,
202 0.0f, (float)piece->buf_out.height
203 };
206
207 float min_x = fminf(fminf(quad[0], quad[2]), fminf(quad[4], quad[6]));
208 float min_y = fminf(fminf(quad[1], quad[3]), fminf(quad[5], quad[7]));
209 float max_x = fmaxf(fmaxf(quad[0], quad[2]), fmaxf(quad[4], quad[6]));
210 float max_y = fmaxf(fmaxf(quad[1], quad[3]), fmaxf(quad[5], quad[7]));
211
212 min_x = CLAMP(min_x / processed_width, 0.0f, 1.0f);
213 min_y = CLAMP(min_y / processed_height, 0.0f, 1.0f);
214 max_x = CLAMP(max_x / processed_width, 0.0f, 1.0f);
215 max_y = CLAMP(max_y / processed_height, 0.0f, 1.0f);
216 bounds[0] = fminf(min_x, max_x);
217 bounds[1] = fminf(min_y, max_y);
218 bounds[2] = fmaxf(min_x, max_x);
219 bounds[3] = fmaxf(min_y, max_y);
220}
221
223{
224 if(IS_NULL_PTR(picker) || IS_NULL_PTR(dev) || picker->geometry_is_raw) return;
225
226 float bounds[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
227 _picker_get_module_bounds_image_norm(dev, picker->module, bounds);
228
229 const float processed_width = dev->roi.processed_width;
230 const float processed_height = dev->roi.processed_height;
231 if(processed_width <= 0.0f || processed_height <= 0.0f) return;
232 // Fixed border inset in scale-1 image pixels, then converted to image-norm.
233 // Keep this explicit here so the caller directly controls default picker coverage.
234 const float inset_pixels = 64.0f;
235 const float inset_x = inset_pixels / processed_width;
236 const float inset_y = inset_pixels / processed_height;
237 const float width = fmaxf(bounds[2] - bounds[0], 0.0f);
238 const float height = fmaxf(bounds[3] - bounds[1], 0.0f);
239 picker->pick_pos[0] = 0.5f * (bounds[0] + bounds[2]);
240 picker->pick_pos[1] = 0.5f * (bounds[1] + bounds[3]);
241 picker->pick_box[0] = bounds[0] + fminf(inset_x, 0.5f * width);
242 picker->pick_box[1] = bounds[1] + fminf(inset_y, 0.5f * height);
243 picker->pick_box[2] = bounds[2] - fminf(inset_x, 0.5f * width);
244 picker->pick_box[3] = bounds[3] - fminf(inset_y, 0.5f * height);
245
246 picker->pick_pos[0] = CLAMP(picker->pick_pos[0], bounds[0], bounds[2]);
247 picker->pick_pos[1] = CLAMP(picker->pick_pos[1], bounds[1], bounds[3]);
248 picker->pick_box[0] = CLAMP(picker->pick_box[0], bounds[0], bounds[2]);
249 picker->pick_box[1] = CLAMP(picker->pick_box[1], bounds[1], bounds[3]);
250 picker->pick_box[2] = CLAMP(picker->pick_box[2], bounds[0], bounds[2]);
251 picker->pick_box[3] = CLAMP(picker->pick_box[3], bounds[1], bounds[3]);
252 if(picker->pick_box[0] > picker->pick_box[2])
253 {
254 const float center = 0.5f * (picker->pick_box[0] + picker->pick_box[2]);
255 picker->pick_box[0] = center;
256 picker->pick_box[2] = center;
257 }
258 if(picker->pick_box[1] > picker->pick_box[3])
259 {
260 const float center = 0.5f * (picker->pick_box[1] + picker->pick_box[3]);
261 picker->pick_box[1] = center;
262 picker->pick_box[3] = center;
263 }
264
267 picker->geometry_is_raw = TRUE;
268}
269
270static int _picker_sample_box(const dt_iop_module_t *module, const dt_iop_roi_t *roi,
271 const dt_pixelpipe_picker_source_t picker_source, int *box)
272{
273 dt_develop_t *const dev = darktable.develop;
274 const dt_colorpicker_sample_t *const sample = dev ? dev->color_picker.primary_sample : NULL;
275 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module) || IS_NULL_PTR(roi) || IS_NULL_PTR(sample)) return 1;
276
277 dt_boundingbox_t fbox = { 0.0f };
278
279 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
280 {
281 _picker_raw_box_to_image_norm(dev, sample->box, fbox);
283 }
284 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
285 {
286 _picker_raw_point_to_image_norm(dev, sample->point, fbox);
288 fbox[2] = fbox[0];
289 fbox[3] = fbox[1];
290 }
291
293 picker_source == PIXELPIPE_PICKER_INPUT
296 fbox, 2);
297
298 const float roi_scale = roi->scale;
299 if(roi_scale != 1.0f)
300 {
301 fbox[0] *= roi_scale;
302 fbox[1] *= roi_scale;
303 fbox[2] *= roi_scale;
304 fbox[3] *= roi_scale;
305 }
306
307 fbox[0] -= roi->x;
308 fbox[1] -= roi->y;
309 fbox[2] -= roi->x;
310 fbox[3] -= roi->y;
311
312 box[0] = fminf(fbox[0], fbox[2]);
313 box[1] = fminf(fbox[1], fbox[3]);
314 box[2] = fmaxf(fbox[0], fbox[2]);
315 box[3] = fmaxf(fbox[1], fbox[3]);
316
318 {
319 box[2] += 1;
320 box[3] += 1;
321 }
322
323 if(box[0] >= roi->width || box[1] >= roi->height || box[2] < 0 || box[3] < 0) return 1;
324
325 box[0] = MIN(roi->width - 1, MAX(0, box[0]));
326 box[1] = MIN(roi->height - 1, MAX(0, box[1]));
327 box[2] = MIN(roi->width - 1, MAX(0, box[2]));
328 box[3] = MIN(roi->height - 1, MAX(0, box[3]));
329
330 if(box[2] <= box[0] || box[3] <= box[1]) return 1;
331
332 return 0;
333}
334
336 const dt_iop_buffer_dsc_t *dsc, const dt_iop_roi_t *roi,
337 const float *pixel, dt_aligned_pixel_t avg_out,
338 dt_aligned_pixel_t min_out, dt_aligned_pixel_t max_out,
339 const dt_pixelpipe_picker_source_t picker_source)
340{
341 int box[4];
342 if(_picker_sample_box(module, roi, picker_source, box)) return FALSE;
343
344 dt_aligned_pixel_t avg = { 0.0f };
345 dt_aligned_pixel_t min = { INFINITY, INFINITY, INFINITY, INFINITY };
346 dt_aligned_pixel_t max = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
347
350 dt_color_picker_helper(dsc, pixel, roi, box, avg, min, max, dsc->cst, picker_cst, profile);
351
352 for(int k = 0; k < 4; k++)
353 {
354 avg_out[k] = avg[k];
355 min_out[k] = min[k];
356 max_out[k] = max[k];
357 }
358
359 return TRUE;
360}
361
363 dt_dev_pixelpipe_t **pipe,
364 const dt_dev_pixelpipe_iop_t **piece)
365{
366 dt_develop_t *const dev = darktable.develop;
367 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module) || dev->color_picker.pending_module != module || IS_NULL_PTR(dev->color_picker.pending_pipe))
368 return 1;
369
370 dt_dev_pixelpipe_t *const current_pipe = dev->color_picker.pending_pipe;
371 const dt_dev_pixelpipe_iop_t *current_piece = NULL;
372
373 /* We walk the current preview graph and look for the current piece matching the sampled hash.
374 Piece pointers cannot cross the signal boundary because the pipe may have been rebuilt. */
375 for(GList *pieces = g_list_first(current_pipe->nodes); pieces; pieces = g_list_next(pieces))
376 {
377 const dt_dev_pixelpipe_iop_t *const current = pieces->data;
378 if(current->module == module && current->global_hash == dev->color_picker.piece_hash)
379 {
380 current_piece = current;
381 break;
382 }
383 }
384
385 if(IS_NULL_PTR(current_piece))
386 {
388 "[picker] ready-data miss module=%s pending_hash=%" PRIu64 " pipe=%p\n",
389 module->op, dev->color_picker.piece_hash, (void *)current_pipe);
390 return 1;
391 }
392
393 if(picker) *picker = dev->color_picker.widget;
394 if(pipe) *pipe = current_pipe;
395 if(piece) *piece = current_piece;
397 "[picker] ready-data module=%s hash=%" PRIu64 " pipe=%p picker=%p\n",
398 module->op, current_piece->global_hash, (void *)current_pipe, (void *)dev->color_picker.widget);
399 return 0;
400}
401
403{
405 || !dev->color_picker.module || !dev->gui_module || dev->color_picker.module != dev->gui_module
406 || !dev->gui_module->enabled)
408
409 dt_dev_pixelpipe_t *const pipe = dev->preview_pipe;
411 const dt_dev_pixelpipe_iop_t *const previous_piece
413 if(IS_NULL_PTR(piece) || IS_NULL_PTR(previous_piece))
414 {
415 dt_print(DT_DEBUG_DEV, "[picker] sample retry module=%s piece=%p prev=%p\n",
416 dev->color_picker.module ? dev->color_picker.module->op : "-", (void *)piece, (void *)previous_piece);
418 }
419
421 || previous_piece->global_hash == DT_PIXELPIPE_CACHE_HASH_INVALID)
422 {
424 "[picker] invalid hash module=%s piece=%" PRIu64 " prev=%" PRIu64 "\n",
425 piece->module->op, piece->global_hash, previous_piece->global_hash);
427 }
428
429 void *input = NULL;
430 dt_pixel_cache_entry_t *input_entry = NULL;
431 dt_dev_pixelpipe_cache_wait_set_owner(&dev->color_picker.input_wait, "color-picker-input", dev->color_picker.module);
432 if(!dt_dev_pixelpipe_cache_peek_gui(pipe, previous_piece, &input, &input_entry,
434 {
435 dev->color_picker.wait_input_hash = previous_piece->global_hash;
436 dt_print(DT_DEBUG_DEV, "[picker] input cache miss module=%s prev_hash=%" PRIu64 "\n",
437 piece->module->op, previous_piece->global_hash);
439 }
440
441 void *output = NULL;
442 dt_pixel_cache_entry_t *output_entry = NULL;
443 dt_dev_pixelpipe_cache_wait_set_owner(&dev->color_picker.output_wait, "color-picker-output", dev->color_picker.module);
444 const gboolean have_output = dt_dev_pixelpipe_cache_peek_gui(pipe, piece, &output, &output_entry,
447 const gboolean output_cache_blocked_by_policy
448 = piece->bypass_cache || pipe->bypass_cache || pipe->no_cache || dt_dev_pixelpipe_get_realtime(pipe);
449 if(!have_output)
450 {
451 /* Module GUIs such as color equalizer only consume the module-input sample. A missing output
452 cacheline must therefore not block picker feedback forever, otherwise one unavailable host
453 cache entry feeds an endless recompute/retry loop. Keep output statistics explicitly invalid
454 so output-dependent consumers can detect the missing sample, but still publish the ready
455 input sample. */
456 dt_print(DT_DEBUG_DEV, "[picker] output cache miss module=%s hash=%" PRIu64 " blocked=%d\n",
457 piece->module->op, piece->global_hash, output_cache_blocked_by_policy);
458 if(!output_cache_blocked_by_policy)
460 else
462
463 for(int k = 0; k < 4; k++)
464 {
465 piece->module->picked_output_color[k] = 0.0f;
466 piece->module->picked_output_color_min[k] = 666.0f;
467 piece->module->picked_output_color_max[k] = -666.0f;
468 }
469 }
470
471 if(previous_piece->dsc_out.datatype != TYPE_FLOAT || (have_output && piece->dsc_out.datatype != TYPE_FLOAT))
472 {
474 "[picker] non-float buffers module=%s input_type=%d output_type=%d\n",
475 piece->module->op, previous_piece->dsc_out.datatype, piece->dsc_out.datatype);
477 }
478
479 /* Unlike histogram/global backbuffers, module color-pickers do not publish a dedicated long-lived buffer.
480 * They reopen the current module input/output cachelines by immutable `global_hash`, then take a temporary
481 * ref plus read lock only for the duration of the sampling pass so concurrent cache recycling cannot free
482 * the payload mid-read. */
485 if(have_output)
486 {
489 }
490
491 const gboolean sampled_input
492 = _sample_picker_buffer(pipe, piece->module, &previous_piece->dsc_out, &previous_piece->roi_out,
493 input, piece->module->picked_color, piece->module->picked_color_min,
494 piece->module->picked_color_max, PIXELPIPE_PICKER_INPUT);
495 const gboolean sampled_output
496 = have_output
497 ? _sample_picker_buffer(pipe, piece->module, &piece->dsc_out, &piece->roi_out, output,
498 piece->module->picked_output_color, piece->module->picked_output_color_min,
499 piece->module->picked_output_color_max, PIXELPIPE_PICKER_OUTPUT)
500 : FALSE;
501
502 if(!have_output && sampled_input)
503 {
504 // Keep GUI picker feedback alive whenever module output is temporarily
505 // unavailable: mirror input sample statistics to output slots. If output
506 // cache becomes available later, subsequent refreshes overwrite these with
507 // true output samples.
508 for(int k = 0; k < 4; k++)
509 {
510 piece->module->picked_output_color[k] = piece->module->picked_color[k];
511 piece->module->picked_output_color_min[k] = piece->module->picked_color_min[k];
512 piece->module->picked_output_color_max[k] = piece->module->picked_color_max[k];
513 }
514 }
515
516 if(have_output)
517 {
520 }
523
524 if(!sampled_input)
525 {
526 dt_print(DT_DEBUG_DEV, "[picker] sample failed module=%s input=%d output=%d\n",
527 piece->module->op, sampled_input, sampled_output);
528 dev->color_picker.picker->update_pending = FALSE;
531 }
532
534 "[picker] sampled module=%s hash=%" PRIu64 " avg=(%g,%g,%g) min=(%g,%g,%g) max=(%g,%g,%g)\n",
535 piece->module->op, piece->global_hash,
536 piece->module->picked_color[0], piece->module->picked_color[1], piece->module->picked_color[2],
537 piece->module->picked_color_min[0], piece->module->picked_color_min[1], piece->module->picked_color_min[2],
538 piece->module->picked_color_max[0], piece->module->picked_color_max[1], piece->module->picked_color_max[2]);
539
540 dev->color_picker.piece_hash = piece->global_hash;
541 dev->color_picker.pending_module = piece->module;
542 dev->color_picker.pending_pipe = pipe;
545 dev->color_picker.picker->update_pending = FALSE;
547
549 dev->color_picker.pending_module = NULL;
550 dev->color_picker.pending_pipe = NULL;
553}
554
555static gboolean _refresh_active_picker_idle(gpointer user_data)
556{
557 dt_develop_t *const dev = (dt_develop_t *)user_data;
558 if(dev) dev->color_picker.refresh_idle_source = 0;
560 return G_SOURCE_REMOVE;
561}
562
564{
565 if(IS_NULL_PTR(dev)) return;
566 if(dev->color_picker.refresh_idle_source) return;
568}
569
570static void _restart_picker_cache_wait(gpointer user_data)
571{
572 dt_develop_t *const dev = (dt_develop_t *)user_data;
574}
575
577{
578 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->color_picker.picker) || !dev->color_picker.enabled) return;
579 if(!dev->color_picker.picker->update_pending && !dev->color_picker.update_pending) return;
580
582 "[picker] refresh module=%s update_pending=%d widget_pending=%d processing=%d\n",
583 dev->color_picker.module ? dev->color_picker.module->op : "-",
584 dev->color_picker.update_pending, dev->color_picker.picker->update_pending,
585 dev->preview_pipe ? dev->preview_pipe->processing : -1);
586
588
589 /* A picker update is satisfied either from the current preview cache or from the next completed
590 preview run. If preview is already processing, re-dirtying it here only feeds TOP_CHANGED
591 loops and prevents the current run from ever publishing the cacheline we are waiting for. */
593 {
594 // Make sure CACHELINE_READY can wake us as soon as this in-flight run
595 // publishes input/output cachelines for the active picker.
597 return;
598 }
599
600 if(IS_NULL_PTR(dev->color_picker.module))
601 {
602 /* The global picker already samples from histogram backbuffers published by preview updates.
603 Refresh it directly from that GUI-owned cache when possible so dragging the picker updates
604 the histogram labels immediately without waiting for another preview completion. */
608 {
609 dev->color_picker.picker->update_pending = FALSE;
612 return;
613 }
614 }
615
616 if(dev->preview_pipe)
617 {
619 if(sampled != DT_COLOR_PICKER_RESAMPLE_RETRY)
620 return;
621 }
622}
623
625{
626 if(picker)
627 {
628 if(picker->module) dt_iop_set_cache_bypass(picker->module, FALSE);
629
631
633 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(picker->colorpick), FALSE);
634 else
636
638 }
639}
640
642{
643 (void)widget;
644 dt_develop_t *const dev = darktable.develop;
645 if(IS_NULL_PTR(dev) || dev->color_picker.picker != picker) return;
646
647 dev->color_picker.picker = NULL;
648 dev->color_picker.widget = NULL;
649}
650
651void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
652{
653 dt_develop_t *const dev = darktable.develop;
654 dt_iop_color_picker_t *picker = dev ? dev->color_picker.picker : NULL;
655 if(picker && picker->module == module)
656 {
657 if(!keep)
658 {
661 _color_picker_reset(picker);
662 dev->color_picker.picker = NULL;
663 dev->color_picker.widget = NULL;
664 dev->color_picker.module = NULL;
669 if(module) module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
670 }
671 }
672}
673
676{
677 // module is NULL if primary colorpicker
678 picker->module = module;
679 picker->kind = kind;
680 picker->picker_cst = module ? module->default_colorspace(module, NULL, NULL) : IOP_CS_NONE;
681 picker->colorpick = button;
682 picker->update_pending = FALSE;
683 picker->geometry_is_raw = FALSE;
684
685 // default values
686 const float middle = 0.5f;
687 const float area = 0.975f;
688 picker->pick_pos[0] = picker->pick_pos[1] = middle;
689 picker->pick_box[0] = (1.0f - area);
690 picker->pick_box[1] = (1.0f - area);
691 picker->pick_box[2] = area;
692 picker->pick_box[3] = area;
693
694 _color_picker_reset(picker);
695}
696
697static gboolean _color_picker_callback_button_press(GtkWidget *button, GdkEventButton *e, dt_iop_color_picker_t *self)
698{
699 // module is NULL if primary colorpicker
700 dt_iop_module_t *module = self->module;
701 dt_develop_t *const dev = darktable.develop;
702
703 if(darktable.gui->reset) return FALSE;
704
705 dt_iop_color_picker_t *prior_picker = dev ? dev->color_picker.picker : NULL;
706 if(prior_picker && prior_picker != self)
707 {
708 _color_picker_reset(prior_picker);
709 if(prior_picker->module) prior_picker->module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
710 }
711
712 if(module && module->off)
713 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), TRUE);
714
715 const GdkModifierType state = !IS_NULL_PTR(e) ? e->state : dt_key_modifier_state();
716 const gboolean ctrl_key_pressed = dt_modifier_is(state, GDK_CONTROL_MASK) || (!IS_NULL_PTR(e) && e->button == 3);
718
719 if(prior_picker != self || (kind == DT_COLOR_PICKER_POINT_AREA &&
720 (ctrl_key_pressed ^ (dev->color_picker.primary_sample->size == DT_LIB_COLORPICKER_SIZE_BOX))))
721 {
722 dev->color_picker.picker = self;
723 dev->color_picker.widget = self->colorpick;
724 dev->color_picker.module = module;
725 dev->color_picker.kind = kind;
726 dev->color_picker.picker_cst = self->picker_cst;
727 dev->color_picker.enabled = TRUE;
728
729 if(module) module->request_color_pick = DT_REQUEST_COLORPICK_MODULE;
730
732 {
733 kind = ctrl_key_pressed ? DT_COLOR_PICKER_AREA : DT_COLOR_PICKER_POINT;
734 }
736
738 {
739 dt_boundingbox_t image_box = { 0.0f };
740 _picker_raw_box_to_image_norm(dev, self->pick_box, image_box);
742 }
743 else if(kind == DT_COLOR_PICKER_POINT)
744 {
745 float image_point[2] = { 0.0f };
746 _picker_raw_point_to_image_norm(dev, self->pick_pos, image_point);
748 }
749 else
751
752 // important to have set up state before toggling button and
753 // triggering more callbacks
756 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->colorpick), TRUE);
757 else
760
761 if(module)
762 dt_iop_request_focus(module);
763
764 dt_print(DT_DEBUG_DEV, "[picker] activate module=%s picker=%p widget=%p kind=%d cst=%d\n",
765 module ? module->op : "global", (void *)self, (void *)self->colorpick, kind, self->picker_cst);
766
767 }
768 else
769 {
770 dt_dev_pixelpipe_cache_wait_cleanup(&dev->color_picker.input_wait, "picker-deactivate-input");
771 dt_dev_pixelpipe_cache_wait_cleanup(&dev->color_picker.output_wait, "picker-deactivate-output");
772 dev->color_picker.picker = NULL;
773 dev->color_picker.widget = NULL;
774 dev->color_picker.module = NULL;
775 dev->color_picker.kind = DT_COLOR_PICKER_POINT;
776 dev->color_picker.picker_cst = IOP_CS_NONE;
777 dev->color_picker.enabled = FALSE;
778 dev->color_picker.update_pending = FALSE;
780 if(module)
781 {
782 module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
783 }
784 dt_print(DT_DEBUG_DEV, "[picker] deactivate module=%s picker=%p widget=%p\n",
785 module ? module->op : "global", (void *)self, (void *)self->colorpick);
786 }
787
788 // Draw picker geometry immediately; data sampling update can complete later.
790 if(dev->color_picker.enabled)
792
793 return TRUE;
794}
795
797{
798 _color_picker_callback_button_press(button, NULL, self);
799}
800
802{
803 dt_develop_t *const dev = darktable.develop;
804 dt_iop_color_picker_t *const picker = dev ? dev->color_picker.picker : NULL;
805 if(picker && picker->module == module && picker->picker_cst != picker_cst)
806 {
807 picker->picker_cst = picker_cst;
809 }
810}
811
813{
814 dt_develop_t *const dev = darktable.develop;
815 dt_iop_color_picker_t *picker = dev ? dev->color_picker.picker : NULL;
816 if(picker && picker->module == module)
817 return picker->picker_cst;
818 else
819 return IOP_CS_NONE;
820}
821
823{
824 dt_develop_t *const dev = darktable.develop;
825 dt_iop_color_picker_t *picker = dev ? dev->color_picker.picker : NULL;
826 if(picker) picker->update_pending = TRUE;
827 if(dev)
828 {
830 }
831 if(dev)
832 dt_print(DT_DEBUG_DEV, "[picker] request update module=%s picker=%p widget=%p\n",
833 dev->color_picker.module ? dev->color_picker.module->op : "-",
834 (void *)dev->color_picker.picker, (void *)dev->color_picker.widget);
836}
837
839 const dt_iop_module_t *module)
840{
842 pipe->dev->color_picker.module);
843 const dt_dev_pixelpipe_iop_t *const previous_piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
844
845 return module == pipe->dev->color_picker.module || (previous_piece && previous_piece->module == module);
846}
847
849{
850 if(IS_NULL_PTR(dev))
851 return;
852
855
857 || !dev->color_picker.module || !dev->gui_module || dev->color_picker.module != dev->gui_module
858 || !dev->gui_module->enabled)
859 return;
860
862 dev->color_picker.module);
863 const dt_dev_pixelpipe_iop_t *const previous_piece
865 if(piece) dev->color_picker.wait_output_hash = piece->global_hash;
866 if(previous_piece) dev->color_picker.wait_input_hash = previous_piece->global_hash;
867}
868
869static void _iop_color_picker_history_resync_callback(gpointer instance, gpointer user_data)
870{
871 (void)instance;
872 (void)user_data;
873 dt_develop_t *const dev = darktable.develop;
876}
877
878static void _iop_color_picker_cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
879{
880 (void)instance;
881 (void)user_data;
882
883 dt_develop_t *const dev = darktable.develop;
884 if(IS_NULL_PTR(dev)) return;
885
886 gboolean matched = FALSE;
887 if(dev->color_picker.wait_input_hash == hash)
888 {
890 matched = TRUE;
891 }
892 if(dev->color_picker.wait_output_hash == hash)
893 {
895 matched = TRUE;
896 }
897
898 if(matched) _queue_refresh_active_picker(dev);
899}
900
908
916
918 const gboolean init_cst, const dt_iop_colorspace_type_t cst)
919{
920 dt_iop_color_picker_t *color_picker = (dt_iop_color_picker_t *)g_malloc(sizeof(dt_iop_color_picker_t));
921
922 if(IS_NULL_PTR(w) || GTK_IS_BOX(w))
923 {
925 _init_picker(color_picker, module, kind, button);
926 if(init_cst)
927 color_picker->picker_cst = cst;
928 g_signal_connect_data(G_OBJECT(button), "button-press-event",
929 G_CALLBACK(_color_picker_callback_button_press), color_picker, (GClosureNotify)g_free, 0);
930 g_signal_connect(G_OBJECT(button), "destroy", G_CALLBACK(_color_picker_widget_destroy), color_picker);
931 if(w) gtk_box_pack_start(GTK_BOX(w), button, FALSE, FALSE, 0);
932
933 dt_develop_t *const dev = darktable.develop;
934 if(dev && dev->color_picker.enabled && dev->color_picker.module == module
936 && dev->color_picker.kind == kind
937 && dev->color_picker.picker_cst == color_picker->picker_cst)
938 {
939 dev->color_picker.picker = color_picker;
940 dev->color_picker.widget = button;
941
943 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
945 }
946
947 return button;
948 }
949 else
950 {
953 _init_picker(color_picker, module, kind, w);
954 if(init_cst)
955 color_picker->picker_cst = cst;
956 g_signal_connect_data(G_OBJECT(w), "quad-pressed",
957 G_CALLBACK(_color_picker_callback), color_picker, (GClosureNotify)g_free, 0);
958 g_signal_connect(G_OBJECT(w), "destroy", G_CALLBACK(_color_picker_widget_destroy), color_picker);
959
960 dt_develop_t *const dev = darktable.develop;
961 if(dev && dev->color_picker.enabled && dev->color_picker.module == module
963 && dev->color_picker.kind == kind
964 && dev->color_picker.picker_cst == color_picker->picker_cst)
965 {
966 dev->color_picker.picker = color_picker;
967 dev->color_picker.widget = w;
968
972 }
973
974 return w;
975 }
976}
977
982
988
989// clang-format off
990// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
991// vim: shiftwidth=2 expandtab tabstop=2 cindent
992// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
993// clang-format on
#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:1720
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1726
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1702
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
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 _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 gboolean _record_point_area(dt_iop_color_picker_t *self)
Synchronize one picker cached geometry with the primary sample.
gboolean dt_iop_color_picker_force_cache(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
static void _iop_color_picker_cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
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)
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:37
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:38
static const float const float const float min
const float max
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
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
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_DEV
Definition darktable.h:717
float dt_boundingbox_t[4]
Definition darktable.h:709
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define dt_unreachable_codepath()
Definition darktable.h:979
#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
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)
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:1557
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1131
void dt_dev_coordinates_image_norm_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1124
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1060
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:1586
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:104
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ TYPE_FLOAT
Definition format.h:46
GdkModifierType dt_key_modifier_state()
Definition gtk.c:2186
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2915
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:196
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_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1456
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1478
float *const restrict const size_t k
float dt_aligned_pixel_t[4]
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
gboolean dt_dev_pixelpipe_get_realtime(const dt_dev_pixelpipe_t *pipe)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:209
@ 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
@ DT_SIGNAL_CONTROL_PICKERDATA_READY
This signal is raised when new color picker data are available in darkroom. no param,...
Definition signal.h:293
#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]
struct dt_lib_t * lib
Definition darktable.h:771
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_develop_t * develop
Definition darktable.h:770
dt_lib_colorpicker_size_t size
Definition colorpicker.h:63
dt_boundingbox_t box
Definition colorpicker.h:62
dt_iop_buffer_dsc_t dsc_out
struct dt_iop_module_t *void * data
struct dt_develop_t * dev
int picker_cst
Definition develop.h:386
struct dt_develop_t::@19 color_picker
Authoritative darkroom color-picker state.
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:391
gboolean update_pending
Definition develop.h:388
uint64_t wait_output_hash
Definition develop.h:403
GtkWidget * widget
Definition develop.h:384
gboolean(* refresh_global_picker)(struct dt_lib_module_t *self)
Definition develop.h:399
struct dt_iop_module_t * pending_module
Definition develop.h:407
int32_t processed_width
Definition develop.h:233
struct dt_iop_module_t * gui_module
Definition develop.h:165
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
dt_dev_pixelpipe_cache_wait_t output_wait
Definition develop.h:405
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:383
struct dt_develop_t::@17 roi
uint64_t wait_input_hash
Definition develop.h:402
guint refresh_idle_source
Definition develop.h:389
int32_t processed_height
Definition develop.h:233
struct dt_dev_pixelpipe_t * pending_pipe
Definition develop.h:408
struct dt_lib_module_t * histogram_module
Definition develop.h:398
gboolean enabled
Definition develop.h:387
uint64_t piece_hash
Definition develop.h:401
dt_dev_pixelpipe_cache_wait_t input_wait
Definition develop.h:404
int32_t reset
Definition gtk.h:172
dt_iop_buffer_type_t datatype
Definition format.h:56
dt_iop_module_t *dt_iop_color_picker_kind_t kind
dt_boundingbox_t pick_box
dt_iop_colorspace_type_t picker_cst
GModule *dt_dev_operation_t op
Definition imageop.h:256
gboolean enabled
Definition imageop.h:298
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
#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)