Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/histogram.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Henrik Andersson.
4 Copyright (C) 2011-2012, 2016 johannes hanika.
5 Copyright (C) 2012 Richard Wonka.
6 Copyright (C) 2012-2014, 2016-2018 Tobias Ellinghaus.
7 Copyright (C) 2013 José Carlos García Sogo.
8 Copyright (C) 2013-2016 Roman Lebedev.
9 Copyright (C) 2014 parafin.
10 Copyright (C) 2016 Alexander V. Smal.
11 Copyright (C) 2016 Asma.
12 Copyright (C) 2017-2018, 2020-2021 Dan Torop.
13 Copyright (C) 2018 Maurizio Paglia.
14 Copyright (C) 2018 rawfiner.
15 Copyright (C) 2019-2020 Aldric Renaudin.
16 Copyright (C) 2019, 2021-2026 Aurélien PIERRE.
17 Copyright (C) 2019 Edgardo Hoszowski.
18 Copyright (C) 2019-2021 Pascal Obry.
19 Copyright (C) 2019 Ulrich Pegelow.
20 Copyright (C) 2020 Bill Ferguson.
21 Copyright (C) 2020-2021 Chris Elston.
22 Copyright (C) 2020 GrahamByrnes.
23 Copyright (C) 2020 Hubert Kowalski.
24 Copyright (C) 2020 Martin Straeten.
25 Copyright (C) 2020-2021 Philippe Weyland.
26 Copyright (C) 2020-2021 Ralf Brown.
27 Copyright (C) 2021-2022 Diederik Ter Rahe.
28 Copyright (C) 2021 luzpaz.
29 Copyright (C) 2021 Marco Carrarini.
30 Copyright (C) 2021 Mark-64.
31 Copyright (C) 2021 Paolo DePetrillo.
32 Copyright (C) 2021 paolodepetrillo.
33 Copyright (C) 2021 Sakari Kapanen.
34 Copyright (C) 2022 Martin Bařinka.
35 Copyright (C) 2022 Philipp Lutz.
36 Copyright (C) 2022 Victor Forsiuk.
37 Copyright (C) 2024 Alynx Zhou.
38 Copyright (C) 2024 Marrony Neris.
39
40 darktable is free software: you can redistribute it and/or modify
41 it under the terms of the GNU General Public License as published by
42 the Free Software Foundation, either version 3 of the License, or
43 (at your option) any later version.
44
45 darktable is distributed in the hope that it will be useful,
46 but WITHOUT ANY WARRANTY; without even the implied warranty of
47 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 GNU General Public License for more details.
49
50 You should have received a copy of the GNU General Public License
51 along with darktable. If not, see <http://www.gnu.org/licenses/>.
52*/
53
54#include <stdint.h>
55#include <inttypes.h>
56
57#include "develop/imageop_gui.h"
58#include "widgets/bauhaus.h"
61#include "common/color_picker.h"
64#include "common/histogram.h"
65#include "common/image.h"
67#include "develop/iop_profile.h"
68#include "common/logging.h"
69#include "system/macros.h"
70#include "math/math.h"
71#include "system/mem_alloc.h"
73#include "system/openmp.h"
74#include "system/simd.h"
75#include "common/times.h"
76#include "common/conf.h"
77#include "control/control.h"
78#include "control/signal.h"
80#include "develop/develop.h"
82#include "widgets/button.h"
84#include "widgets/draw.h"
85#include "gui/application.h"
86#include "libs/lib.h"
87#include "libs/lib_api.h"
88#include "libs/colorpicker.h"
90#include "widgets/label.h"
91#include "widgets/scroll_wrap.h"
93#include "gui/screen_metrics.h"
95
96#ifdef GDK_WINDOWING_QUARTZ
97#endif
98
99#define HISTOGRAM_BINS 256
100#define GAMMA 1.f / 2.f
101#define DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE (1.f / 256.f)
102#define DT_LIB_HISTOGRAM_SCOPE_ENABLE_SMOOTHING 1
103#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES 1
104#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES 4
105#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_FORCE (256 * 0.33)
106#define DT_LIB_HISTOGRAM_SCOPE_RESTRICTED_LABEL_OPERATOR CAIRO_OPERATOR_ADD
107#define DT_LIB_HISTOGRAM_SCOPE_DEFAULT_HEIGHT 250
108#define DT_LIB_HISTOGRAM_SCOPE_MIN_HEIGHT 120
109#define DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT 500
110#define DT_LIB_HISTOGRAM_SCOPE_HANDLE_HEIGHT 8
111#define DT_LIB_HISTOGRAM_SCOPE_HEIGHT_CONF "plugin/darkroom/histogram/scope_height"
112
113DT_MODULE(1)
114
125
126
128{
129 // If any of those params changes, we need to recompute the Cairo buffer.
130 float zoom;
131 int width;
136
146
148 = { N_("RGB"), N_("Lab"), N_("LCh"), N_("HSL"), N_("HSV"), N_("none"), NULL };
150 = { N_("mean"), N_("min"), N_("max"), NULL };
151
152
153typedef struct dt_lib_histogram_t
154{
155 struct dt_lib_module_t *module;
156 GtkWidget *scope_draw; // GtkDrawingArea -- scope, scale, and draggable overlays
157 GtkWidget *scope_resize_handle; // GtkDrawingArea -- vertical resize grip kept outside the rendered scope
158 dt_backbuf_t *backbuf; // reference to the dev backbuf currently in use
159 const char *op; // pipeline stage ("initialscale" | "colorout" | "gamma")
160 dt_lib_histogram_scope_type_t scope; // which scope/display type is active
161 float zoom; // zoom level for the vectorscope
163
165 cairo_surface_t *cst;
166
181
183
185
191
199
204static void _reset_cache(dt_lib_histogram_t *d);
205
206static void _histogram_restart_cache_wait(gpointer user_data)
207{
208 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
209 if(IS_NULL_PTR(self)) return;
211}
212
221static void _histogram_restart_scope_cache_wait(gpointer user_data)
222{
223 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
224 dt_lib_histogram_t *d = !IS_NULL_PTR(self) ? self->data : NULL;
225 if(IS_NULL_PTR(d)) return;
226
229}
230
239
240/* `roi` and `cst` are the caller's SNAPSHOT of `piece->roi_in`/`piece->dsc_in.cst`, not the live
241 * fields: the pipeline worker rewrites those on every ROI-planning pass while this runs on the GUI
242 * thread, and the buffer we are about to bin was sized against one particular reading of them.
243 * Re-reading `piece->roi_in` here would let the binning loop walk past the end of that buffer. */
245 const dt_iop_roi_t *const roi, const dt_iop_colorspace_type_t cst,
246 const float *pixel, dt_iop_module_t *module)
247{
248 dt_dev_histogram_collection_params_t histogram_params = piece->histogram_params;
249 dt_histogram_roi_t histogram_roi;
250
251 if(IS_NULL_PTR(histogram_params.roi))
252 {
253 histogram_roi = (dt_histogram_roi_t){
254 .width = roi->width, .height = roi->height,
255 .crop_x = 0, .crop_y = 0, .crop_width = 0, .crop_height = 0
256 };
257 histogram_params.roi = &histogram_roi;
258 }
259
261 dt_histogram_helper(&histogram_params, &module->histogram_stats, cst, module->histogram_cst,
262 pixel, &module->histogram, module->histogram_middle_grey,
265 &module->histogram, module->histogram_max);
267
268 if(module->gui && module->gui->widget) dt_control_queue_redraw_widget(module->gui->widget);
269}
270
272{
273 if(IS_NULL_PTR(dev) || IS_NULL_PTR(op)) return NULL;
274
275 if(!strcmp(op, "initialscale"))
276 return &dev->raw_histogram;
277 else if(!strcmp(op, "colorout"))
278 return &dev->output_histogram;
279 else if(!strcmp(op, "gamma"))
280 return &dev->display_histogram;
281 else
282 return NULL;
283}
284
286{
287 if(IS_NULL_PTR(backbuf)) return;
288
289 /* Global histogram backbuffers keep one structural ref on top of the module-output lifetime.
290 * Clearing that published view therefore means releasing the extra GUI-side keepalive ref here. */
293}
294
295static gboolean _refresh_global_histogram_backbuf_for_hash(dt_develop_t *dev, const char *op,
296 const uint64_t expected_hash)
297{
298 dt_backbuf_t *const backbuf = _get_histogram_backbuf(dev, op);
299 if(IS_NULL_PTR(backbuf) || IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe)) return FALSE;
300
303 if(IS_NULL_PTR(piece))
304 {
306 return FALSE;
307 }
308
309 const dt_dev_pixelpipe_iop_t *const previous_piece
311
312 /* Copies, not pointers into the live piece: the pipeline worker rewrites roi_out/dsc_out on
313 * every ROI-planning pass, and what gets published here is the (geometry, hash) PAIR every
314 * scope later reads the cacheline with. Following the live fields would let the pair drift
315 * apart between the peek below and the publication. */
316 dt_iop_roi_t roi = piece->roi_out;
317 dt_iop_buffer_dsc_t dsc = piece->dsc_out;
318 uint64_t hash = piece->global_hash;
319
320 if(!strcmp(op, "gamma"))
321 {
322 if(IS_NULL_PTR(previous_piece))
323 {
325 return FALSE;
326 }
327
328 roi = previous_piece->roi_out;
329 dsc = previous_piece->dsc_out;
330 hash = previous_piece->global_hash;
331 }
332
333 if(expected_hash != DT_PIXELPIPE_CACHE_HASH_INVALID && hash != expected_hash) return FALSE;
334
335 /* Retained, because this is where the keepalive every scope later borrows is MINTED. Looking the
336 * entry up and referencing it afterwards would let the worker evict it in between, and the
337 * backbuffer would then publish a hash whose entry is already gone -- with every borrowing
338 * consumer believing a keepalive covers it. */
339 dt_pixel_cache_entry_t *entry = NULL;
342 !strcmp(op, "gamma") ? previous_piece : piece,
343 NULL, &entry, NULL, NULL, NULL))
344 {
346 return FALSE;
347 }
348
349 /* The scopes read `backbuf->width * height * bpp` bytes out of this cacheline. Publish the pair
350 * only once the cache confirms it reserved at least that much, so a plan that moved between the
351 * hash and the geometry above cannot hand the drawing code an over-long buffer. */
352 if(roi.width <= 0 || roi.height <= 0 || dsc.bpp < 1
353 || dt_pixel_cache_entry_get_size(entry) < (size_t)roi.width * (size_t)roi.height * (size_t)dsc.bpp)
354 {
356 return FALSE;
357 }
358
359 /* The module output already owns its producer ref. Tagging it as a global histogram backbuffer
360 * reserves one additional consumer ref so GUI readers only need `peek()` and read locks later.
361 *
362 * The retained lookup above is already holding that reference, so give the backbuffer its own
363 * and then drop ours. The helper is idempotent, which collapses the two cases this used to
364 * branch on: a different cacheline leaves the backbuffer holding exactly one reference, and
365 * re-publishing the one it already holds leaves that one untouched while our extra goes.
366 *
367 * Keyed on the ENTRY throughout. Releasing the previous keepalive by its hash was correct only
368 * while an entry's hash could not move under its holder, which rekey reuse does not promise --
369 * see dt_dev_pixelpipe_cache_unref_entry(). */
370 dt_dev_backbuf_take_keepalive(backbuf, entry);
372
374 return TRUE;
375}
376
378 const uint64_t expected_hash)
379{
380 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe) || IS_NULL_PTR(module)) return FALSE;
381
383 if(IS_NULL_PTR(piece) || !(piece->request_histogram & DT_REQUEST_ON)) return FALSE;
384 if((piece->request_histogram & DT_REQUEST_ONLY_IN_GUI) && !dev->gui_attached) return FALSE;
385
386 const dt_dev_pixelpipe_iop_t *const previous_piece
388 if(IS_NULL_PTR(previous_piece) || previous_piece->global_hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
389 if(expected_hash != DT_PIXELPIPE_CACHE_HASH_INVALID && previous_piece->global_hash != expected_hash) return FALSE;
390 if(previous_piece->dsc_out.datatype != TYPE_FLOAT) return FALSE;
391
392 void *input = NULL;
393 dt_pixel_cache_entry_t *input_entry = NULL;
395 dt_lib_histogram_t *const d = !IS_NULL_PTR(histogram_module) ? histogram_module->data : NULL;
396 if(!IS_NULL_PTR(d))
397 dt_dev_pixelpipe_cache_wait_set_owner(&d->module_wait, "histogram-module-refresh", module);
398 /* Retained: this is an intermediate module cacheline, at refcount 0 between renders, so the
399 * reference has to be taken with the lookup and not after it. */
400 if(!dt_dev_pixelpipe_cache_peek_gui_retained(dev->preview_pipe, previous_piece, &input, &input_entry,
401 !IS_NULL_PTR(d) ? &d->module_wait : NULL,
402 _histogram_restart_cache_wait, histogram_module))
403 return FALSE;
404
406
407 /* `piece->roi_in` and `piece->dsc_in` are rewritten by the pipeline worker on every ROI-planning
408 * pass, and this runs on the GUI thread without the pipe's busy_mutex (see the comment in
409 * `_refresh_preview_histograms()`: taking it here would freeze the UI). Read them ONCE, into
410 * locals, and describe the buffer with nothing else afterwards. Reading them again for the loop
411 * bounds after having sized an allocation from an earlier reading is how a ROI that grew in
412 * between made the colourspace conversion write past the end of its own buffer, in the middle of
413 * an OpenMP region (Sentry 145090920, `_transform_lab_to_rgb_matrix.omp_outlined`). */
414 const dt_iop_roi_t roi_in = piece->roi_in;
415 const dt_iop_buffer_dsc_t dsc_in = piece->dsc_in;
416
417 const float *histogram_input = input;
418 float *transformed_input = NULL;
419 dt_iop_buffer_dsc_t input_dsc = previous_piece->dsc_out;
420
421 /* The cacheline was produced by `previous_piece` against ITS own roi_out, whichever reading of the
422 * plan was current then. The one authoritative bound on what may be read out of it is the size the
423 * cache actually reserved, so check the geometry we just snapshotted against it rather than trust
424 * two live fields to still agree. A mismatch means the plan moved under us: bail out and let the
425 * caller re-queue this refresh for the cacheline the new plan will publish. This also covers the
426 * histogram binning below, which addresses `dsc_in.channels` floats per pixel just the same
427 * (Sentry 144448425, `histogram_helper_cs_rgb` in `dt_histogram_worker.omp_outlined`). */
428 const size_t pixels = (size_t)roi_in.width * (size_t)roi_in.height;
429 const size_t bytes = pixels * (size_t)dsc_in.channels * sizeof(float);
430 if(roi_in.width <= 0 || roi_in.height <= 0 || dsc_in.channels < 1
431 || dt_pixel_cache_entry_get_size(input_entry) < bytes)
432 {
435 return FALSE;
436 }
437
438 /* `dt_colorspaces_apply_profile()` addresses 4 floats per pixel unconditionally, so the
439 * destination has to be that wide too -- `bytes` is sized on `dsc_in.channels`. The only
440 * conversions it performs are Lab<->RGB, which every module declares with 4 channels, so this
441 * only ever declines a pair the matrix path would have refused anyway. */
442 if(input_dsc.cst != dsc_in.cst && dsc_in.channels == 4
443 && !(dt_iop_colorspace_is_rgb(input_dsc.cst) && dt_iop_colorspace_is_rgb(dsc_in.cst)))
444 {
445 transformed_input = dt_alloc_align(bytes);
446
447 if(IS_NULL_PTR(transformed_input))
448 {
451 return FALSE;
452 }
453
454 memcpy(transformed_input, input, bytes);
455 dt_colorspaces_apply_profile(module->op, module->multi_name, transformed_input, transformed_input,
456 roi_in.width, roi_in.height,
457 input_dsc.cst, dsc_in.cst, &input_dsc.cst,
459 histogram_input = transformed_input;
460 }
461 else if(input_dsc.cst != dsc_in.cst)
462 {
463 input_dsc.cst = dsc_in.cst;
464 }
465
466 _refresh_module_histogram(dev->preview_pipe, piece, &roi_in, dsc_in.cst, histogram_input, module);
467
470 dt_free_align(transformed_input);
471
472 return TRUE;
473}
474
476{
477 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
478 if(!dev->preview_pipe->gui_observable_source) return;
479 /* Do not grab preview_pipe->busy_mutex from GUI thread: it may be held by
480 * the pipeline worker for long recomputations and would freeze the UI.
481 * We rely on non-blocking cache probes and the cache-wait manager instead. */
482
483 const char *ops[] = { "initialscale", "colorout", "gamma" };
484 uint64_t *pending_hashes[] = {
488 };
489
490 for(size_t i = 0; i < G_N_ELEMENTS(ops); i++)
491 {
494 if(IS_NULL_PTR(piece))
495 {
497 continue;
498 }
499
500 uint64_t hash = piece->global_hash;
501 if(!strcmp(ops[i], "gamma"))
502 {
503 const dt_dev_pixelpipe_iop_t *const previous_piece
505 hash = previous_piece ? previous_piece->global_hash : DT_PIXELPIPE_CACHE_HASH_INVALID;
506 }
507
509 {
511 *pending_hashes[i] = DT_PIXELPIPE_CACHE_HASH_INVALID;
512 continue;
513 }
514
515 // Avoid re-requesting the same cacheline on every history resync.
516 // While the same hash is pending, we wait for DT_SIGNAL_CACHELINE_READY
517 // to complete the refresh instead of feeding CACHE_REQUEST in a loop.
518 if(*pending_hashes[i] == hash) continue;
519
521 *pending_hashes[i] = DT_PIXELPIPE_CACHE_HASH_INVALID;
522 else
523 *pending_hashes[i] = hash;
524 }
525
526 GHashTable *seen_modules = g_hash_table_new(g_direct_hash, g_direct_equal);
527 for(GList *node = g_list_first(dev->preview_pipe->nodes); node; node = g_list_next(node))
528 {
529 dt_dev_pixelpipe_iop_t *piece = node->data;
530 if(IS_NULL_PTR(piece) || !piece->enabled) continue;
531 if(!(piece->request_histogram & DT_REQUEST_ON)) continue;
532 if((piece->request_histogram & DT_REQUEST_ONLY_IN_GUI) && !dev->gui_attached) continue;
533
534 g_hash_table_insert(seen_modules, piece->module, piece->module);
535
536 const dt_dev_pixelpipe_iop_t *const previous_piece
538 if(!IS_NULL_PTR(previous_piece)
539 && !_refresh_preview_module_histogram_for_hash(dev, piece->module, previous_piece->global_hash))
540 {
541 gboolean found = FALSE;
542 for(GList *l = _preview_refresh_state.module_histograms; l; l = g_list_next(l))
543 {
544 dt_histogram_pending_module_refresh_t *pending = l->data;
545 if(IS_NULL_PTR(pending) || pending->module != piece->module) continue;
546 pending->hash = previous_piece->global_hash;
547 found = TRUE;
548 break;
549 }
550
551 if(!found)
552 {
553 dt_histogram_pending_module_refresh_t *pending = g_malloc0(sizeof(*pending));
554 pending->module = piece->module;
555 pending->hash = previous_piece->global_hash;
557 = g_list_prepend(_preview_refresh_state.module_histograms, pending);
558 }
559 }
560 else
561 {
562 for(GList *l = _preview_refresh_state.module_histograms; l;)
563 {
564 GList *next = g_list_next(l);
565 dt_histogram_pending_module_refresh_t *pending = l->data;
566 if(pending && pending->module == piece->module)
567 {
569 = g_list_delete_link(_preview_refresh_state.module_histograms, l);
570 g_free(pending);
571 }
572 l = next;
573 }
574 }
575 }
576
577 // Remove stale pending module refreshes.
578 for(GList *l = _preview_refresh_state.module_histograms; l;)
579 {
580 GList *next = g_list_next(l);
581 dt_histogram_pending_module_refresh_t *pending = l->data;
582 if(IS_NULL_PTR(pending) || !g_hash_table_contains(seen_modules, pending->module))
583 {
585 = g_list_delete_link(_preview_refresh_state.module_histograms, l);
586 g_free(pending);
587 }
588 l = next;
589 }
590 g_hash_table_destroy(seen_modules);
591}
592
593static void _preview_history_resync_callback(gpointer instance, gpointer user_data)
594{
595 (void)instance;
596 (void)user_data;
598}
599
600static void _preview_cacheline_ready_callback(gpointer instance, const guint64 hash,
601 const guint64 producer_node_key, gpointer user_data)
602{
603 (void)instance;
604 (void)producer_node_key; // scope/module/picker buffer fetches match by producer node through
605 // the cache-wait manager; this backbuf-hash refresh trigger is exact-hash.
606 (void)user_data;
607
608 dt_develop_t *const dev = dt_dev_get_global();
609 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
611 {
614 }
615
617 {
620 }
621
623 {
626 }
627
628 for(GList *l = _preview_refresh_state.module_histograms; l;)
629 {
630 GList *next = g_list_next(l);
631 dt_histogram_pending_module_refresh_t *pending = l->data;
632 if(pending->hash == hash)
633 {
634 _refresh_preview_module_histogram_for_hash(dev, pending->module, pending->hash);
636 g_free(pending);
637 }
638 l = next;
639 }
640}
641
642const char *name(struct dt_lib_module_t *self)
643{
644 return _("scopes");
645}
646
647const char **views(dt_lib_module_t *self)
648{
649 static const char *v[] = {"darkroom", "studio_capture", NULL};
650 return v;
651}
652
654{
656}
657
659{
660 return 1;
661}
662
664{
665 return 1000;
666}
667
668static void _update_picker_output(dt_lib_module_t *self);
670static void _update_everything(dt_lib_module_t *self);
671
672
674{
675 switch(value)
676 {
677 case 0:
678 {
679 d->op = "initialscale";
680 break;
681 }
682 case 1:
683 {
684 d->op = "colorout";
685 break;
686 }
687 case 2:
688 {
689 d->op = "gamma";
690 }
691 }
692}
693
694
696{
697 if(!strcmp(d->op, "initialscale") || !strcmp(d->op, "demosaic")) return 0;
698 if(!strcmp(d->op, "colorout")) return 1;
699 if(!strcmp(d->op, "gamma")) return 2;
700 return 2;
701}
702
703
704#ifdef _OPENMP
705#pragma omp declare simd \
706 aligned(rgb_in, xyz_out:64) \
707 uniform(rgb_in, xyz_out)
708#endif
710{
711 if(_backbuf_op_to_int(d) > 0)
712 {
713 // We are in display RGB
715 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
716 profile->lutsize, profile->nonlinearlut);
717 }
718 else
719 {
720 // We are in sensor RGB
722 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
723 profile->lutsize, profile->nonlinearlut);
724 }
725}
726
727
728#ifdef _OPENMP
729#pragma omp declare simd \
730 aligned(rgb_in, rgb_out:64) \
731 uniform(rgb_in, rgb_out)
732#endif
734{
735 if(_backbuf_op_to_int(d) > 0)
736 {
737 // We are in display RGB
739 rgb_out[c] = rgb_in[c];
740 }
741 else
742 {
743 // We are in the sensor/input RGB profile. Convert directly between the
744 // cached stage profile and the display profile so the swatch patch uses
745 // the same authoritative ICC path as the rest of the GUI.
748 dt_ioppr_transform_image_colorspace_rgb(rgb_in, rgb_out, 1, 1, profile_in, profile_out,
749 "[histogram] sample swatch");
750 }
751}
752
754{
755 d->cache.view = DT_LIB_HISTOGRAM_SCOPE_N;
756 d->cache.width = -1;
757 d->cache.height = -1;
758 d->cache.hash = (uint64_t)-1;
759 d->cache.zoom = -1.;
760}
761
763{
764 if(IS_NULL_PTR(d) || !d->pending_hashes) return;
765 g_array_set_size(d->pending_hashes, 0);
766}
767
768static gboolean _has_pending_hash(const dt_lib_histogram_t *d, const uint64_t hash)
769{
770 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
771
772 for(guint i = 0; i < d->pending_hashes->len; i++)
773 if(g_array_index(d->pending_hashes, uint64_t, i) == hash) return TRUE;
774
775 return FALSE;
776}
777
779{
780 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID || _has_pending_hash(d, hash)) return;
781 g_array_append_val(d->pending_hashes, hash);
782}
783
785{
786 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
787
788 for(guint i = 0; i < d->pending_hashes->len; i++)
789 {
790 if(g_array_index(d->pending_hashes, uint64_t, i) == hash)
791 {
792 g_array_remove_index(d->pending_hashes, i);
793 return TRUE;
794 }
795 }
796
797 return FALSE;
798}
799
800static uint64_t _get_live_histogram_hash(const char *op)
801{
802 dt_develop_t *const dev = dt_dev_get_global();
803 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
805 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read the live module
806 * hashes without the mutex and rely on the cache peek manager to handle
807 * synchronization and retries for missing cachelines. */
808 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
810
813
814 if(!strcmp(op, "gamma"))
816
818 return hash;
819}
820
821static gboolean _histogram_refresh_idle(gpointer user_data)
822{
823 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
824 dt_lib_histogram_t *d = self ? self->data : NULL;
825 if(IS_NULL_PTR(d)) return G_SOURCE_REMOVE;
826
827 d->refresh_idle_source = 0;
828 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
829 _update_everything(self);
830 return G_SOURCE_REMOVE;
831}
832
834{
835 dt_lib_histogram_t *d = self ? self->data : NULL;
836 if(IS_NULL_PTR(d) || d->refresh_idle_source != 0) return;
837 d->refresh_idle_source = g_idle_add(_histogram_refresh_idle, self);
838}
839
841{
843 if(IS_NULL_PTR(d)) return;
844
847
848 for(GSList *samples = dt_dev_get_global()->color_picker.samples; samples; samples = g_slist_next(samples))
849 {
850 dt_colorpicker_sample_t *sample = samples->data;
851 if(sample->locked) continue;
853 }
854
855 if(dt_dev_get_global()->color_picker.picker)
857
858 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
859 _update_everything(self);
860}
861
862
863static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op);
864
879{
880 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->backbuf)) return FALSE;
881
882 return !IS_NULL_PTR(_get_backbuf_source_piece(d->backbuf, d->op));
883}
884
886{
887 gtk_widget_queue_draw(d->scope_draw);
888}
889
890uint32_t _find_max_histogram(const uint32_t *const restrict bins, const size_t binning_size)
891{
892 uint32_t max_hist = 0;
893 __OMP_PARALLEL_FOR_SIMD__(aligned(bins: 64) reduction(max: max_hist) )
894 for(size_t k = 0; k < binning_size; k++) if(bins[k] > max_hist) max_hist = bins[k];
895
896 return max_hist;
897}
898
899static inline void _sample_raw_box_to_image_norm(const dt_colorpicker_sample_t *const sample, float box[4])
900{
901 memcpy(box, sample->box, sizeof(float) * 4);
903}
904
905static inline void _sample_raw_point_to_image_norm(const dt_colorpicker_sample_t *const sample, float point[2])
906{
907 point[0] = sample->point[0];
908 point[1] = sample->point[1];
910}
911
912
913// `stride` is the row length of `image` in pixels (the source buffer width). Callers must never
914// pass a widget/panel dimension here: the binning window [min_x;max_x]×[min_y;max_y] is expressed
915// in source-buffer coordinates, and a foreign stride silently re-samples a sheared subset of the
916// image (issue #828: the histogram shape used to change with the left panel width).
917static inline void _bin_pixels_histogram_in_roi(const float *const restrict image, uint32_t *const restrict bins,
918 const size_t min_x, const size_t max_x,
919 const size_t min_y, const size_t max_y,
920 const size_t stride)
921{
922 //fprintf(stdout, "computing histogram from x = [%lu;%lu], y = [%lu;%lu]\n", min_x, max_x, min_y, max_y);
923 // Process
924#ifdef _OPENMP
925#ifndef _WIN32
926__OMP_PARALLEL_FOR__(reduction(+: bins[0: HISTOGRAM_BINS * 4]) collapse(3))
927#else
928__OMP_PARALLEL_FOR__(shared(bins) collapse(3))
929#endif
930#endif
931 for(size_t i = min_y; i < max_y; i++)
932 for(size_t j = min_x; j < max_x; j++)
933 for(size_t c = 0; c < 3; c++)
934 {
935 const float value = image[(i * stride + j) * 4 + c];
936 const size_t index = (size_t)CLAMP(roundf(value * (HISTOGRAM_BINS - 1)), 0, HISTOGRAM_BINS - 1);
937 bins[index * 4 + c]++;
938 }
939}
940
941
942static inline void _bin_pickers_histogram(const float *const restrict image,
943 const size_t width, const size_t height,
944 uint32_t *bins,
946{
947 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
948 {
949 float image_box[4] = { 0.0f };
950 _sample_raw_box_to_image_norm(sample, image_box);
951 const size_t box[4] = {
952 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
953 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
954 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
955 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
956 };
957 _bin_pixels_histogram_in_roi(image, bins, box[0], box[2], box[1], box[3], width);
958 }
959 else
960 {
961 float image_point[2] = { 0.0f };
962 _sample_raw_point_to_image_norm(sample, image_point);
963 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
964 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
965 _bin_pixels_histogram_in_roi(image, bins, x, x + 1, y, y + 1, width);
966 }
967}
968
970{
971 if(IS_NULL_PTR(d)) return FALSE;
972 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
973 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
974 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
975 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->picker_button));
976 return restrict_active && picker_active;
977}
978
989{
990 const float *data;
991 size_t width;
992 size_t height;
993 gboolean owned;
995
996static dt_histogram_scope_buf_t _orient_scope_buf(const void *data, const dt_backbuf_t *backbuf,
997 const char *op)
998{
999 dt_histogram_scope_buf_t result = { .data = (const float *)data, .width = backbuf->width,
1000 .height = backbuf->height, .owned = FALSE };
1001
1002 const dt_image_orientation_t orientation = g_strcmp0(op, "initialscale")
1004 if(orientation == ORIENTATION_NONE) return result;
1005
1006 const size_t src_w = backbuf->width;
1007 const size_t src_h = backbuf->height;
1008 const size_t dst_w = (orientation & ORIENTATION_SWAP_XY) ? src_h : src_w;
1009 const size_t dst_h = (orientation & ORIENTATION_SWAP_XY) ? src_w : src_h;
1010 const size_t bpp = 4 * sizeof(float);
1011
1012 float *const oriented = dt_alloc_align(dst_w * dst_h * bpp);
1013 if(IS_NULL_PTR(oriented)) return result;
1014
1015 /* wd/ht and fwd/fht are always the *source* (pre-orientation) dimensions here -- this mirrors
1016 * iop/flip.c's own process(), the only other real caller of this function with a non-identity
1017 * orientation: fwd/fht only feed the internal mirror-offset math, not the output layout, which
1018 * dt_imageio_flip_buffers derives on its own from wd/ht and the orientation bits. */
1019 dt_imageio_flip_buffers((char *)oriented, (const char *)data, bpp, (int)src_w, (int)src_h,
1020 (int)src_w, (int)src_h, (int)(src_w * bpp), orientation);
1021
1022 result.data = oriented;
1023 result.width = dst_w;
1024 result.height = dst_h;
1025 result.owned = TRUE;
1026 return result;
1027}
1028
1029static void _process_histogram(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1030 const int height, dt_lib_histogram_t *d)
1031{
1032 // Histogram backbuffers already own their keepalive ref in the pipeline state. Drawing only borrows them.
1033 struct dt_pixel_cache_entry_t *entry = NULL;
1034 void *data = NULL;
1035 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1036 if(IS_NULL_PTR(piece)) return;
1037 if(!IS_NULL_PTR(d))
1038 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1039 if(!dt_dev_pixelpipe_cache_peek_gui(dt_dev_get_global()->preview_pipe, piece, &data, &entry,
1040 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
1042 !IS_NULL_PTR(d) ? d->module : NULL))
1043 {
1044 return;
1045 }
1046
1048
1050
1051 uint32_t *bins = calloc(4 * HISTOGRAM_BINS, sizeof(uint32_t));
1052 if(IS_NULL_PTR(bins))
1053 {
1054 if(oriented.owned) dt_free_align(oriented.data);
1056 return;
1057 }
1058
1059 const gboolean restrict_mode = _is_restricted(d);
1060 if(restrict_mode)
1061 {
1062 // Bin only areas within color pickers
1064 while(samples)
1065 {
1066 dt_colorpicker_sample_t *sample = samples->data;
1068 bins, sample);
1069 samples = g_slist_next(samples);
1070 }
1071
1072 if(dt_dev_get_global()->color_picker.picker)
1074 bins, dt_dev_get_global()->color_picker.primary_sample);
1075 }
1076 else
1077 {
1078 _bin_pixels_histogram_in_roi(oriented.data, bins, 0, oriented.width, 0, oriented.height, oriented.width);
1079 }
1080
1081 if(oriented.owned) dt_free_align(oriented.data);
1083
1085
1086 // Draw thingy
1087 if(overall_histogram_max > 0)
1088 {
1089 // Paint background
1090 cairo_rectangle(cr, 0, 0, width, height);
1091 set_color(cr, dt_bauhaus_get_global()->graph_bg);
1092 cairo_fill(cr);
1093
1094 set_color(cr, dt_bauhaus_get_global()->graph_grid);
1095 dt_draw_grid(cr, 4, 0, 0, width, height);
1096
1097 cairo_save(cr);
1099 cairo_translate(cr, 0, height);
1100 cairo_scale(cr, width / 255.0, - (double)height / (double)(1. + log(overall_histogram_max)));
1102
1103 for(int k = 0; k < 3; k++)
1104 {
1105 set_color(cr, dt_bauhaus_get_global()->graph_colors[k]);
1106 dt_draw_histogram_8(cr, bins, 4, k, FALSE);
1107 }
1108
1111 cairo_paint_with_alpha(cr, 0.5);
1112 cairo_restore(cr);
1113 }
1114
1115 dt_free(bins);
1116}
1117
1118
1119static inline void _bin_pixels_waveform_in_roi(const float *const restrict image, uint32_t *const restrict bins,
1120 const size_t min_x, const size_t max_x,
1121 const size_t min_y, const size_t max_y,
1122 const size_t source_width, const size_t source_height,
1123 const size_t tone_bins, const size_t raster_extent,
1124 const gboolean vertical)
1125{
1126 if(vertical)
1127 {
1128 /* In vertical waveform/parade, pixel value is drawn along the widget width and source image
1129 * position along the widget height. We therefore loop on final raster rows and collect the
1130 * source rows that map there, so both axes have the same detail as the drawn surface. */
1132 for(size_t raster_y = 0; raster_y < raster_extent; raster_y++)
1133 {
1134 const double source_y0d = (double)raster_y * (double)source_height / (double)raster_extent;
1135 const double source_y1d = (double)(raster_y + 1) * (double)source_height / (double)raster_extent;
1136 const size_t source_y0 = MAX(min_y, (size_t)floor(source_y0d));
1137 const size_t source_y1 = MIN(max_y, (size_t)ceil(source_y1d));
1138 if(source_y0 >= source_y1) continue;
1139
1140 for(size_t i = source_y0; i < source_y1; i++)
1141 {
1142 const double overlap = MIN((double)(i + 1), source_y1d) - MAX((double)i, source_y0d);
1143 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1145 for(size_t j = min_x; j < max_x; j++)
1146 for(size_t c = 0; c < 3; c++)
1147 {
1148 const float value = image[(i * source_width + j) * 4 + c];
1149 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1150 const size_t tone0 = (size_t)floorf(tone_position);
1151 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1152 const float tone_mix = tone_position - (float)tone0;
1153 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1154 const uint32_t weight0 = weight - weight1;
1155 bins[(raster_y * tone_bins + tone0) * 4 + c] += weight0;
1156 bins[(raster_y * tone_bins + tone1) * 4 + c] += weight1;
1157 }
1158 }
1159 }
1160 }
1161 else
1162 {
1163 /* In horizontal waveform/parade, pixel value is drawn along the widget height and source image
1164 * position along the widget width. Each final raster column owns disjoint bins, avoiding races. */
1166 for(size_t raster_x = 0; raster_x < raster_extent; raster_x++)
1167 {
1168 const double source_x0d = (double)raster_x * (double)source_width / (double)raster_extent;
1169 const double source_x1d = (double)(raster_x + 1) * (double)source_width / (double)raster_extent;
1170 const size_t source_x0 = MAX(min_x, (size_t)floor(source_x0d));
1171 const size_t source_x1 = MIN(max_x, (size_t)ceil(source_x1d));
1172 if(source_x0 >= source_x1) continue;
1173
1174 for(size_t j = source_x0; j < source_x1; j++)
1175 {
1176 const double overlap = MIN((double)(j + 1), source_x1d) - MAX((double)j, source_x0d);
1177 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1179 for(size_t i = min_y; i < max_y; i++)
1180 for(size_t c = 0; c < 3; c++)
1181 {
1182 const float value = image[(i * source_width + j) * 4 + c];
1183 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1184 const size_t tone0 = (size_t)floorf(tone_position);
1185 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1186 const float tone_mix = tone_position - (float)tone0;
1187 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1188 const uint32_t weight0 = weight - weight1;
1189 bins[((tone_bins - 1 - tone0) * raster_extent + raster_x) * 4 + c] += weight0;
1190 bins[((tone_bins - 1 - tone1) * raster_extent + raster_x) * 4 + c] += weight1;
1191 }
1192 }
1193 }
1194 }
1195}
1196
1197static inline void _bin_pickers_waveforms(const float *const restrict image, uint32_t *const restrict bins,
1198 const size_t width, const size_t height,
1199 const size_t tone_bins, const size_t raster_extent,
1200 const gboolean vertical, dt_colorpicker_sample_t *sample)
1201{
1202 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1203 {
1204 float image_box[4] = { 0.0f };
1206 const size_t box[4] = {
1207 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1208 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1209 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1210 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1211 };
1212 _bin_pixels_waveform_in_roi(image, bins, box[0], box[2], box[1], box[3],
1213 width, height, tone_bins, raster_extent, vertical);
1214 }
1215 else
1216 {
1217 float image_point[2] = { 0.0f };
1219 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1220 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1221 _bin_pixels_waveform_in_roi(image, bins, x, x + 1, y, y + 1,
1222 width, height, tone_bins, raster_extent, vertical);
1223 }
1224}
1225
1226
1227static inline void _bin_pixels_waveform(const float *const restrict image, uint32_t *const restrict bins,
1228 const size_t width, const size_t height, const size_t binning_size,
1229 const size_t tone_bins, const size_t raster_extent,
1230 const gboolean vertical, const gboolean restricted)
1231{
1232 // Init
1234 for(size_t k = 0; k < binning_size; k++) bins[k] = 0;
1235
1236 if(restricted)
1237 {
1238 // Bin only areas within color pickers
1240 while(samples)
1241 {
1242 dt_colorpicker_sample_t *sample = samples->data;
1243 _bin_pickers_waveforms(image, bins, width, height, tone_bins, raster_extent, vertical, sample);
1244 samples = g_slist_next(samples);
1245 }
1246
1247 if(dt_dev_get_global()->color_picker.picker)
1249 dt_dev_get_global()->color_picker.primary_sample);
1250 }
1251 else
1252 {
1253 // Bin the whole image
1255 width, height, tone_bins, raster_extent, vertical);
1256 }
1257}
1258
1259static void _create_waveform_image(const uint32_t *const restrict bins, uint8_t *const restrict image,
1260 const uint32_t max_hist,
1261 const size_t width, const size_t height)
1262{
1263 __OMP_FOR_SIMD__(aligned(image, bins: 64) )
1264 for(size_t k = 0; k < height * width * 4; k += 4)
1265 {
1266 image[k + 3] = 255; // alpha
1267
1268 // We apply a slight "gamma" boost for legibility
1269 image[k + 2] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 0] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1270 image[k + 1] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 1] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1271 image[k + 0] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 2] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1272 }
1273}
1274
1275static void _paint_waveform(cairo_t *cr, uint8_t *const restrict image, const int width, const int height,
1276 const size_t img_width, const size_t img_height, const size_t tone_bins,
1277 const gboolean vertical)
1278{
1279 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1281 const double scale_w = (vertical) ? (double)width / (double)tone_bins
1282 : (double)width / (double)img_width;
1283 const double scale_h = (vertical) ? (double)height / (double)img_height
1284 : (double)height / (double)tone_bins;
1289 cairo_paint(cr);
1291}
1292
1293
1294static void _process_waveform(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1295 const int height, const gboolean vertical, const gboolean parade,
1297{
1298 struct dt_pixel_cache_entry_t *entry = NULL;
1299 void *data = NULL;
1300 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1301 if(IS_NULL_PTR(piece)) return;
1302 if(!IS_NULL_PTR(d))
1303 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1304 if(!dt_dev_pixelpipe_cache_peek_gui(dt_dev_get_global()->preview_pipe, piece, &data, &entry,
1305 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
1307 !IS_NULL_PTR(d) ? d->module : NULL))
1308 {
1309 return;
1310 }
1311
1313
1315
1316 /* The value axis must match the final drawing axis: widget height for horizontal scopes and
1317 * widget width for vertical scopes. Keeping it fixed forces Cairo to stretch sparse
1318 * value rows/columns, which shows as hatching when the scope is resized. */
1319 const size_t tone_bins = MAX(2, (vertical) ? width : height);
1320 /* The source-position axis must also match the final raster size. Otherwise Cairo stretches the
1321 * preview-pipe dimensions to the scope allocation, which can leave visible regular stripes. */
1322 const size_t raster_extent = MAX(2, (vertical)
1323 ? (parade ? (height + 2) / 3 : height)
1324 : (parade ? (width + 2) / 3 : width));
1325 const size_t binning_size = 4 * tone_bins * raster_extent;
1326 const size_t img_width = (parade) ? width : ((vertical) ? tone_bins : raster_extent);
1327 const size_t img_height = (parade) ? height : ((vertical) ? raster_extent : tone_bins);
1328 const size_t image_size = 4 * img_width * img_height;
1329 const size_t source_axis = (vertical) ? oriented.height : oriented.width;
1330 float source_min = INFINITY;
1331 float source_max = -INFINITY;
1332 size_t source_clipped = 0;
1333 size_t source_nan = 0;
1334 const size_t source_step = MAX((size_t)1, (oriented.width * oriented.height) / 4096);
1335 for(size_t pixel = 0; pixel < oriented.width * oriented.height; pixel += source_step)
1336 for(size_t c = 0; c < 3; c++)
1337 {
1338 const float value = oriented.data[pixel * 4 + c];
1339 if(isnan(value))
1340 source_nan++;
1341 else
1342 {
1345 source_clipped += (value < 0.f || value > 1.f);
1346 }
1347 }
1348 uint32_t *smooth_bins = NULL;
1349
1352 "[histogram/scope] waveform setup op=%s parade=%d vertical=%d widget=%dx%d backbuf=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT " "
1353 "tone_bins=%" G_GSIZE_FORMAT " raster_extent=%" G_GSIZE_FORMAT " source_axis=%" G_GSIZE_FORMAT
1354 " source_per_raster=%.4f binning_size=%" G_GSIZE_FORMAT " "
1355 "source_value=%0.6f..%0.6f clipped=%" G_GSIZE_FORMAT " nan=%" G_GSIZE_FORMAT " sample_step=%" G_GSIZE_FORMAT "\n",
1356 op, parade, vertical, width, height, oriented.width, oriented.height,
1360 binning_size * sizeof(uint32_t),
1361 0);
1362 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1363 image_size * sizeof(uint8_t),
1364 0);
1365 if(IS_NULL_PTR(image) || IS_NULL_PTR(bins))
1366 {
1369 "[histogram/scope] waveform allocation failed bins=%p image=%p binning_size=%" G_GSIZE_FORMAT
1370 " image_size=%" G_GSIZE_FORMAT "\n",
1371 (void *)bins, (void *)image, binning_size, image_size);
1372 goto error;
1373 }
1374
1375 // 1. Pixel binning along columns/rows, aka compute a column/row-wise histogram
1378
1379 const uint32_t *render_bins = bins;
1380 int smoothing_passes = 0;
1381#if DT_LIB_HISTOGRAM_SCOPE_ENABLE_SMOOTHING \
1382 && (DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES > 0 \
1383 || DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES > 0)
1387 {
1388 /* The waveform/parade is a spatially-indexed histogram. Even after fractional resampling,
1389 * isolated source-column content can create one-pixel density modulation that reads as stries.
1390 * The passes below are explicit at call site because the source/destination ownership matters:
1391 * bins and smooth_bins alternate roles, and render_bins always records the last valid raster. */
1392 const uint32_t original_force = 256u - smoothing_force;
1393
1395 {
1396 const uint32_t *const source_bins = render_bins;
1397 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1398
1399 if(vertical)
1400 {
1401 for(size_t axis = 0; axis < raster_extent; axis++)
1402 {
1403 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1404 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1405 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1406 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1407 for(size_t tone = 0; tone < tone_bins; tone++)
1408 for(size_t c = 0; c < 4; c++)
1409 {
1410 const size_t index = (axis * tone_bins + tone) * 4 + c;
1411 const uint64_t smoothed = source_bins[(axis_m2 * tone_bins + tone) * 4 + c]
1412 + 4u * source_bins[(axis_m1 * tone_bins + tone) * 4 + c]
1413 + 6u * source_bins[index]
1414 + 4u * source_bins[(axis_p1 * tone_bins + tone) * 4 + c]
1415 + source_bins[(axis_p2 * tone_bins + tone) * 4 + c];
1417 + smoothing_force * ((smoothed + 8u) / 16u);
1418 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1419 }
1420 }
1421 }
1422 else
1423 {
1424 for(size_t tone = 0; tone < tone_bins; tone++)
1425 for(size_t axis = 0; axis < raster_extent; axis++)
1426 {
1427 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1428 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1429 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1430 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1431 for(size_t c = 0; c < 4; c++)
1432 {
1433 const size_t index = (tone * raster_extent + axis) * 4 + c;
1434 const uint64_t smoothed = source_bins[(tone * raster_extent + axis_m2) * 4 + c]
1435 + 4u * source_bins[(tone * raster_extent + axis_m1) * 4 + c]
1436 + 6u * source_bins[index]
1437 + 4u * source_bins[(tone * raster_extent + axis_p1) * 4 + c]
1438 + source_bins[(tone * raster_extent + axis_p2) * 4 + c];
1440 + smoothing_force * ((smoothed + 8u) / 16u);
1441 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1442 }
1443 }
1444 }
1445
1448 }
1449
1451 {
1452 const uint32_t *const source_bins = render_bins;
1453 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1454
1455 if(vertical)
1456 {
1457 for(size_t axis = 0; axis < raster_extent; axis++)
1458 for(size_t tone = 0; tone < tone_bins; tone++)
1459 {
1460 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1461 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1462 for(size_t c = 0; c < 4; c++)
1463 {
1464 const size_t index = (axis * tone_bins + tone) * 4 + c;
1465 const uint64_t smoothed = source_bins[(axis * tone_bins + tone_m1) * 4 + c]
1466 + 2u * source_bins[index]
1467 + source_bins[(axis * tone_bins + tone_p1) * 4 + c];
1469 + smoothing_force * ((smoothed + 2u) / 4u);
1470 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1471 }
1472 }
1473 }
1474 else
1475 {
1476 for(size_t tone = 0; tone < tone_bins; tone++)
1477 {
1478 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1479 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1480 for(size_t axis = 0; axis < raster_extent; axis++)
1481 for(size_t c = 0; c < 4; c++)
1482 {
1483 const size_t index = (tone * raster_extent + axis) * 4 + c;
1484 const uint64_t smoothed = source_bins[(tone_m1 * raster_extent + axis) * 4 + c]
1485 + 2u * source_bins[index]
1486 + source_bins[(tone_p1 * raster_extent + axis) * 4 + c];
1488 + smoothing_force * ((smoothed + 2u) / 4u);
1489 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1490 }
1491 }
1492 }
1493
1496 }
1497 }
1498#endif
1499
1500 // 2. Paint image.
1501 // In a 1D histogram, pixel frequencies are shown as height (y axis) for each RGB quantum (x axis).
1502 // Here, we do a sort of 2D histogram : pixel frequencies are shown as opacity ("z" axis),
1503 // for each image column (x axis), for each RGB quantum (y axis)
1505 size_t empty_axis = 0;
1507 uint64_t max_axis = 0;
1508 uint32_t min_axis_peak = UINT32_MAX;
1509 uint32_t max_axis_peak = 0;
1510 for(size_t axis = 0; axis < raster_extent; axis++)
1511 {
1512 uint64_t axis_total = 0;
1513 uint32_t axis_peak = 0;
1514 for(size_t tone = 0; tone < tone_bins; tone++)
1515 {
1516 const size_t pixel = (vertical) ? axis * tone_bins + tone : tone * raster_extent + axis;
1517 for(size_t c = 0; c < 3; c++)
1518 {
1519 const uint32_t value = render_bins[pixel * 4 + c];
1520 axis_total += value;
1522 }
1523 }
1524
1525 if(axis_total == 0) empty_axis++;
1530 }
1531 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1533 "[histogram/scope] waveform raster op=%s parade=%d vertical=%d image=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT
1534 " stride=%" G_GSIZE_FORMAT " overall_max=%u empty_axis=%" G_GSIZE_FORMAT " axis_total=%" PRIu64 "..%" PRIu64
1535 " axis_peak=%u..%u smooth=%d scale=%0.4fx%0.4f\n",
1536 op, parade, vertical, img_width, img_height, stride, overall_max_hist,
1538 (double)width / (double)img_width, (double)height / (double)img_height);
1539
1540 // 3. Send everything to GUI buffer.
1541 if(overall_max_hist > 0)
1542 {
1543 if(parade)
1544 {
1545 /* Build the parade directly in the final widget raster. This keeps channel thirds on integer
1546 * device pixels and removes all Cairo source-offset/scaling interactions from the diagnostic
1547 * path: if stries remain, they are in the histogram bins, not in channel painting. */
1548 for(size_t k = 0; k < image_size; k++) image[k] = 0;
1549
1550 for(size_t c = 0; c < 3; c++)
1551 {
1552 const size_t x0 = vertical ? 0 : c * width / 3;
1553 const size_t x1 = vertical ? width : (c + 1) * width / 3;
1554 const size_t y0 = vertical ? c * height / 3 : 0;
1555 const size_t y1 = vertical ? (c + 1) * height / 3 : height;
1556 const size_t channel_width = x1 - x0;
1557 const size_t channel_height = y1 - y0;
1558 if(channel_width == 0 || channel_height == 0) continue;
1559
1560 for(size_t y = y0; y < y1; y++)
1561 for(size_t x = x0; x < x1; x++)
1562 {
1563 const size_t axis = vertical
1564 ? (y - y0) * raster_extent / channel_height
1565 : (x - x0) * raster_extent / channel_width;
1566 const size_t tone = vertical ? x : y;
1567 const size_t bin_index = vertical ? (axis * tone_bins + tone) * 4 + c
1568 : (tone * raster_extent + axis) * 4 + c;
1569 const uint8_t value = (uint8_t)CLAMP(roundf(powf((float)render_bins[bin_index] / (float)overall_max_hist, GAMMA) * 255.f), 0, 255);
1570 const size_t image_index = (y * img_width + x) * 4;
1571 image[image_index + 2 - c] = value;
1572 image[image_index + 3] = 255;
1573 }
1574 }
1575 }
1576 else
1577 _create_waveform_image(render_bins, image, overall_max_hist, img_width, img_height);
1578
1579 cairo_save(cr);
1580
1581 // Paint background - Color not exposed to user theme because this is tricky
1582 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1583 cairo_paint(cr);
1584
1598 cairo_set_source_rgb(cr, 0.21, 0.21, 0.21);
1599 const gboolean raw_stage = (!strcmp(op, "initialscale") || !strcmp(op, "demosaic"));
1600 if(raw_stage)
1601 {
1602 /* The regular 4x4 grid has a tonal guide at 0.75, which is not an integer raw bit stop.
1603 * In raw mode, non-parade scopes keep only the global spatial guides here; the tone guides
1604 * are drawn below from powers of two. Parade intentionally has no spatial guides. */
1605 for(int k = 1; !parade && k < 4; k++)
1606 {
1607 if(vertical)
1608 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1609 else
1610 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1611 cairo_stroke(cr);
1612 }
1613 }
1614 else if(parade)
1615 {
1616 /* Parade already splits the spatial axis into RGB strips. Keep only the regular tone-axis
1617 * guides here so the grid does not add misleading source-position lines inside each strip. */
1618 for(int k = 1; k < 4; k++)
1619 {
1620 if(vertical)
1621 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1622 else
1623 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1624 cairo_stroke(cr);
1625 }
1626 }
1627 else
1628 dt_draw_grid(cr, 4, 0, 0, width, height);
1629
1630 if(raw_stage)
1631 {
1632 /* Raw data is linear, so integer bit stops are powers of two in normalized tone space.
1633 * Drawing those stops on the tone axis makes exposure clipping and low-bit detail readable
1634 * without depending on the current scope height. */
1635 for(int stop = 0; stop <= 8; stop++)
1636 {
1637 const double value = 1.0 / (double)(1u << stop);
1638 if(value < (double)DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE) break;
1639
1640 if(vertical)
1641 {
1642 const double x = value * (double)width;
1643 dt_draw_line(cr, x, 0, x, height);
1644 }
1645 else
1646 {
1647 const double y = (1.0 - value) * (double)height;
1648 dt_draw_line(cr, 0, y, width, y);
1649 }
1650 cairo_stroke(cr);
1651 }
1652 }
1653
1654 _paint_waveform(cr, image, width, height, img_width, img_height, tone_bins, vertical);
1655
1656 cairo_restore(cr);
1657 }
1658
1659error:;
1663 if(oriented.owned) dt_free_align(oriented.data);
1665}
1666
1667static float _Luv_to_vectorscope_coord_zoom(const float value, const float zoom)
1668{
1669 // Convert u, v coordinates of Luv vectors into x, y coordinates
1670 // into the space of the vectorscope square buffer
1671 return (value + zoom) * (HISTOGRAM_BINS - 1) / (2.f * zoom);
1672}
1673
1674static float _vectorscope_coord_zoom_to_Luv(const float value, const float zoom)
1675{
1676 // Inverse of the above
1677 return value * (2.f * zoom) / (HISTOGRAM_BINS - 1) - zoom;
1678}
1679
1680static void _bin_pixels_vectorscope_in_roi(const float *const restrict image, uint32_t *const restrict vectorscope,
1681 const size_t min_x, const size_t max_x, const size_t min_y,
1682 const size_t max_y, const size_t width, const float zoom,
1684{
1685#ifdef _OPENMP
1686#ifndef _WIN32
1687#pragma omp parallel for default(firstprivate) \
1688 reduction(+: vectorscope[0: HISTOGRAM_BINS * HISTOGRAM_BINS])
1689#else
1690#pragma omp parallel for default(firstprivate) \
1691 shared(vectorscope)
1692#endif
1693#endif
1694 for(size_t i = min_y; i < max_y; i++)
1695 for(size_t j = min_x; j < max_x; j++)
1696 {
1697 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1698 dt_aligned_pixel_t xyY = { 0.f };
1699 dt_aligned_pixel_t Luv = { 0.f };
1700 _scope_pixel_to_xyz(image + (i * width + j) * 4, XYZ_D50, d);
1703
1704 // Luv is sampled between 0 and 100.0f, u and v between +/- 220.f
1705 const size_t U_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[1], zoom)), 0, HISTOGRAM_BINS - 1);
1706 const size_t V_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[2], zoom)), 0, HISTOGRAM_BINS - 1);
1707
1708 // We put V = 0 at the bottom of the image.
1710 }
1711}
1712
1713static inline void _bin_pickers_vectorscope(const float *const restrict image,
1714 uint32_t *const restrict vectorscope, const size_t width,
1715 const size_t height, const float zoom, dt_lib_histogram_t *d,
1716 const dt_colorpicker_sample_t *const sample)
1717{
1718 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1719 {
1720 float image_box[4] = { 0.0f };
1722 const size_t box[4] = {
1723 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1724 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1725 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1726 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1727 };
1728 _bin_pixels_vectorscope_in_roi(image, vectorscope, box[0], box[2], box[1], box[3], width, zoom, d);
1729 }
1730 else
1731 {
1732 float image_point[2] = { 0.0f };
1734 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1735 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1736 _bin_pixels_vectorscope_in_roi(image, vectorscope, x, x + 1, y, y + 1, width, zoom, d);
1737 }
1738}
1739
1740
1741static void _create_vectorscope_image(const uint32_t *const restrict vectorscope, uint8_t *const restrict image,
1742 const uint32_t max_hist, const float zoom)
1743{
1746 for(size_t i = 0; i < HISTOGRAM_BINS; i++)
1747 for(size_t j = 0; j < HISTOGRAM_BINS; j++)
1748 {
1749 const size_t index = (HISTOGRAM_BINS - 1 - i) * HISTOGRAM_BINS + j;
1750 const float value = sqrtf((float)vectorscope[index] / (float)max_hist);
1751 dt_aligned_pixel_t RGB = { 0.f };
1752 // RGB gamuts tend to have a max chroma around L = 67
1754 dt_aligned_pixel_t xyY = { 0.f };
1755 dt_aligned_pixel_t XYZ = { 0.f };
1757 for(int c = 0; c < 2; c++) xyY[c] = fmaxf(xyY[c], 0.f);
1759 for(int c = 0; c < 3; c++) XYZ[c] = fmaxf(XYZ[c], 0.f);
1761
1762 // We will normalize RGB to get closer to display peak emission
1763 for(int c = 0; c < 3; c++) RGB[c] = fmaxf(RGB[c], 0.f);
1764 const float max_RGB = fmax(RGB[0], fmaxf(RGB[1], RGB[2]));
1765 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1766
1767 image[index * 4 + 3] = (uint8_t)roundf(value * 255.f); // alpha
1768 // Premultiply alpha
1769 image[index * 4 + 2] = (uint8_t)roundf(powf(RGB[0] * value, 1.f / 2.2f) * 255.f);
1770 image[index * 4 + 1] = (uint8_t)roundf(powf(RGB[1] * value, 1.f / 2.2f) * 255.f);
1771 image[index * 4 + 0] = (uint8_t)roundf(powf(RGB[2] * value, 1.f / 2.2f) * 255.f);
1772 }
1773}
1774
1775static void _bin_vectorscope(const float *const restrict image, uint32_t *const vectorscope,
1776 const size_t width, const size_t height,
1777 const float zoom, dt_lib_histogram_t *d)
1778{
1780 for(size_t k = 0; k < HISTOGRAM_BINS * HISTOGRAM_BINS; k++) vectorscope[k] = 0;
1781
1782 if(_is_restricted(d))
1783 {
1784 // Bin only areas within color pickers
1786 while(samples)
1787 {
1788 dt_colorpicker_sample_t *sample = samples->data;
1789 _bin_pickers_vectorscope(image, vectorscope, width, height, zoom, d, sample);
1790 samples = g_slist_next(samples);
1791 }
1792
1793 if(dt_dev_get_global()->color_picker.picker)
1795 dt_dev_get_global()->color_picker.primary_sample);
1796 }
1797 else
1798 {
1799 // Bin the whole image
1801 }
1802}
1803
1804
1805static void _process_vectorscope(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1806 const int height, const float zoom, dt_lib_histogram_t *d)
1807{
1809 if(IS_NULL_PTR(profile) || IS_NULL_PTR(d)) return;
1810
1811 struct dt_pixel_cache_entry_t *entry = NULL;
1812 void *data = NULL;
1813 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1814 if(IS_NULL_PTR(piece)) return;
1815 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1816 if(!dt_dev_pixelpipe_cache_peek_gui(dt_dev_get_global()->preview_pipe, piece, &data, &entry, &d->scope_wait,
1818 {
1819 return;
1820 }
1821
1823
1825 HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint32_t),
1826 0);
1827 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1828 4 * HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint8_t),
1829 0);
1830 if(IS_NULL_PTR(vectorscope) || IS_NULL_PTR(image)) goto error;
1831
1832 _bin_vectorscope(data, vectorscope, backbuf->width, backbuf->height, zoom, d);
1833
1836
1837 // 2. Draw
1838 if(max_hist > 0)
1839 {
1842 const int side = MIN(width, height);
1843 cairo_translate(cr, (double)(width - side) / 2., (double)(height - side) / 2.);
1844 cairo_scale(cr, (double)side / HISTOGRAM_BINS, (double)side / HISTOGRAM_BINS);
1845
1846 const double radius = (float)(HISTOGRAM_BINS - 1) / 2 - DT_PIXEL_APPLY_DPI(1.);
1847 const double x_center = (float)(HISTOGRAM_BINS - 1) / 2;
1848
1849 // Background circle - Color will not be exposed to user theme because this is tricky
1850 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1851 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1852 cairo_fill(cr);
1853
1854 // Center circle
1855 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1856 cairo_arc(cr, x_center, x_center, 2., 0., 2. * M_PI);
1857 cairo_fill(cr);
1858
1859 // Concentric circles
1860 for(int k = 0; k < 4; k++)
1861 {
1862 cairo_arc(cr, x_center, x_center, (double)k * HISTOGRAM_BINS / 8., 0., 2. * M_PI);
1863 cairo_stroke(cr);
1864 }
1865
1866 // RGB space primaries and secondaries
1867 dt_aligned_pixel_t colors[6] = { { 1.f, 0.f, 0.f, 0.f }, { 1.f, 1.f, 0.f, 0.f }, { 0.f, 1.f, 0.f, 0.f },
1868 { 0.f, 1.f, 1.f, 0.f }, { 0.f, 0.f, 1.f, 0.f }, { 1.f, 0.f, 1.f, 0.f } };
1869
1870 cairo_save(cr);
1871 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1872 cairo_clip(cr);
1873
1874 for(size_t k = 0; k < 6; k++)
1875 {
1876 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1877 dt_aligned_pixel_t xyY = { 0.f };
1878 dt_aligned_pixel_t Luv = { 0.f };
1879 dt_ioppr_rgb_matrix_to_xyz(colors[k], XYZ_D50, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
1880 profile->lutsize, profile->nonlinearlut);
1883
1884 const double x = _Luv_to_vectorscope_coord_zoom(Luv[1], zoom);
1885 // Remember v = 0 is at the bottom of the square while Cairo puts y = 0 on top
1886 const double y = HISTOGRAM_BINS - 1 - _Luv_to_vectorscope_coord_zoom(Luv[2], zoom);
1887
1888 // First, draw hue angles
1889 dt_aligned_pixel_t Lch = { 0.f };
1891
1892 const double delta_x = radius * cosf(Lch[2]);
1893 const double delta_y = radius * sinf(Lch[2]);
1894 const double destination_x = x_center + delta_x;
1895 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1898 cairo_set_source_rgba(cr, colors[k][0], colors[k][1], colors[k][2], 0.5);
1899 cairo_stroke(cr);
1900
1901 // Then draw color squares and ensure center is filled with scope background color
1902 const double half_square = DT_PIXEL_APPLY_DPI(4);
1903 cairo_arc(cr, x, y, half_square, 0, 2. * M_PI);
1904 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1906 cairo_set_source_rgb(cr, colors[k][0], colors[k][1], colors[k][2]);
1907 cairo_stroke(cr);
1908 }
1909 cairo_restore(cr);
1910
1911 // Hues ring
1912 cairo_save(cr);
1913 cairo_arc(cr, x_center, x_center, radius - DT_PIXEL_APPLY_DPI(1.), 0., 2. * M_PI);
1914 cairo_set_source_rgb(cr, 0.33, 0.33, 0.33);
1916 cairo_stroke(cr);
1917 cairo_restore(cr);
1918
1919 for(size_t h = 0; h < 180; h++)
1920 {
1921 dt_aligned_pixel_t Lch = { 50.f, 110.f, h / 180.f * 2.f * M_PI_F, 1.f };
1922 dt_aligned_pixel_t Luv = { 0.f };
1923 dt_aligned_pixel_t xyY = { 0.f };
1924 dt_aligned_pixel_t XYZ = { 0.f };
1925 dt_aligned_pixel_t RGB = { 0.f };
1930 const float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
1931 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1932 const double delta_x = (radius - DT_PIXEL_APPLY_DPI(1.)) * cosf(Lch[2]);
1933 const double delta_y = (radius - DT_PIXEL_APPLY_DPI(1.)) * sinf(Lch[2]);
1934 const double destination_x = x_center + delta_x;
1935 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1936 cairo_set_source_rgba(cr, RGB[0], RGB[1], RGB[2], 0.7);
1938 cairo_fill(cr);
1939 }
1940
1941 // Actual vectorscope
1942 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1943 cairo_clip(cr);
1946 cairo_paint(cr);
1948
1949 // Draw the skin tones area
1950 // Values obtained with :
1951 // get_skin_tones_range();
1952 const float max_c = 49.34f;
1953 const float min_c = 9.00f;
1954 const float max_h = 0.99f;
1955 const float min_h = 0.26f;
1956
1957 const float n_w_x = min_c * cosf(max_h);
1958 const float n_w_y = min_c * sinf(max_h);
1959 const float n_e_x = max_c * cosf(max_h);
1960 const float n_e_y = max_c * sinf(max_h);
1961 const float s_e_x = max_c * cosf(min_h);
1962 const float s_e_y = max_c * sinf(min_h);
1963 const float s_w_x = min_c * cosf(min_h);
1964 const float s_w_y = min_c * sinf(min_h);
1975 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1976 cairo_stroke(cr);
1977 }
1978
1979error:;
1983}
1984
1985gboolean _needs_recompute(dt_lib_histogram_t *d, const int width, const int height)
1986{
1987 // Check if cache is up-to-date
1989 gboolean hash_match = (d->cache.hash == dt_dev_backbuf_get_hash(d->backbuf));
1990 gboolean size_match = (d->cache.width == width && d->cache.height == height);
1991 gboolean zoom_match = (d->cache.zoom == d->zoom);
1992 gboolean view_match = (d->cache.view == view);
1993 gboolean has_surface = (!IS_NULL_PTR(d->cst));
1995}
1996
1997
1998static gboolean _draw_callback(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1999{
2000 // Note: the draw callback is called from our own callback (mapped to "preview pipe finished recomputing" signal)
2001 // but is also called by Gtk when the main window is resized, exposed, etc.
2002 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2003 if(IS_NULL_PTR(d->cst)) return 1;
2004 cairo_set_source_surface(crf, d->cst, 0, 0);
2006 return 0;
2007}
2008
2009
2011{
2012 GtkAllocation allocation;
2013 gtk_widget_get_allocation(d->scope_draw, &allocation);
2014 *width = allocation.width;
2015 *height = allocation.height;
2016}
2017
2042{
2043 if(_is_restricted(d))
2044 {
2045 cairo_save(cr);
2047
2052 pango_layout_set_text(layout, _("Restricted"), -1);
2053
2054 PangoRectangle ink;
2055 PangoRectangle logical;
2057
2058 const double padding = DT_PIXEL_APPLY_DPI(2.);
2059 const double text_padding = DT_PIXEL_APPLY_DPI(1.5);
2060 const double label_x = padding;
2061 const double label_y = MAX(padding, height - logical.height - 2. * text_padding - padding);
2062
2063 set_color(cr, dt_bauhaus_get_global()->graph_scope_restricted);
2065 pango_cairo_show_layout(cr, layout);
2066
2068 g_object_unref(layout);
2069 cairo_restore(cr);
2070 }
2071}
2072
2074{
2075 if(IS_NULL_PTR(d->cst)) return 1;
2076
2077 dt_times_t start;
2078 dt_get_times(&start);
2079
2080 int width, height;
2082
2083 // Save cache integrity
2084 d->cache.hash = dt_dev_backbuf_get_hash(d->backbuf);
2085 d->cache.width = width;
2086 d->cache.height = height;
2087 d->cache.zoom = d->zoom;
2088 d->cache.view = d->scope;
2089
2090 cairo_t *cr = cairo_create(d->cst);
2091
2092 // Paint background
2094 cairo_set_line_width(cr, 1.); // we want exactly 1 px no matter the resolution
2095
2096 // Paint content
2097 switch(d->scope)
2098 {
2100 {
2101 _process_histogram(d->backbuf, d->op, cr, width, height, d);
2102 break;
2103 }
2105 {
2106 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, FALSE, d);
2107 break;
2108 }
2110 {
2111 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, FALSE, d);
2112 break;
2113 }
2115 {
2116 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, TRUE, d);
2117 break;
2118 }
2120 {
2121 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, TRUE, d);
2122 break;
2123 }
2125 {
2126 _process_vectorscope(d->backbuf, d->op, cr, width, height, d->zoom, d);
2127 break;
2128 }
2129 default:
2130 break;
2131 }
2132
2134
2135 cairo_destroy(cr);
2136 dt_show_times_f(&start, "[histogram]", "redraw");
2137 return 0;
2138}
2139
2141{
2142 if(d->cst && cairo_surface_get_reference_count(d->cst) > 0) cairo_surface_destroy(d->cst);
2143 if(d->cst && cairo_surface_get_reference_count(d->cst) == 0) d->cst = NULL;
2144}
2145
2147{
2148 int width, height;
2150
2152 {
2154 // If width and height have changed, we need to recreate the surface.
2155 // Recreate it anyway.
2158 // Don't send gtk_queue_redraw event from here, catch the return value and do it in the calling function
2159 //fprintf(stdout, "recreate histograms\n");
2160 return 1;
2161 }
2162
2163 return 0;
2164}
2165
2166static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op)
2167{
2168 dt_develop_t *const dev = dt_dev_get_global();
2169 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2170 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(op) || IS_NULL_PTR(backbuf)) return NULL;
2171
2172 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2173 if(IS_NULL_PTR(module)) return NULL;
2174
2176 if(IS_NULL_PTR(piece)) return NULL;
2177
2178 if(!strcmp(op, "gamma"))
2180
2181 if(IS_NULL_PTR(piece) || piece->global_hash != dt_dev_backbuf_get_hash(backbuf)) return NULL;
2182 return piece;
2183}
2184
2207static gboolean _resolve_backbuf_sampling_source(const char *const op, const dt_backbuf_t *const backbuf,
2208 dt_iop_roi_t *const roi, dt_iop_buffer_dsc_t *const dsc,
2209 double *const iop_order, int *const direction)
2210{
2211 dt_develop_t *const dev = dt_dev_get_global();
2212 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2213 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(op) || IS_NULL_PTR(backbuf) || IS_NULL_PTR(roi) || IS_NULL_PTR(dsc) || !iop_order || IS_NULL_PTR(direction)) return FALSE;
2214 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read live module state
2215 * without blocking and let the cache peek manager handle synchronization. */
2216 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2217 if(IS_NULL_PTR(module)) return FALSE;
2218
2220 if(IS_NULL_PTR(piece)) return FALSE;
2221
2222 uint64_t hash = piece->global_hash;
2223 *roi = piece->roi_out;
2224 *dsc = piece->dsc_out;
2225 *iop_order = module->iop_order;
2227
2228 if(!strcmp(op, "gamma"))
2229 {
2231 if(IS_NULL_PTR(previous_piece)) return FALSE;
2232
2233 hash = previous_piece->global_hash;
2234 *roi = previous_piece->roi_out;
2235 *dsc = previous_piece->dsc_out;
2237 }
2238
2239 const gboolean valid = hash == dt_dev_backbuf_get_hash(backbuf);
2240 return valid;
2241}
2242
2243static void _pixelpipe_pick_from_image(const dt_backbuf_t *const backbuf,
2245 const char *op)
2246{
2247 struct dt_pixel_cache_entry_t *entry = NULL;
2248 void *data = NULL;
2250 if(IS_NULL_PTR(source_piece)) return;
2251 dt_dev_pixelpipe_cache_wait_set_owner(&d->picker_wait, "histogram-picker", d->module);
2253 &d->picker_wait, _histogram_restart_cache_wait, d->module))
2254 {
2255 return;
2256 }
2257
2259
2260 const float *const pixel = data;
2261 dt_iop_buffer_dsc_t dsc = {
2262 .channels = 4,
2263 .datatype = TYPE_FLOAT,
2264 .bpp = 4 * sizeof(float),
2265 .cst = IOP_CS_RGB
2266 };
2267 dt_iop_roi_t roi = {
2268 .x = 0,
2269 .y = 0,
2270 .width = (int)backbuf->width,
2271 .height = (int)backbuf->height,
2272 .scale = 1.0
2273 };
2274 double iop_order = 0.0;
2276 const gboolean have_live_stage = _resolve_backbuf_sampling_source(op, backbuf, &roi, &dsc, &iop_order, &direction);
2277 int box[4];
2278
2279 if(have_live_stage)
2280 {
2281 dt_boundingbox_t fbox = { 0.0f };
2282
2283 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2284 {
2287 }
2288 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2289 {
2292 fbox[2] = fbox[0];
2293 fbox[3] = fbox[1];
2294 }
2295
2296 dt_dev_distort_backtransform_plus(dt_dev_get_global()->preview_pipe, iop_order,
2297 direction, fbox, 2);
2298
2299 /* The backtransform returns stage coordinates in the sampled piece space. Shift them by the
2300 stage ROI before clamping so the picker box matches the published cache window exactly. */
2301 fbox[0] -= roi.x;
2302 fbox[1] -= roi.y;
2303 fbox[2] -= roi.x;
2304 fbox[3] -= roi.y;
2305
2306 box[0] = fminf(fbox[0], fbox[2]);
2307 box[1] = fminf(fbox[1], fbox[3]);
2308 box[2] = fmaxf(fbox[0], fbox[2]);
2309 box[3] = fmaxf(fbox[1], fbox[3]);
2310
2311 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2312 {
2313 box[2] += 1;
2314 box[3] += 1;
2315 }
2316
2317 box[0] = MIN(roi.width - 1, MAX(0, box[0]));
2318 box[1] = MIN(roi.height - 1, MAX(0, box[1]));
2319 box[2] = MIN(roi.width - 1, MAX(0, box[2]));
2320 box[3] = MIN(roi.height - 1, MAX(0, box[3]));
2321 }
2322 else
2323 {
2325 return;
2326 }
2327
2328 dt_aligned_pixel_t mean = { 0.0f };
2331
2332 dt_color_picker_helper(&dsc, pixel, &roi, box, mean, min, max, dsc.cst, IOP_CS_RGB, NULL);
2333
2335 {
2336 sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k] = mean[k];
2339 }
2340
2341 dt_lib_histogram_t scope = *d;
2342 scope.op = op;
2343
2345 {
2346 _scope_pixel_to_display_rgb(sample->scope[k], sample->display[k], &scope);
2347
2348 dt_aligned_pixel_t XYZ = { 0.f };
2349 _scope_pixel_to_xyz(sample->scope[k], XYZ, &scope);
2350 dt_XYZ_to_Lab(XYZ, sample->lab[k]);
2351 }
2352
2354}
2355
2357{
2359 while(samples)
2360 {
2361 dt_colorpicker_sample_t *sample = samples->data;
2362 if(!sample->locked)
2363 {
2364 const char *const op = sample->backbuf_op[0] ? sample->backbuf_op : d->op;
2366 if(backbuf) _pixelpipe_pick_from_image(backbuf, sample, d, op);
2367 }
2368 samples = g_slist_next(samples);
2369 }
2370
2371 if(dt_dev_get_global()->color_picker.picker)
2372 _pixelpipe_pick_from_image(d->backbuf, dt_dev_get_global()->color_picker.primary_sample, d, d->op);
2373}
2374
2375
2377{
2379
2381 {
2383 }
2384
2386
2387 for(GSList *samples = dt_dev_get_global()->color_picker.samples;
2388 samples;
2389 samples = g_slist_next(samples))
2390 {
2391 _update_sample_label(self, samples->data);
2392 }
2393
2395
2396 // allow live sample button to work for iop samples
2397 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button),
2398 !IS_NULL_PTR(dt_dev_get_global()->color_picker.picker));
2399}
2400
2402{
2403 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return FALSE;
2404
2406 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
2407
2408 if(!_is_backbuf_ready(d)) return FALSE;
2409
2410 /* In restricted mode the scope bins only picker rectangles/points. Moving the
2411 * global picker does not change the preview cache hash, so invalidate the
2412 * Cairo scope cache explicitly before recomputing from the same backbuffer. */
2413 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2414 _reset_cache(d);
2415
2416 _update_everything(self);
2417 return TRUE;
2418}
2419
2420
2421static void _lib_histogram_history_resync_callback(gpointer instance, dt_lib_module_t *self)
2422{
2423 (void)instance;
2425}
2426
2427static void _lib_histogram_cacheline_ready_callback(gpointer instance, const guint64 hash,
2428 const guint64 producer_node_key, dt_lib_module_t *self)
2429{
2430 (void)instance;
2433 if(IS_NULL_PTR(d) || !_remove_pending_hash(d, hash)) return;
2434
2436}
2437
2438// this is only called in darkroom view when history was resynchronized
2453
2475
2476
2477static void _set_stage(dt_lib_module_t *self, int value)
2478{
2481 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2482
2483 // Vectorscope is meaningless for raw Bayer data
2484 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2485 {
2487 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2488 }
2489
2492}
2493
2495{
2497 d->scope = scope;
2498 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2500}
2501
2502
2504{
2505 _reset_cache(d);
2507 // Don't start a redraw from here, Gtk does it automatically on resize event
2508}
2509
2511{
2512 if(d->scope != DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE) return FALSE;
2513
2514 int delta_y = 0;
2515 if(!dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y)) return TRUE;
2516
2517 const float new_value = 4.f * delta_y + d->zoom;
2518
2520 {
2521 d->zoom = new_value;
2522 dt_conf_set_float("plugin/darkroom/histogram/zoom", new_value);
2523 if(_is_backbuf_ready(d))
2524 {
2527 }
2528 }
2529 return TRUE;
2530}
2531
2532static int _scope_resize_handle_get_size(gpointer user_data)
2533{
2534 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2535 return gtk_widget_get_allocated_height(d->scope_draw);
2536}
2537
2545static int _scope_resize_handle_resize(int requested_size, gboolean finished, gpointer user_data)
2546{
2547 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2548 int window_height;
2550
2552 const int max_height = MAX(min_height, MIN(DT_PIXEL_APPLY_DPI(DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT),
2553 window_height * 3 / 4));
2554 d->scope_height = CLAMP(requested_size, min_height, max_height);
2555
2556 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
2557 gtk_widget_queue_resize(d->scope_draw);
2558 if(finished)
2560
2561 return d->scope_height;
2562}
2563
2565{
2566 d->op = dt_conf_get_string_const("plugin/darkroom/histogram/op");
2567 if(!strcmp(d->op, "demosaic"))
2568 {
2569 d->op = "initialscale";
2570 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2571 }
2572 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
2573 d->zoom = fminf(fmaxf(dt_conf_get_float("plugin/darkroom/histogram/zoom"), 32.f), 252.f);
2574
2576 dt_conf_get_int("plugin/darkroom/histogram/display"), 0, DT_LIB_HISTOGRAM_SCOPE_N - 1);
2577
2578 // Vectorscope is not valid on raw Bayer data; fall back to histogram
2579 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2581}
2582
2583
2585{
2586 const guint width = gtk_widget_get_allocated_width(widget);
2587 const guint height = gtk_widget_get_allocated_height(widget);
2588
2589 set_color(cr, sample->swatch);
2590 cairo_rectangle(cr, 0, 0, width, height);
2591 cairo_fill (cr);
2592
2593 // if the sample is locked we want to add a lock
2594 if(sample->locked)
2595 {
2596 const int border = DT_PIXEL_APPLY_DPI(2);
2597 const int icon_width = width - 2 * border;
2598 const int icon_height = height - 2 * border;
2599 if(icon_width > 0 && icon_height > 0)
2600 {
2601 GdkRGBA fg_color;
2603
2605 dtgtk_cairo_paint_lock(cr, border, border, icon_width, icon_height, 0, NULL);
2606 }
2607 }
2608
2609 return FALSE;
2610}
2611
2613{
2614 dt_lib_histogram_t *d = self->data;
2615 const dt_lib_colorpicker_statistic_t statistic
2617
2618 sample->swatch.red = sample->display[statistic][0];
2619 sample->swatch.green = sample->display[statistic][1];
2620 sample->swatch.blue = sample->display[statistic][2];
2622 sample->label_rgb[ch] = (int)roundf(sample->scope[statistic][ch] * 255.f);
2623
2624 // Setting the output label
2625 char text[128] = { 0 };
2626 dt_aligned_pixel_t alt = { 0 };
2627
2628 switch(d->model)
2629 {
2631 snprintf(text, sizeof(text), "%6d %6d %6d", sample->label_rgb[0], sample->label_rgb[1], sample->label_rgb[2]);
2632 break;
2633
2635 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f",
2636 CLAMP(sample->lab[statistic][0], .0f, 100.0f), sample->lab[statistic][1], sample->lab[statistic][2]);
2637 break;
2638
2640 dt_Lab_2_LCH(sample->lab[statistic], alt);
2641 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", CLAMP(alt[0], .0f, 100.0f), alt[1], alt[2] * 360.f);
2642 break;
2643
2645 dt_RGB_2_HSL(sample->scope[statistic], alt);
2646 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2647 break;
2648
2650 dt_RGB_2_HSV(sample->scope[statistic], alt);
2651 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2652 break;
2653
2654 default:
2656 snprintf(text, sizeof(text), "\342\227\216");
2657 break;
2658 }
2659
2662
2664}
2665
2667{
2668 _update_everything(self);
2669}
2670
2671static void _picker_button_toggled(GtkToggleButton *button, dt_lib_histogram_t *d)
2672{
2673 const gboolean picker_active = gtk_toggle_button_get_active(button);
2674 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), picker_active);
2675
2676 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
2677 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
2678 if(restrict_active)
2679 {
2680 /* The picker state is part of restricted-scope rendering but not of the
2681 * preview backbuffer hash. Invalidate on both activation and deactivation
2682 * so the same cached pixels are rebinned for the newly selected scope. */
2683 _reset_cache(d);
2685 }
2686}
2687
2688/* set sample area proxy impl */
2689
2691{
2693 dt_lib_histogram_t *d = self->data;
2694 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2695 _reset_cache(d);
2696 _update_everything(self);
2697}
2698
2699static void _set_sample_point(dt_lib_module_t *self, const float pos[2])
2700{
2702 dt_lib_histogram_t *d = self->data;
2703 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2704 _reset_cache(d);
2705 _update_everything(self);
2706}
2707
2708
2709static gboolean _sample_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
2711{
2712 gchar **sample_parts = g_malloc0_n(14, sizeof(char*));
2713
2714 sample_parts[3] = g_strdup_printf("%22s(0x%02X%02X%02X)\n<big><b>%14s</b></big>", " ",
2715 CLAMP(sample->label_rgb[0], 0, 255), CLAMP(sample->label_rgb[1], 0, 255),
2716 CLAMP(sample->label_rgb[2], 0, 255), _("RGB"));
2717 sample_parts[7] = g_strdup_printf("\n<big><b>%14s</b></big>", _("Lab"));
2718
2719 for(int i = 0; i < DT_LIB_COLORPICKER_STATISTIC_N; i++)
2720 {
2721 sample_parts[i] = g_strdup_printf("<span background='#%02X%02X%02X'>%32s</span>",
2722 (int)roundf(CLAMP(sample->display[i][0], 0.f, 1.f) * 255.f),
2723 (int)roundf(CLAMP(sample->display[i][1], 0.f, 1.f) * 255.f),
2724 (int)roundf(CLAMP(sample->display[i][2], 0.f, 1.f) * 255.f), " ");
2725
2726 sample_parts[i + 4] = g_strdup_printf("<span foreground='#FF7F7F'>%6d</span> "
2727 "<span foreground='#7FFF7F'>%6d</span> "
2728 "<span foreground='#7F7FFF'>%6d</span> %s",
2729 (int)roundf(sample->scope[i][0] * 255.f),
2730 (int)roundf(sample->scope[i][1] * 255.f),
2731 (int)roundf(sample->scope[i][2] * 255.f),
2733
2734 sample_parts[i + 8] = g_strdup_printf("%6.02f %6.02f %6.02f %s",
2735 sample->lab[i][0], sample->lab[i][1], sample->lab[i][2],
2737 }
2738
2741 sample_parts[11] = g_strdup_printf("\n<big><b>%14s</b></big>", _("color"));
2745
2746 gchar *tooltip_text = g_strjoinv("\n", sample_parts);
2748
2750
2752
2753 return TRUE;
2754}
2755
2757{
2758 dt_lib_histogram_t *d = self->data;
2759 d->statistic = dt_bauhaus_combobox_get(widget);
2760 dt_dev_get_global()->color_picker.statistic = (int)d->statistic;
2761 dt_conf_set_string("ui_last/colorpicker_mode", dt_lib_colorpicker_statistic_names[d->statistic]);
2762 _update_everything(self);
2763}
2764
2766{
2767 dt_lib_histogram_t *d = self->data;
2768 d->model = dt_bauhaus_combobox_get(widget);
2769 dt_conf_set_string("ui_last/colorpicker_model", dt_lib_colorpicker_model_names[d->model]);
2770 _update_everything(self);
2771}
2772
2792
2793static gboolean _sample_enter_callback(GtkWidget *widget, GdkEvent *event, dt_colorpicker_sample_t *sample)
2794{
2795 if(dt_dev_get_global()->color_picker.picker)
2796 {
2799 }
2800
2801 return FALSE;
2802}
2803
2804static gboolean _sample_leave_callback(GtkWidget *widget, GdkEvent *event, gpointer data)
2805{
2806 if(event->crossing.detail == GDK_NOTIFY_INFERIOR) return FALSE;
2807
2808 if(dt_dev_get_global()->color_picker.selected_sample)
2809 {
2812 }
2813
2814 return FALSE;
2815}
2816
2818{
2821 = g_slist_remove(dt_dev_get_global()->color_picker.samples, (gpointer)sample);
2822 dt_free(sample);
2823}
2824
2825static void _remove_sample_cb(GtkButton *widget, dt_colorpicker_sample_t *sample)
2826{
2828 dt_lib_histogram_t *d = self ? self->data : NULL;
2829 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram") && !IS_NULL_PTR(d))
2830 _reset_cache(d);
2831
2832 _remove_sample(sample);
2834 if(!IS_NULL_PTR(self))
2835 _update_everything(self);
2836}
2837
2839{
2840 if(event->button == 1)
2841 {
2842 sample->locked = !sample->locked;
2843 gtk_widget_queue_draw(widget);
2844 }
2845 else if(event->button == 3)
2846 {
2847 // copy to active picker
2850
2851 // no active picker, too much iffy GTK work to activate a default
2852 if(IS_NULL_PTR(picker)) return FALSE;
2853
2854 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2855 {
2856 float point[2] = { 0.0f };
2858 _set_sample_point(self, point);
2859 }
2860 else if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2861 {
2862 dt_boundingbox_t box = { 0.0f };
2863 _sample_raw_box_to_image_norm(sample, box);
2864 _set_sample_box_area(self, box);
2865 }
2866 else
2867 return FALSE;
2868
2870 }
2871 return FALSE;
2872}
2873
2874static void _add_sample(GtkButton *widget, dt_lib_module_t *self)
2875{
2876 dt_develop_t *const dev = dt_dev_get_global();
2877 dt_lib_histogram_t *d = self->data;
2878
2880 return;
2881
2883 if(IS_NULL_PTR(sample)) return;
2884
2886 sample->locked = FALSE;
2887 g_strlcpy(sample->backbuf_op, d->op ? d->op : "", sizeof(sample->backbuf_op));
2888
2889 sample->container = gtk_event_box_new();
2891 g_signal_connect(G_OBJECT(sample->container), "enter-notify-event", G_CALLBACK(_sample_enter_callback), sample);
2892 g_signal_connect(G_OBJECT(sample->container), "leave-notify-event", G_CALLBACK(_sample_leave_callback), sample);
2893
2896
2899 gtk_widget_set_tooltip_text(sample->color_patch, _("hover to highlight sample on canvas,\n"
2900 "click to lock sample,\n"
2901 "right-click to load sample area into active color picker"));
2902 g_signal_connect(G_OBJECT(sample->color_patch), "button-press-event", G_CALLBACK(_live_sample_button), sample);
2904
2909
2910 sample->output_label = gtk_label_new("");
2911 dt_gui_add_class(sample->output_label, "dt_monospace");
2915 g_signal_connect(G_OBJECT(sample->output_label), "query-tooltip", G_CALLBACK(_sample_tooltip_callback), sample);
2918
2920 g_signal_connect(G_OBJECT(delete_button), "clicked", G_CALLBACK(_remove_sample_cb), sample);
2921 gtk_box_pack_start(GTK_BOX(container), delete_button, FALSE, FALSE, 0);
2922
2923 gtk_box_pack_start(GTK_BOX(d->samples_container), sample->container, FALSE, FALSE, 0);
2925
2927 = g_slist_append(dev->color_picker.samples, sample);
2928
2929 // remove emphasis on primary sample from mouseover on this button
2931
2932 // Updating the display
2933 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2934 _reset_cache(d);
2935 _update_everything(self);
2936}
2937
2938static void _display_samples_changed(GtkToggleButton *button, dt_lib_module_t *self)
2939{
2940 dt_conf_set_bool("ui_last/colorpicker_display_samples", gtk_toggle_button_get_active(button));
2942 _update_everything(self);
2944}
2945
2946static void _restrict_histogram_changed(GtkToggleButton *button, dt_lib_module_t *self)
2947{
2948 dt_lib_histogram_t *d = self->data;
2949 const gboolean restrict_active = gtk_toggle_button_get_active(button);
2950 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
2952 dt_conf_set_bool("ui_last/colorpicker_restrict_histogram", restrict_active);
2954
2955 if(picker_active)
2956 {
2957 /* Restrict toggling changes the scope bins between full-image and picker
2958 * geometry while the preview backbuffer hash stays identical. Make the
2959 * cached Cairo surface dirty so _update_everything() renders the new mode. */
2960 _reset_cache(d);
2961 }
2962 _update_everything(self);
2963}
2964
2966{
2967 dt_lib_histogram_t *d = self->data;
2969
2971
2972 // Resetting the picked colors
2973 for(int i = 0; i < 3; i++)
2974 {
2975 for(int s = 0; s < DT_LIB_COLORPICKER_STATISTIC_N; s++)
2976 {
2977 primary_sample->display[s][i] = 0.f;
2978 primary_sample->scope[s][i] = 0.f;
2979 primary_sample->lab[s][i] = 0.f;
2980 }
2981 primary_sample->label_rgb[i] = 0;
2982 }
2983 primary_sample->swatch.red = primary_sample->swatch.green
2984 = primary_sample->swatch.blue = 0.0;
2985
2987
2988 // Removing any live samples
2989 while(dt_dev_get_global()->color_picker.samples)
2990 _remove_sample(dt_dev_get_global()->color_picker.samples->data);
2991
2992 // Resetting GUI elements
2993 dt_bauhaus_combobox_set(d->statistic_selector, 0);
2994 dt_bauhaus_combobox_set(d->color_mode_selector, 0);
2995 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box)))
2996 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box), FALSE);
2997
2998 _reset_cache(d);
2999 _set_params(d);
3002
3004}
3005
3006static void _pref_stage_toggled(GtkCheckMenuItem *item, gpointer user_data)
3007{
3008 if(!gtk_check_menu_item_get_active(item)) return;
3010 _set_stage(self, GPOINTER_TO_INT(user_data));
3011}
3012
3013static void _pref_display_toggled(GtkCheckMenuItem *item, gpointer user_data)
3014{
3015 if(!gtk_check_menu_item_get_active(item)) return;
3018}
3019
3020void set_preferences(void *menu, dt_lib_module_t *self)
3021{
3023
3024 // "Show data from" submenu
3025 GtkWidget *mi_stage = gtk_menu_item_new_with_label(_("Show data from"));
3028
3029 const char *stage_labels[] = { N_("Raw image"), N_("Output color profile"), N_("Final display"), NULL };
3030 const int current_stage = _backbuf_op_to_int(d);
3032 for(int i = 0; stage_labels[i]; i++)
3033 {
3037 g_object_set_data(G_OBJECT(item), "self", self);
3040 }
3043
3044 // "Display" submenu
3048
3049 const char *display_labels[] = {
3050 N_("Histogram"), N_("Waveform (horizontal)"), N_("Waveform (vertical)"),
3051 N_("Parade (horizontal)"), N_("Parade (vertical)"), N_("Vectorscope"), NULL
3052 };
3053 const gboolean vectorscope_ok = strcmp(d->op, "initialscale");
3055 for(int i = 0; display_labels[i]; i++)
3056 {
3059 if((dt_lib_histogram_scope_type_t)i == d->scope)
3063 g_object_set_data(G_OBJECT(item), "self", self);
3066 }
3069}
3070
3071void gui_init(dt_lib_module_t *self)
3072{
3073 dt_develop_t *const dev = dt_dev_get_global();
3074 /* initialize ui widgets */
3076 if(d) memset(d, 0, sizeof(dt_lib_histogram_t));
3077 self->data = (void *)d;
3078 d->module = self;
3079 d->cst = NULL;
3080 d->pending_hashes = g_array_new(FALSE, FALSE, sizeof(uint64_t));
3081 d->refresh_idle_source = 0;
3082
3084 d->scope_draw = gtk_drawing_area_new();
3089 d->scope_height = CLAMP(d->scope_height,
3092 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
3093 g_signal_connect(G_OBJECT(d->scope_draw), "draw", G_CALLBACK(_draw_callback), d);
3094 g_signal_connect(G_OBJECT(d->scope_draw), "scroll-event", G_CALLBACK(_area_scrolled_callback), d);
3095 g_signal_connect(G_OBJECT(d->scope_draw), "size-allocate", G_CALLBACK(_resize_callback), d);
3096
3097 // The grip floats on the scope's bottom edge (overlay) so it takes no layout space and leaves no
3098 // margin-like gap; invisible until hovered.
3102
3116 _("Drag to resize the scope vertically"),
3119 gtk_overlay_add_overlay(GTK_OVERLAY(scope_overlay), d->scope_resize_handle);
3120
3122 (&d->cs,
3123 "plugins/darkroom/colorpicker/show",
3124 _("Color picker"),
3125 GTK_BOX(self->widget), GTK_PACK_END);
3126
3127 // The develop module owns the picker state because both the preview pipe and the GUI need to
3128 // observe it. Histogram only binds its widgets to that shared state.
3129 dev->color_picker.histogram_module = self;
3131 dev->color_picker.display_samples = dt_conf_get_bool("ui_last/colorpicker_display_samples");
3132 dev->color_picker.restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
3133 dev->color_picker.primary_sample->swatch.alpha = 1.0;
3134
3135 const char *str = dt_conf_get_string_const("ui_last/colorpicker_model");
3138 if(g_strcmp0(str, *names) == 0)
3139 d->model = i;
3140
3141 str = dt_conf_get_string_const("ui_last/colorpicker_mode");
3144 if(g_strcmp0(str, *names) == 0)
3145 d->statistic = i;
3146
3147 // The color patch
3149 gtk_widget_set_name(GTK_WIDGET(color_patch_wrapper), "color-picker-area");
3150
3151 // The picker button, mode and statistic combo boxes
3153 gtk_box_pack_start(GTK_BOX(d->cs.container), picker_row, TRUE, TRUE, 0);
3154
3155 d->statistic_selector = dt_bauhaus_combobox_new_full(
3156 dt_bauhaus_get_global(), NULL, NULL, _("select which statistic to show"), d->statistic,
3159 dt_bauhaus_widget_set_label(d->statistic_selector, NULL);
3160 gtk_widget_set_valign(d->statistic_selector, GTK_ALIGN_CENTER);
3161 gtk_box_pack_start(GTK_BOX(picker_row), d->statistic_selector, TRUE, TRUE, 0);
3162
3163 d->color_mode_selector = dt_bauhaus_combobox_new_full(
3164 dt_bauhaus_get_global(), NULL, NULL, _("select which color mode to use"), d->model,
3167 dt_bauhaus_widget_set_label(d->color_mode_selector, NULL);
3168 gtk_widget_set_valign(d->color_mode_selector, GTK_ALIGN_CENTER);
3169 gtk_box_pack_start(GTK_BOX(picker_row), d->color_mode_selector, TRUE, TRUE, 0);
3170
3172 gtk_widget_set_tooltip_text(d->picker_button, _("turn on color picker\nctrl+click or right-click to select an area"));
3173 gtk_widget_set_name(GTK_WIDGET(d->picker_button), "color-picker-button");
3174 g_signal_connect(G_OBJECT(d->picker_button), "toggled", G_CALLBACK(_picker_button_toggled), d);
3175
3176 // The small sample, label and add button
3183 gtk_box_pack_start(GTK_BOX(d->cs.container), sample_row_events, TRUE, TRUE, 0);
3184
3187
3191
3196
3201 dt_gui_add_class(label, "dt_monospace");
3208
3209 d->add_sample_button = dtgtk_button_new(dtgtk_cairo_paint_square_plus, 0, NULL);
3210 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), FALSE);
3211 g_signal_connect(G_OBJECT(d->add_sample_button), "clicked", G_CALLBACK(_add_sample), self);
3212 gtk_box_pack_end(GTK_BOX(sample_row), d->add_sample_button, FALSE, FALSE, 0);
3213
3214 // Adding the live samples section
3215 label = dt_ui_section_label_new(_("Live samples"));
3216 gtk_box_pack_start(GTK_BOX(d->cs.container), label, TRUE, TRUE, 0);
3217
3219 gtk_box_pack_start(GTK_BOX(d->cs.container),
3220 dt_ui_scroll_wrap(d->samples_container, 1, "plugins/darkroom/colorpicker/windowheight",
3222
3223 d->display_samples_check_box = gtk_check_button_new_with_label(_("Display samples on image"));
3224 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->display_samples_check_box))),
3226 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box),
3227 dt_conf_get_bool("ui_last/colorpicker_display_samples"));
3228 g_signal_connect(G_OBJECT(d->display_samples_check_box), "toggled",
3230 gtk_box_pack_start(GTK_BOX(d->cs.container), d->display_samples_check_box, TRUE, TRUE, 0);
3231
3232 d->restrict_button = gtk_check_button_new_with_label(_("Restrict scope to selection"));
3234 gboolean restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
3235 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->restrict_button), restrict_histogram);
3236 g_signal_connect(G_OBJECT(d->restrict_button), "toggled", G_CALLBACK(_restrict_histogram_changed), self);
3237 gtk_box_pack_start(GTK_BOX(d->cs.container), d->restrict_button, TRUE, TRUE, 0);
3238
3239 _reset_cache(d);
3240 _set_params(d);
3241}
3242
3244{
3245 dt_develop_t *const dev = dt_dev_get_global();
3246 if(IS_NULL_PTR(self->data)) return;
3247 dt_lib_histogram_t *d = self->data;
3248 dt_dev_pixelpipe_cache_wait_cleanup(&d->scope_wait, "histogram-gui-cleanup-scope");
3249 dt_dev_pixelpipe_cache_wait_cleanup(&d->picker_wait, "histogram-gui-cleanup-picker");
3250 dt_dev_pixelpipe_cache_wait_cleanup(&d->module_wait, "histogram-gui-cleanup-module");
3253 if(d->refresh_idle_source != 0)
3254 {
3255 g_source_remove(d->refresh_idle_source);
3256 d->refresh_idle_source = 0;
3257 }
3258 if(d->pending_hashes) g_array_free(d->pending_hashes, TRUE);
3261
3264 while(dev->color_picker.samples)
3266
3268 self->data = NULL;
3269}
3270
3271// clang-format off
3272// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3273// vim: shiftwidth=2 expandtab tabstop=2 cindent
3274// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3275// clang-format on
GtkWidget * dt_gui_main_window(void)
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
GtkWidget * dt_bauhaus_combobox_new_full(dt_bauhaus_t *bh, dt_gui_module_t *self, const char *label, const char *tip, int pos, GtkCallback callback, gpointer data, const char **texts)
Definition bauhaus.c:1708
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2139
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:1875
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:135
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_gui_new_collapsible_section(dt_gui_collapsible_section_t *cs, const char *confname, const char *label, GtkBox *parent, GtkPackType pack)
Create a collapsible section and pack it into the parent box.
dt_iop_colorspace_type_t
@ IOP_CS_RGB
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)
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_POINT_AREA
char * Lch_to_color_name(dt_aligned_pixel_t color)
Name the colour at color in plain language, e.g. "salmon" or "olive drab".
dt_lib_colorpicker_statistic_t
Definition colorpicker.h:43
@ DT_LIB_COLORPICKER_STATISTIC_MAX
Definition colorpicker.h:46
@ DT_LIB_COLORPICKER_STATISTIC_N
Definition colorpicker.h:47
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:44
@ DT_LIB_COLORPICKER_SIZE_POINT
Definition colorpicker.h:38
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:39
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)
void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name, const float *const image_in, float *const image_out, const int width, const int height, const int cst_from, const int cst_to, int *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
Convert a 4-channel float buffer between RGB and Lab through one profile.
static const float x
static dt_aligned_pixel_t rgb_out
const float v
static float4 dt_xyY_to_XYZ(const float4 xyY)
Definition colorspace.h:645
static float4 dt_XYZ_to_xyY(const float4 XYZ)
Definition colorspace.h:635
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t xyY
dt_xyY_to_Luv(xyY, Luv)
dt_apply_transposed_color_matrix(XYZ, xyz_to_srgb_matrix_transposed, sRGB)
static const float const float const float min
static dt_aligned_pixel_t XYZ
dt_Lch_to_Luv(Lch, Luv)
const float max
dt_Luv_to_xyY(Luv, xyY)
static dt_aligned_pixel_t XYZ_D50
dt_Luv_to_Lch(Luv, Lch)
dt_XYZ_to_Lab(XYZ, Lab)
static dt_aligned_pixel_t Luv
static dt_aligned_pixel_t RGB
static dt_aligned_pixel_t Lch
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
int dt_conf_key_exists(const char *key)
Does key have a value?
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
void dt_histogram_max_helper(const dt_dev_histogram_stats_t *const histogram_stats, const dt_iop_colorspace_type_t cst, const dt_iop_colorspace_type_t cst_to, uint32_t **histogram, uint32_t *histogram_max)
void dt_histogram_helper(dt_dev_histogram_collection_params_t *histogram_params, dt_dev_histogram_stats_t *histogram_stats, const dt_iop_colorspace_type_t cst, const dt_iop_colorspace_type_t cst_to, const void *pixel, uint32_t **histogram, const int compensate_middle_grey, const dt_iop_order_iccprofile_info_t *const profile_info)
dt_image_orientation_t dt_image_get_effective_orientation(const dt_image_t *img)
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:1015
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:531
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:624
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:511
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
#define M_PI_F
const dt_dev_pixelpipe_iop_t * dt_dev_pixelpipe_get_module_piece(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
void dt_dev_pixelpipe_cache_wait_cleanup(dt_dev_pixelpipe_cache_wait_t *wait, const char *reason)
Cancel one pending GUI cache wait request and clear its runtime state.
const dt_dev_pixelpipe_iop_t * dt_dev_pixelpipe_get_prev_enabled_piece(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void dt_dev_pixelpipe_cache_wait_set_owner(dt_dev_pixelpipe_cache_wait_t *wait, const char *owner_tag, gpointer owner_object)
Attach debug ownership metadata to one cache wait request.
gboolean dt_dev_pixelpipe_cache_peek_gui_retained(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, void **data, dt_pixel_cache_entry_t **cache_entry, dt_dev_pixelpipe_cache_wait_t *wait, dt_dev_pixelpipe_cache_ready_callback_t restart, gpointer restart_data)
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)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
void dt_dev_backbuf_take_keepalive(dt_backbuf_t *backbuf, struct dt_pixel_cache_entry_t *entry)
Take the keepalive reference on entry, releasing whatever this backbuffer held. Idempotent for the sa...
Definition develop.c:2201
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1358
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1345
void dt_dev_backbuf_release_keepalive(dt_backbuf_t *backbuf)
Release this backbuffer's keepalive reference, if it holds one. Safe to call twice.
Definition develop.c:2217
void dt_dev_set_backbuf(dt_backbuf_t *backbuf, const int width, const int height, const size_t bpp, const int64_t hash, const int64_t history_hash)
Definition develop.c:2226
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:1893
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:108
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
GHashTable * names
GtkWidget* -> the name the application knows it by.
GtkWidget * view
the tree view
static void dt_draw_line(cairo_t *cr, float left, float top, float right, float bottom)
Definition draw.h:118
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:158
static void dt_draw_histogram_8(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const gboolean linear)
Definition draw.h:469
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
const dt_collection_filter_flag_t colors[6]
Definition filter.c:302
static gboolean dt_iop_colorspace_is_rgb(const dt_iop_colorspace_type_t cst)
Definition format.h:75
@ TYPE_FLOAT
Definition format.h:56
GdkRGBA color[]
Definition geotagging.c:541
float dt_boundingbox_t[4]
Definition image.h:83
dt_image_orientation_t
Definition image.h:225
@ ORIENTATION_SWAP_XY
Definition image.h:230
@ ORIENTATION_NONE
Definition image.h:227
const char * tooltip
Definition image.h:319
int bpp
__DT_CLONE_TARGETS__ void dt_imageio_flip_buffers(char *out, const char *in, const size_t bpp, const int wd, const int ht, const int fwd, const int fht, const int stride, const dt_image_orientation_t orientation)
dt_iop_module_t * dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
Definition imageop.c:1855
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
static void gui_init(dt_lib_import_t *d)
Definition import.c:1184
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
hash
Definition layers.h:90
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1240
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1262
dt_lib_colorpicker_model_t
@ DT_LIB_COLORPICKER_MODEL_LAB
@ DT_LIB_COLORPICKER_MODEL_LCH
@ DT_LIB_COLORPICKER_MODEL_HSL
@ DT_LIB_COLORPICKER_MODEL_RGB
@ DT_LIB_COLORPICKER_MODEL_NONE
@ DT_LIB_COLORPICKER_MODEL_HSV
static void _create_vectorscope_image(const uint32_t *const restrict vectorscope, uint8_t *const restrict image, const uint32_t max_hist, const float zoom)
#define DT_LIB_HISTOGRAM_SCOPE_RESTRICTED_LABEL_OPERATOR
static void _pixelpipe_pick_samples(dt_lib_histogram_t *d)
static void _set_sample_point(dt_lib_module_t *self, const float pos[2])
static void _refresh_module_histogram(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *const roi, const dt_iop_colorspace_type_t cst, const float *pixel, dt_iop_module_t *module)
static void _add_pending_hash(dt_lib_histogram_t *d, const uint64_t hash)
void gui_reset(dt_lib_module_t *self)
static void _bin_pixels_vectorscope_in_roi(const float *const restrict image, uint32_t *const restrict vectorscope, const size_t min_x, const size_t max_x, const size_t min_y, const size_t max_y, const size_t width, const float zoom, dt_lib_histogram_t *d)
#define DT_LIB_HISTOGRAM_SCOPE_MIN_HEIGHT
void _set_params(dt_lib_histogram_t *d)
static dt_backbuf_t * _get_histogram_backbuf(dt_develop_t *dev, const char *op)
const gchar * dt_lib_colorpicker_model_names[]
static void _update_sample_label(dt_lib_module_t *self, dt_colorpicker_sample_t *sample)
static void _add_sample(GtkButton *widget, dt_lib_module_t *self)
void set_preferences(void *menu, dt_lib_module_t *self)
static gboolean _sample_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, const dt_colorpicker_sample_t *sample)
static int _scope_resize_handle_resize(int requested_size, gboolean finished, gpointer user_data)
Apply one vertical size request from the generic Bauhaus resize handle.
static void _process_restricted_text(dt_lib_histogram_t *d, cairo_t *cr, const int height)
Draw the restricted-scope label over the current Cairo surface.
static gboolean _remove_pending_hash(dt_lib_histogram_t *d, const uint64_t hash)
static void _refresh_preview_histograms(dt_develop_t *dev)
static void _preview_cacheline_ready_callback(gpointer instance, const guint64 hash, const guint64 producer_node_key, gpointer user_data)
static void _clear_histogram_backbuf(dt_backbuf_t *backbuf)
void _scope_pixel_to_xyz(const dt_aligned_pixel_t rgb_in, dt_aligned_pixel_t xyz_out, dt_lib_histogram_t *d)
static gboolean _is_backbuf_ready(dt_lib_histogram_t *d)
Check that the selected histogram backbuffer belongs to the current preview graph.
void _get_allocation_size(dt_lib_histogram_t *d, int *width, int *height)
static gboolean _refresh_global_picker(dt_lib_module_t *self)
#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_FORCE
static void _display_samples_changed(GtkToggleButton *button, dt_lib_module_t *self)
static void _bin_pixels_waveform_in_roi(const float *const restrict image, uint32_t *const restrict bins, const size_t min_x, const size_t max_x, const size_t min_y, const size_t max_y, const size_t source_width, const size_t source_height, const size_t tone_bins, const size_t raster_extent, const gboolean vertical)
static const dt_dev_pixelpipe_iop_t * _get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op)
static void _resize_callback(GtkWidget *widget, GdkRectangle *allocation, dt_lib_histogram_t *d)
#define DT_LIB_HISTOGRAM_SCOPE_DEFAULT_HEIGHT
static gboolean _histogram_refresh_idle(gpointer user_data)
void _scope_pixel_to_display_rgb(const dt_aligned_pixel_t rgb_in, dt_aligned_pixel_t rgb_out, dt_lib_histogram_t *d)
static void _clear_pending_hashes(dt_lib_histogram_t *d)
static void _redraw_scopes(dt_lib_histogram_t *d)
static int _scope_resize_handle_get_size(gpointer user_data)
static void _color_mode_changed(GtkWidget *widget, dt_lib_module_t *self)
void gui_cleanup(dt_lib_module_t *self)
static void _bin_pickers_waveforms(const float *const restrict image, uint32_t *const restrict bins, const size_t width, const size_t height, const size_t tone_bins, const size_t raster_extent, const gboolean vertical, dt_colorpicker_sample_t *sample)
void _backbuf_int_to_op(const int value, dt_lib_histogram_t *d)
static gboolean _refresh_global_histogram_backbuf_for_hash(dt_develop_t *dev, const char *op, const uint64_t expected_hash)
const gchar * dt_lib_colorpicker_statistic_names[]
static void _paint_waveform(cairo_t *cr, uint8_t *const restrict image, const int width, const int height, const size_t img_width, const size_t img_height, const size_t tone_bins, const gboolean vertical)
static void _sample_raw_point_to_image_norm(const dt_colorpicker_sample_t *const sample, float point[2])
static void _create_waveform_image(const uint32_t *const restrict bins, uint8_t *const restrict image, const uint32_t max_hist, const size_t width, const size_t height)
static uint64_t _get_live_histogram_hash(const char *op)
static void _sample_raw_box_to_image_norm(const dt_colorpicker_sample_t *const sample, float box[4])
static void _sync_pending_histogram_hashes(dt_lib_module_t *self)
#define GAMMA
static gboolean _is_restricted(dt_lib_histogram_t *d)
int _backbuf_op_to_int(dt_lib_histogram_t *d)
static dt_histogram_preview_refresh_state_t _preview_refresh_state
static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_lib_histogram_t *d)
#define DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE
static void _lib_histogram_history_resync_callback(gpointer instance, dt_lib_module_t *self)
static void _statistic_changed(GtkWidget *widget, dt_lib_module_t *self)
#define HISTOGRAM_BINS
gboolean _trigger_recompute(dt_lib_histogram_t *d)
static void _bin_pickers_vectorscope(const float *const restrict image, uint32_t *const restrict vectorscope, const size_t width, const size_t height, const float zoom, dt_lib_histogram_t *d, const dt_colorpicker_sample_t *const sample)
static void _destroy_surface(dt_lib_histogram_t *d)
static gboolean _sample_enter_callback(GtkWidget *widget, GdkEvent *event, dt_colorpicker_sample_t *sample)
static void _remove_sample_cb(GtkButton *widget, dt_colorpicker_sample_t *sample)
gboolean _needs_recompute(dt_lib_histogram_t *d, const int width, const int height)
static void _pref_stage_toggled(GtkCheckMenuItem *item, gpointer user_data)
static void _update_everything(dt_lib_module_t *self)
static gboolean _has_pending_hash(const dt_lib_histogram_t *d, const uint64_t hash)
static void _pref_display_toggled(GtkCheckMenuItem *item, gpointer user_data)
uint32_t container(dt_lib_module_t *self)
#define DT_LIB_HISTOGRAM_SCOPE_HEIGHT_CONF
static void _label_size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
static gboolean _sample_draw_callback(GtkWidget *widget, cairo_t *cr, dt_colorpicker_sample_t *sample)
static void _bin_pixels_histogram_in_roi(const float *const restrict image, uint32_t *const restrict bins, const size_t min_x, const size_t max_x, const size_t min_y, const size_t max_y, const size_t stride)
static void _process_vectorscope(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width, const int height, const float zoom, dt_lib_histogram_t *d)
static void _set_scope(dt_lib_module_t *self, dt_lib_histogram_scope_type_t scope)
static void _preview_history_resync_callback(gpointer instance, gpointer user_data)
static void _schedule_histogram_refresh(dt_lib_module_t *self)
static void _restrict_histogram_changed(GtkToggleButton *button, dt_lib_module_t *self)
static void _update_picker_output(dt_lib_module_t *self)
void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
static void _set_sample_box_area(dt_lib_module_t *self, const dt_boundingbox_t box)
void view_enter(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
static void _histogram_restart_scope_cache_wait(gpointer user_data)
Retry a scope render after its source cacheline has been published.
int position()
static float _Luv_to_vectorscope_coord_zoom(const float value, const float zoom)
static void _bin_pickers_histogram(const float *const restrict image, const size_t width, const size_t height, uint32_t *bins, dt_colorpicker_sample_t *sample)
const char ** views(dt_lib_module_t *self)
uint32_t _find_max_histogram(const uint32_t *const restrict bins, const size_t binning_size)
static gboolean _sample_leave_callback(GtkWidget *widget, GdkEvent *event, gpointer data)
int expandable(dt_lib_module_t *self)
static gboolean _draw_callback(GtkWidget *widget, cairo_t *crf, gpointer user_data)
static void _bin_pixels_waveform(const float *const restrict image, uint32_t *const restrict bins, const size_t width, const size_t height, const size_t binning_size, const size_t tone_bins, const size_t raster_extent, const gboolean vertical, const gboolean restricted)
static gboolean _live_sample_button(GtkWidget *widget, GdkEventButton *event, dt_colorpicker_sample_t *sample)
#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES
#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES
static void _pixelpipe_pick_from_image(const dt_backbuf_t *const backbuf, dt_colorpicker_sample_t *const sample, dt_lib_histogram_t *d, const char *op)
static float _vectorscope_coord_zoom_to_Luv(const float value, const float zoom)
static void _remove_sample(dt_colorpicker_sample_t *sample)
#define DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT
static gboolean _resolve_backbuf_sampling_source(const char *const op, const dt_backbuf_t *const backbuf, dt_iop_roi_t *const roi, dt_iop_buffer_dsc_t *const dsc, double *const iop_order, int *const direction)
Resolve the live preview-stage geometry behind one global histogram backbuffer.
static void _lib_histogram_cacheline_ready_callback(gpointer instance, const guint64 hash, const guint64 producer_node_key, dt_lib_module_t *self)
static gboolean _refresh_preview_module_histogram_for_hash(dt_develop_t *dev, dt_iop_module_t *module, const uint64_t expected_hash)
static void _process_histogram(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width, const int height, dt_lib_histogram_t *d)
gboolean _redraw_surface(dt_lib_histogram_t *d)
static void _histogram_restart_cache_wait(gpointer user_data)
static void _set_stage(dt_lib_module_t *self, int value)
dt_lib_histogram_scope_type_t
@ DT_LIB_HISTOGRAM_SCOPE_PARADE_VERTICAL
@ DT_LIB_HISTOGRAM_SCOPE_WAVEFORM_HORIZONTAL
@ DT_LIB_HISTOGRAM_SCOPE_HISTOGRAM
@ DT_LIB_HISTOGRAM_SCOPE_PARADE_HORIZONTAL
@ DT_LIB_HISTOGRAM_SCOPE_WAVEFORM_VERTICAL
@ DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE
@ DT_LIB_HISTOGRAM_SCOPE_N
static dt_histogram_scope_buf_t _orient_scope_buf(const void *data, const dt_backbuf_t *backbuf, const char *op)
static void _clear_pending_preview_histograms(void)
static void _picker_button_toggled(GtkToggleButton *button, dt_lib_histogram_t *d)
static void _reset_cache(dt_lib_histogram_t *d)
static void _bin_vectorscope(const float *const restrict image, uint32_t *const vectorscope, const size_t width, const size_t height, const float zoom, dt_lib_histogram_t *d)
@ DT_DEBUG_VERBOSE
Definition logging.h:78
@ DT_DEBUG_DEV
Definition logging.h:53
int32_t dt_get_debug_flags(void)
Definition darktable.c:2094
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#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
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_FOR_SIMD__(...)
Definition openmp.h:97
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
const char * name
Definition pdf.h:90
@ DT_REQUEST_ON
Definition pixelpipe.h:52
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:53
void dt_dev_pixelpipe_cache_unref_entry(dt_pixel_cache_entry_t *entry)
Release one reference held on entry, by POINTER.
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Take or release one reference on a cache line. A referenced line is never evicted,...
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.
size_t dt_pixel_cache_entry_get_size(const dt_pixel_cache_entry_t *entry)
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
#define dt_pixelpipe_cache_free_align(mem)
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
GtkWidget * dtgtk_resize_handle_new(GtkOrientation orientation, gboolean invert, const char *tooltip, dtgtk_resize_handle_get_size_f get_size, dtgtk_resize_handle_resize_f resize, gpointer user_data)
Create a themed handle widget driving one-dimensional resize gestures.
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:43
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:213
@ 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:189
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
#define for_four_channels(_var,...)
Definition simd.h:89
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
unsigned __int64 uint64_t
Definition strptime.c:75
What the pipeline last published: which cacheline, and what shape its pixels are in.
lib_colorpicker_sample_statistics display
Definition colorpicker.h:70
dt_lib_colorpicker_size_t size
Definition colorpicker.h:64
lib_colorpicker_sample_statistics scope
Definition colorpicker.h:73
char backbuf_op[32]
Global histogram stage backing this live sample.
Definition colorpicker.h:93
dt_boundingbox_t box
Definition colorpicker.h:63
lib_colorpicker_sample_statistics lab
Definition colorpicker.h:75
const struct dt_histogram_roi_t * roi
Definition pixelpipe.h:60
dt_iop_buffer_dsc_t dsc_out
struct dt_iop_order_iccprofile_info_t * input_profile_info
struct dt_iop_order_iccprofile_info_t * output_profile_info
gboolean gui_observable_source
int32_t gui_attached
Definition develop.h:167
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:374
dt_backbuf_t display_histogram
Definition develop.h:320
GList * iop
Definition develop.h:269
dt_backbuf_t output_histogram
Definition develop.h:319
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.
gboolean restrict_histogram
Definition develop.h:379
int statistic
Definition develop.h:380
GSList * samples
Definition develop.h:375
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:366
dt_backbuf_t raw_histogram
Definition develop.h:318
struct dt_colorpicker_sample_t * selected_sample
Definition develop.h:376
struct dt_lib_module_t * histogram_module
Definition develop.h:381
gboolean display_samples
Definition develop.h:377
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
GtkWidget * widget
Definition imageop_gui.h:47
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
char multi_name[128]
Definition imageop.h:387
dt_dev_histogram_stats_t histogram_stats
Definition imageop.h:293
uint32_t histogram_max[4]
Definition imageop.h:295
int histogram_middle_grey
Definition imageop.h:302
uint32_t * histogram
Definition imageop.h:291
dt_iop_colorspace_type_t histogram_cst
Definition imageop.h:300
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,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
dt_lib_histogram_scope_type_t view
GtkWidget * statistic_selector
dt_lib_colorpicker_model_t model
dt_lib_colorpicker_statistic_t statistic
dt_dev_pixelpipe_cache_wait_t scope_wait
dt_lib_histogram_cache_t cache
GtkWidget * samples_container
cairo_surface_t * cst
dt_dev_pixelpipe_cache_wait_t module_wait
GtkWidget * color_mode_selector
GtkWidget * add_sample_button
dt_backbuf_t * backbuf
dt_lib_histogram_scope_type_t scope
GtkWidget * scope_resize_handle
GtkWidget * display_samples_check_box
dt_dev_pixelpipe_cache_wait_t picker_wait
GtkWidget * restrict_button
GtkWidget * picker_button
struct dt_lib_module_t *GtkWidget * scope_draw
dt_gui_collapsible_section_t cs
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
void * data
dt_atomic_uint64 age
uint64_t producer_node_key
One consumer's outstanding request, owned by that consumer, not by the queue.
GQueue * log
Definition supervisor.c:121
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static void dt_get_times(dt_times_t *t)
Definition times.h:50
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...) __attribute__((format(printf
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_remove(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_lock(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_square_plus(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER