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{
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 const uint64_t previous_hash = dt_dev_backbuf_get_hash(backbuf);
360 if(previous_hash != hash)
361 {
362 /* The module output already owns its producer ref. Tagging it as a global histogram backbuffer
363 * reserves one additional consumer ref so GUI readers only need `peek()` and read locks later --
364 * and that consumer ref is precisely the one the retained lookup above just took, handed over
365 * rather than taken a second time. `_clear_histogram_backbuf()` and the next publication release
366 * it through `dt_dev_pixelpipe_cache_unref_hash()`. */
368 }
369 else
370 {
371 /* Already published under this very hash, so the keepalive is held: ours is one too many. */
373 }
374
376 return TRUE;
377}
378
380 const uint64_t expected_hash)
381{
382 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe) || IS_NULL_PTR(module)) return FALSE;
383
385 if(IS_NULL_PTR(piece) || !(piece->request_histogram & DT_REQUEST_ON)) return FALSE;
386 if((piece->request_histogram & DT_REQUEST_ONLY_IN_GUI) && !dev->gui_attached) return FALSE;
387
388 const dt_dev_pixelpipe_iop_t *const previous_piece
390 if(IS_NULL_PTR(previous_piece) || previous_piece->global_hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
391 if(expected_hash != DT_PIXELPIPE_CACHE_HASH_INVALID && previous_piece->global_hash != expected_hash) return FALSE;
392 if(previous_piece->dsc_out.datatype != TYPE_FLOAT) return FALSE;
393
394 void *input = NULL;
395 dt_pixel_cache_entry_t *input_entry = NULL;
397 dt_lib_histogram_t *const d = !IS_NULL_PTR(histogram_module) ? histogram_module->data : NULL;
398 if(!IS_NULL_PTR(d))
399 dt_dev_pixelpipe_cache_wait_set_owner(&d->module_wait, "histogram-module-refresh", module);
400 /* Retained: this is an intermediate module cacheline, at refcount 0 between renders, so the
401 * reference has to be taken with the lookup and not after it. */
402 if(!dt_dev_pixelpipe_cache_peek_gui_retained(dev->preview_pipe, previous_piece, &input, &input_entry,
403 !IS_NULL_PTR(d) ? &d->module_wait : NULL,
404 _histogram_restart_cache_wait, histogram_module))
405 return FALSE;
406
408
409 /* `piece->roi_in` and `piece->dsc_in` are rewritten by the pipeline worker on every ROI-planning
410 * pass, and this runs on the GUI thread without the pipe's busy_mutex (see the comment in
411 * `_refresh_preview_histograms()`: taking it here would freeze the UI). Read them ONCE, into
412 * locals, and describe the buffer with nothing else afterwards. Reading them again for the loop
413 * bounds after having sized an allocation from an earlier reading is how a ROI that grew in
414 * between made the colourspace conversion write past the end of its own buffer, in the middle of
415 * an OpenMP region (Sentry 145090920, `_transform_lab_to_rgb_matrix.omp_outlined`). */
416 const dt_iop_roi_t roi_in = piece->roi_in;
417 const dt_iop_buffer_dsc_t dsc_in = piece->dsc_in;
418
419 const float *histogram_input = input;
420 float *transformed_input = NULL;
421 dt_iop_buffer_dsc_t input_dsc = previous_piece->dsc_out;
422
423 /* The cacheline was produced by `previous_piece` against ITS own roi_out, whichever reading of the
424 * plan was current then. The one authoritative bound on what may be read out of it is the size the
425 * cache actually reserved, so check the geometry we just snapshotted against it rather than trust
426 * two live fields to still agree. A mismatch means the plan moved under us: bail out and let the
427 * caller re-queue this refresh for the cacheline the new plan will publish. This also covers the
428 * histogram binning below, which addresses `dsc_in.channels` floats per pixel just the same
429 * (Sentry 144448425, `histogram_helper_cs_rgb` in `dt_histogram_worker.omp_outlined`). */
430 const size_t pixels = (size_t)roi_in.width * (size_t)roi_in.height;
431 const size_t bytes = pixels * (size_t)dsc_in.channels * sizeof(float);
432 if(roi_in.width <= 0 || roi_in.height <= 0 || dsc_in.channels < 1
433 || dt_pixel_cache_entry_get_size(input_entry) < bytes)
434 {
437 return FALSE;
438 }
439
440 /* `dt_colorspaces_apply_profile()` addresses 4 floats per pixel unconditionally, so the
441 * destination has to be that wide too -- `bytes` is sized on `dsc_in.channels`. The only
442 * conversions it performs are Lab<->RGB, which every module declares with 4 channels, so this
443 * only ever declines a pair the matrix path would have refused anyway. */
444 if(input_dsc.cst != dsc_in.cst && dsc_in.channels == 4
445 && !(dt_iop_colorspace_is_rgb(input_dsc.cst) && dt_iop_colorspace_is_rgb(dsc_in.cst)))
446 {
447 transformed_input = dt_alloc_align(bytes);
448
449 if(IS_NULL_PTR(transformed_input))
450 {
453 return FALSE;
454 }
455
456 memcpy(transformed_input, input, bytes);
457 dt_colorspaces_apply_profile(module->op, module->multi_name, transformed_input, transformed_input,
458 roi_in.width, roi_in.height,
459 input_dsc.cst, dsc_in.cst, &input_dsc.cst,
461 histogram_input = transformed_input;
462 }
463 else if(input_dsc.cst != dsc_in.cst)
464 {
465 input_dsc.cst = dsc_in.cst;
466 }
467
468 _refresh_module_histogram(dev->preview_pipe, piece, &roi_in, dsc_in.cst, histogram_input, module);
469
472 dt_free_align(transformed_input);
473
474 return TRUE;
475}
476
478{
479 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
480 if(!dev->preview_pipe->gui_observable_source) return;
481 /* Do not grab preview_pipe->busy_mutex from GUI thread: it may be held by
482 * the pipeline worker for long recomputations and would freeze the UI.
483 * We rely on non-blocking cache probes and the cache-wait manager instead. */
484
485 const char *ops[] = { "initialscale", "colorout", "gamma" };
486 uint64_t *pending_hashes[] = {
490 };
491
492 for(size_t i = 0; i < G_N_ELEMENTS(ops); i++)
493 {
496 if(IS_NULL_PTR(piece))
497 {
499 continue;
500 }
501
502 uint64_t hash = piece->global_hash;
503 if(!strcmp(ops[i], "gamma"))
504 {
505 const dt_dev_pixelpipe_iop_t *const previous_piece
507 hash = previous_piece ? previous_piece->global_hash : DT_PIXELPIPE_CACHE_HASH_INVALID;
508 }
509
511 {
513 *pending_hashes[i] = DT_PIXELPIPE_CACHE_HASH_INVALID;
514 continue;
515 }
516
517 // Avoid re-requesting the same cacheline on every history resync.
518 // While the same hash is pending, we wait for DT_SIGNAL_CACHELINE_READY
519 // to complete the refresh instead of feeding CACHE_REQUEST in a loop.
520 if(*pending_hashes[i] == hash) continue;
521
523 *pending_hashes[i] = DT_PIXELPIPE_CACHE_HASH_INVALID;
524 else
525 *pending_hashes[i] = hash;
526 }
527
528 GHashTable *seen_modules = g_hash_table_new(g_direct_hash, g_direct_equal);
529 for(GList *node = g_list_first(dev->preview_pipe->nodes); node; node = g_list_next(node))
530 {
531 dt_dev_pixelpipe_iop_t *piece = node->data;
532 if(IS_NULL_PTR(piece) || !piece->enabled) continue;
533 if(!(piece->request_histogram & DT_REQUEST_ON)) continue;
534 if((piece->request_histogram & DT_REQUEST_ONLY_IN_GUI) && !dev->gui_attached) continue;
535
536 g_hash_table_insert(seen_modules, piece->module, piece->module);
537
538 const dt_dev_pixelpipe_iop_t *const previous_piece
540 if(!IS_NULL_PTR(previous_piece)
541 && !_refresh_preview_module_histogram_for_hash(dev, piece->module, previous_piece->global_hash))
542 {
543 gboolean found = FALSE;
544 for(GList *l = _preview_refresh_state.module_histograms; l; l = g_list_next(l))
545 {
546 dt_histogram_pending_module_refresh_t *pending = l->data;
547 if(IS_NULL_PTR(pending) || pending->module != piece->module) continue;
548 pending->hash = previous_piece->global_hash;
549 found = TRUE;
550 break;
551 }
552
553 if(!found)
554 {
555 dt_histogram_pending_module_refresh_t *pending = g_malloc0(sizeof(*pending));
556 pending->module = piece->module;
557 pending->hash = previous_piece->global_hash;
559 = g_list_prepend(_preview_refresh_state.module_histograms, pending);
560 }
561 }
562 else
563 {
564 for(GList *l = _preview_refresh_state.module_histograms; l;)
565 {
566 GList *next = g_list_next(l);
567 dt_histogram_pending_module_refresh_t *pending = l->data;
568 if(pending && pending->module == piece->module)
569 {
571 = g_list_delete_link(_preview_refresh_state.module_histograms, l);
572 g_free(pending);
573 }
574 l = next;
575 }
576 }
577 }
578
579 // Remove stale pending module refreshes.
580 for(GList *l = _preview_refresh_state.module_histograms; l;)
581 {
582 GList *next = g_list_next(l);
583 dt_histogram_pending_module_refresh_t *pending = l->data;
584 if(IS_NULL_PTR(pending) || !g_hash_table_contains(seen_modules, pending->module))
585 {
587 = g_list_delete_link(_preview_refresh_state.module_histograms, l);
588 g_free(pending);
589 }
590 l = next;
591 }
592 g_hash_table_destroy(seen_modules);
593}
594
595static void _preview_history_resync_callback(gpointer instance, gpointer user_data)
596{
597 (void)instance;
598 (void)user_data;
600}
601
602static void _preview_cacheline_ready_callback(gpointer instance, const guint64 hash,
603 const guint64 producer_node_key, gpointer user_data)
604{
605 (void)instance;
606 (void)producer_node_key; // scope/module/picker buffer fetches match by producer node through
607 // the cache-wait manager; this backbuf-hash refresh trigger is exact-hash.
608 (void)user_data;
609
610 dt_develop_t *const dev = dt_dev_get_global();
611 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
613 {
614 _refresh_global_histogram_backbuf_for_hash(dev, "initialscale", hash);
616 }
617
619 {
620 _refresh_global_histogram_backbuf_for_hash(dev, "colorout", hash);
622 }
623
625 {
628 }
629
630 for(GList *l = _preview_refresh_state.module_histograms; l;)
631 {
632 GList *next = g_list_next(l);
633 dt_histogram_pending_module_refresh_t *pending = l->data;
634 if(pending->hash == hash)
635 {
636 _refresh_preview_module_histogram_for_hash(dev, pending->module, pending->hash);
638 g_free(pending);
639 }
640 l = next;
641 }
642}
643
644const char *name(struct dt_lib_module_t *self)
645{
646 return _("scopes");
647}
648
649const char **views(dt_lib_module_t *self)
650{
651 static const char *v[] = {"darkroom", "studio_capture", NULL};
652 return v;
653}
654
656{
658}
659
661{
662 return 1;
663}
664
666{
667 return 1000;
668}
669
670static void _update_picker_output(dt_lib_module_t *self);
672static void _update_everything(dt_lib_module_t *self);
673
674
676{
677 switch(value)
678 {
679 case 0:
680 {
681 d->op = "initialscale";
682 break;
683 }
684 case 1:
685 {
686 d->op = "colorout";
687 break;
688 }
689 case 2:
690 {
691 d->op = "gamma";
692 }
693 }
694}
695
696
698{
699 if(!strcmp(d->op, "initialscale") || !strcmp(d->op, "demosaic")) return 0;
700 if(!strcmp(d->op, "colorout")) return 1;
701 if(!strcmp(d->op, "gamma")) return 2;
702 return 2;
703}
704
705
706#ifdef _OPENMP
707#pragma omp declare simd \
708 aligned(rgb_in, xyz_out:64) \
709 uniform(rgb_in, xyz_out)
710#endif
712{
713 if(_backbuf_op_to_int(d) > 0)
714 {
715 // We are in display RGB
717 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
718 profile->lutsize, profile->nonlinearlut);
719 }
720 else
721 {
722 // We are in sensor RGB
724 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
725 profile->lutsize, profile->nonlinearlut);
726 }
727}
728
729
730#ifdef _OPENMP
731#pragma omp declare simd \
732 aligned(rgb_in, rgb_out:64) \
733 uniform(rgb_in, rgb_out)
734#endif
736{
737 if(_backbuf_op_to_int(d) > 0)
738 {
739 // We are in display RGB
741 rgb_out[c] = rgb_in[c];
742 }
743 else
744 {
745 // We are in the sensor/input RGB profile. Convert directly between the
746 // cached stage profile and the display profile so the swatch patch uses
747 // the same authoritative ICC path as the rest of the GUI.
750 dt_ioppr_transform_image_colorspace_rgb(rgb_in, rgb_out, 1, 1, profile_in, profile_out,
751 "[histogram] sample swatch");
752 }
753}
754
756{
757 d->cache.view = DT_LIB_HISTOGRAM_SCOPE_N;
758 d->cache.width = -1;
759 d->cache.height = -1;
760 d->cache.hash = (uint64_t)-1;
761 d->cache.zoom = -1.;
762}
763
765{
766 if(IS_NULL_PTR(d) || !d->pending_hashes) return;
767 g_array_set_size(d->pending_hashes, 0);
768}
769
770static gboolean _has_pending_hash(const dt_lib_histogram_t *d, const uint64_t hash)
771{
772 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
773
774 for(guint i = 0; i < d->pending_hashes->len; i++)
775 if(g_array_index(d->pending_hashes, uint64_t, i) == hash) return TRUE;
776
777 return FALSE;
778}
779
781{
782 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID || _has_pending_hash(d, hash)) return;
783 g_array_append_val(d->pending_hashes, hash);
784}
785
787{
788 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
789
790 for(guint i = 0; i < d->pending_hashes->len; i++)
791 {
792 if(g_array_index(d->pending_hashes, uint64_t, i) == hash)
793 {
794 g_array_remove_index(d->pending_hashes, i);
795 return TRUE;
796 }
797 }
798
799 return FALSE;
800}
801
802static uint64_t _get_live_histogram_hash(const char *op)
803{
804 dt_develop_t *const dev = dt_dev_get_global();
805 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
807 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read the live module
808 * hashes without the mutex and rely on the cache peek manager to handle
809 * synchronization and retries for missing cachelines. */
810 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
812
815
816 if(!strcmp(op, "gamma"))
817 piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
818
819 const uint64_t hash = piece ? piece->global_hash : DT_PIXELPIPE_CACHE_HASH_INVALID;
820 return hash;
821}
822
823static gboolean _histogram_refresh_idle(gpointer user_data)
824{
825 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
826 dt_lib_histogram_t *d = self ? self->data : NULL;
827 if(IS_NULL_PTR(d)) return G_SOURCE_REMOVE;
828
829 d->refresh_idle_source = 0;
830 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
831 _update_everything(self);
832 return G_SOURCE_REMOVE;
833}
834
836{
837 dt_lib_histogram_t *d = self ? self->data : NULL;
838 if(IS_NULL_PTR(d) || d->refresh_idle_source != 0) return;
839 d->refresh_idle_source = g_idle_add(_histogram_refresh_idle, self);
840}
841
843{
845 if(IS_NULL_PTR(d)) return;
846
849
850 for(GSList *samples = dt_dev_get_global()->color_picker.samples; samples; samples = g_slist_next(samples))
851 {
852 dt_colorpicker_sample_t *sample = samples->data;
853 if(sample->locked) continue;
855 }
856
857 if(dt_dev_get_global()->color_picker.picker)
859
860 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
861 _update_everything(self);
862}
863
864
865static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op);
866
881{
882 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->backbuf)) return FALSE;
883
884 return !IS_NULL_PTR(_get_backbuf_source_piece(d->backbuf, d->op));
885}
886
888{
889 gtk_widget_queue_draw(d->scope_draw);
890}
891
892uint32_t _find_max_histogram(const uint32_t *const restrict bins, const size_t binning_size)
893{
894 uint32_t max_hist = 0;
895 __OMP_PARALLEL_FOR_SIMD__(aligned(bins: 64) reduction(max: max_hist) )
896 for(size_t k = 0; k < binning_size; k++) if(bins[k] > max_hist) max_hist = bins[k];
897
898 return max_hist;
899}
900
901static inline void _sample_raw_box_to_image_norm(const dt_colorpicker_sample_t *const sample, float box[4])
902{
903 memcpy(box, sample->box, sizeof(float) * 4);
905}
906
907static inline void _sample_raw_point_to_image_norm(const dt_colorpicker_sample_t *const sample, float point[2])
908{
909 point[0] = sample->point[0];
910 point[1] = sample->point[1];
912}
913
914
915// `stride` is the row length of `image` in pixels (the source buffer width). Callers must never
916// pass a widget/panel dimension here: the binning window [min_x;max_x]×[min_y;max_y] is expressed
917// in source-buffer coordinates, and a foreign stride silently re-samples a sheared subset of the
918// image (issue #828: the histogram shape used to change with the left panel width).
919static inline void _bin_pixels_histogram_in_roi(const float *const restrict image, uint32_t *const restrict bins,
920 const size_t min_x, const size_t max_x,
921 const size_t min_y, const size_t max_y,
922 const size_t stride)
923{
924 //fprintf(stdout, "computing histogram from x = [%lu;%lu], y = [%lu;%lu]\n", min_x, max_x, min_y, max_y);
925 // Process
926#ifdef _OPENMP
927#ifndef _WIN32
928__OMP_PARALLEL_FOR__(reduction(+: bins[0: HISTOGRAM_BINS * 4]) collapse(3))
929#else
930__OMP_PARALLEL_FOR__(shared(bins) collapse(3))
931#endif
932#endif
933 for(size_t i = min_y; i < max_y; i++)
934 for(size_t j = min_x; j < max_x; j++)
935 for(size_t c = 0; c < 3; c++)
936 {
937 const float value = image[(i * stride + j) * 4 + c];
938 const size_t index = (size_t)CLAMP(roundf(value * (HISTOGRAM_BINS - 1)), 0, HISTOGRAM_BINS - 1);
939 bins[index * 4 + c]++;
940 }
941}
942
943
944static inline void _bin_pickers_histogram(const float *const restrict image,
945 const size_t width, const size_t height,
946 uint32_t *bins,
948{
949 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
950 {
951 float image_box[4] = { 0.0f };
952 _sample_raw_box_to_image_norm(sample, image_box);
953 const size_t box[4] = {
954 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
955 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
956 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
957 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
958 };
959 _bin_pixels_histogram_in_roi(image, bins, box[0], box[2], box[1], box[3], width);
960 }
961 else
962 {
963 float image_point[2] = { 0.0f };
964 _sample_raw_point_to_image_norm(sample, image_point);
965 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
966 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
967 _bin_pixels_histogram_in_roi(image, bins, x, x + 1, y, y + 1, width);
968 }
969}
970
972{
973 if(IS_NULL_PTR(d)) return FALSE;
974 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
975 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
976 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
977 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->picker_button));
978 return restrict_active && picker_active;
979}
980
991{
992 const float *data;
993 size_t width;
994 size_t height;
995 gboolean owned;
997
998static dt_histogram_scope_buf_t _orient_scope_buf(const void *data, const dt_backbuf_t *backbuf,
999 const char *op)
1000{
1001 dt_histogram_scope_buf_t result = { .data = (const float *)data, .width = backbuf->width,
1002 .height = backbuf->height, .owned = FALSE };
1003
1004 const dt_image_orientation_t orientation = g_strcmp0(op, "initialscale")
1006 if(orientation == ORIENTATION_NONE) return result;
1007
1008 const size_t src_w = backbuf->width;
1009 const size_t src_h = backbuf->height;
1010 const size_t dst_w = (orientation & ORIENTATION_SWAP_XY) ? src_h : src_w;
1011 const size_t dst_h = (orientation & ORIENTATION_SWAP_XY) ? src_w : src_h;
1012 const size_t bpp = 4 * sizeof(float);
1013
1014 float *const oriented = dt_alloc_align(dst_w * dst_h * bpp);
1015 if(IS_NULL_PTR(oriented)) return result;
1016
1017 /* wd/ht and fwd/fht are always the *source* (pre-orientation) dimensions here -- this mirrors
1018 * iop/flip.c's own process(), the only other real caller of this function with a non-identity
1019 * orientation: fwd/fht only feed the internal mirror-offset math, not the output layout, which
1020 * dt_imageio_flip_buffers derives on its own from wd/ht and the orientation bits. */
1021 dt_imageio_flip_buffers((char *)oriented, (const char *)data, bpp, (int)src_w, (int)src_h,
1022 (int)src_w, (int)src_h, (int)(src_w * bpp), orientation);
1023
1024 result.data = oriented;
1025 result.width = dst_w;
1026 result.height = dst_h;
1027 result.owned = TRUE;
1028 return result;
1029}
1030
1031static void _process_histogram(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1032 const int height, dt_lib_histogram_t *d)
1033{
1034 // Histogram backbuffers already own their keepalive ref in the pipeline state. Drawing only borrows them.
1035 struct dt_pixel_cache_entry_t *entry = NULL;
1036 void *data = NULL;
1037 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1038 if(IS_NULL_PTR(piece)) return;
1039 if(!IS_NULL_PTR(d))
1040 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1041 if(!dt_dev_pixelpipe_cache_peek_gui(dt_dev_get_global()->preview_pipe, piece, &data, &entry,
1042 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
1044 !IS_NULL_PTR(d) ? d->module : NULL))
1045 {
1046 return;
1047 }
1048
1050
1052
1053 uint32_t *bins = calloc(4 * HISTOGRAM_BINS, sizeof(uint32_t));
1054 if(IS_NULL_PTR(bins))
1055 {
1056 if(oriented.owned) dt_free_align(oriented.data);
1058 return;
1059 }
1060
1061 const gboolean restrict_mode = _is_restricted(d);
1062 if(restrict_mode)
1063 {
1064 // Bin only areas within color pickers
1066 while(samples)
1067 {
1068 dt_colorpicker_sample_t *sample = samples->data;
1070 bins, sample);
1071 samples = g_slist_next(samples);
1072 }
1073
1074 if(dt_dev_get_global()->color_picker.picker)
1076 bins, dt_dev_get_global()->color_picker.primary_sample);
1077 }
1078 else
1079 {
1080 _bin_pixels_histogram_in_roi(oriented.data, bins, 0, oriented.width, 0, oriented.height, oriented.width);
1081 }
1082
1083 if(oriented.owned) dt_free_align(oriented.data);
1085
1087
1088 // Draw thingy
1089 if(overall_histogram_max > 0)
1090 {
1091 // Paint background
1092 cairo_rectangle(cr, 0, 0, width, height);
1093 set_color(cr, dt_bauhaus_get_global()->graph_bg);
1094 cairo_fill(cr);
1095
1096 set_color(cr, dt_bauhaus_get_global()->graph_grid);
1097 dt_draw_grid(cr, 4, 0, 0, width, height);
1098
1099 cairo_save(cr);
1101 cairo_translate(cr, 0, height);
1102 cairo_scale(cr, width / 255.0, - (double)height / (double)(1. + log(overall_histogram_max)));
1104
1105 for(int k = 0; k < 3; k++)
1106 {
1107 set_color(cr, dt_bauhaus_get_global()->graph_colors[k]);
1108 dt_draw_histogram_8(cr, bins, 4, k, FALSE);
1109 }
1110
1113 cairo_paint_with_alpha(cr, 0.5);
1114 cairo_restore(cr);
1115 }
1116
1117 dt_free(bins);
1118}
1119
1120
1121static inline void _bin_pixels_waveform_in_roi(const float *const restrict image, uint32_t *const restrict bins,
1122 const size_t min_x, const size_t max_x,
1123 const size_t min_y, const size_t max_y,
1124 const size_t source_width, const size_t source_height,
1125 const size_t tone_bins, const size_t raster_extent,
1126 const gboolean vertical)
1127{
1128 if(vertical)
1129 {
1130 /* In vertical waveform/parade, pixel value is drawn along the widget width and source image
1131 * position along the widget height. We therefore loop on final raster rows and collect the
1132 * source rows that map there, so both axes have the same detail as the drawn surface. */
1134 for(size_t raster_y = 0; raster_y < raster_extent; raster_y++)
1135 {
1136 const double source_y0d = (double)raster_y * (double)source_height / (double)raster_extent;
1137 const double source_y1d = (double)(raster_y + 1) * (double)source_height / (double)raster_extent;
1138 const size_t source_y0 = MAX(min_y, (size_t)floor(source_y0d));
1139 const size_t source_y1 = MIN(max_y, (size_t)ceil(source_y1d));
1140 if(source_y0 >= source_y1) continue;
1141
1142 for(size_t i = source_y0; i < source_y1; i++)
1143 {
1144 const double overlap = MIN((double)(i + 1), source_y1d) - MAX((double)i, source_y0d);
1145 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1147 for(size_t j = min_x; j < max_x; j++)
1148 for(size_t c = 0; c < 3; c++)
1149 {
1150 const float value = image[(i * source_width + j) * 4 + c];
1151 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1152 const size_t tone0 = (size_t)floorf(tone_position);
1153 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1154 const float tone_mix = tone_position - (float)tone0;
1155 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1156 const uint32_t weight0 = weight - weight1;
1157 bins[(raster_y * tone_bins + tone0) * 4 + c] += weight0;
1158 bins[(raster_y * tone_bins + tone1) * 4 + c] += weight1;
1159 }
1160 }
1161 }
1162 }
1163 else
1164 {
1165 /* In horizontal waveform/parade, pixel value is drawn along the widget height and source image
1166 * position along the widget width. Each final raster column owns disjoint bins, avoiding races. */
1168 for(size_t raster_x = 0; raster_x < raster_extent; raster_x++)
1169 {
1170 const double source_x0d = (double)raster_x * (double)source_width / (double)raster_extent;
1171 const double source_x1d = (double)(raster_x + 1) * (double)source_width / (double)raster_extent;
1172 const size_t source_x0 = MAX(min_x, (size_t)floor(source_x0d));
1173 const size_t source_x1 = MIN(max_x, (size_t)ceil(source_x1d));
1174 if(source_x0 >= source_x1) continue;
1175
1176 for(size_t j = source_x0; j < source_x1; j++)
1177 {
1178 const double overlap = MIN((double)(j + 1), source_x1d) - MAX((double)j, source_x0d);
1179 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1181 for(size_t i = min_y; i < max_y; i++)
1182 for(size_t c = 0; c < 3; c++)
1183 {
1184 const float value = image[(i * source_width + j) * 4 + c];
1185 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1186 const size_t tone0 = (size_t)floorf(tone_position);
1187 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1188 const float tone_mix = tone_position - (float)tone0;
1189 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1190 const uint32_t weight0 = weight - weight1;
1191 bins[((tone_bins - 1 - tone0) * raster_extent + raster_x) * 4 + c] += weight0;
1192 bins[((tone_bins - 1 - tone1) * raster_extent + raster_x) * 4 + c] += weight1;
1193 }
1194 }
1195 }
1196 }
1197}
1198
1199static inline void _bin_pickers_waveforms(const float *const restrict image, uint32_t *const restrict bins,
1200 const size_t width, const size_t height,
1201 const size_t tone_bins, const size_t raster_extent,
1202 const gboolean vertical, dt_colorpicker_sample_t *sample)
1203{
1204 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1205 {
1206 float image_box[4] = { 0.0f };
1208 const size_t box[4] = {
1209 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1210 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1211 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1212 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1213 };
1214 _bin_pixels_waveform_in_roi(image, bins, box[0], box[2], box[1], box[3],
1215 width, height, tone_bins, raster_extent, vertical);
1216 }
1217 else
1218 {
1219 float image_point[2] = { 0.0f };
1221 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1222 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1223 _bin_pixels_waveform_in_roi(image, bins, x, x + 1, y, y + 1,
1224 width, height, tone_bins, raster_extent, vertical);
1225 }
1226}
1227
1228
1229static inline void _bin_pixels_waveform(const float *const restrict image, uint32_t *const restrict bins,
1230 const size_t width, const size_t height, const size_t binning_size,
1231 const size_t tone_bins, const size_t raster_extent,
1232 const gboolean vertical, const gboolean restricted)
1233{
1234 // Init
1236 for(size_t k = 0; k < binning_size; k++) bins[k] = 0;
1237
1238 if(restricted)
1239 {
1240 // Bin only areas within color pickers
1242 while(samples)
1243 {
1244 dt_colorpicker_sample_t *sample = samples->data;
1245 _bin_pickers_waveforms(image, bins, width, height, tone_bins, raster_extent, vertical, sample);
1246 samples = g_slist_next(samples);
1247 }
1248
1249 if(dt_dev_get_global()->color_picker.picker)
1251 dt_dev_get_global()->color_picker.primary_sample);
1252 }
1253 else
1254 {
1255 // Bin the whole image
1257 width, height, tone_bins, raster_extent, vertical);
1258 }
1259}
1260
1261static void _create_waveform_image(const uint32_t *const restrict bins, uint8_t *const restrict image,
1262 const uint32_t max_hist,
1263 const size_t width, const size_t height)
1264{
1265 __OMP_FOR_SIMD__(aligned(image, bins: 64) )
1266 for(size_t k = 0; k < height * width * 4; k += 4)
1267 {
1268 image[k + 3] = 255; // alpha
1269
1270 // We apply a slight "gamma" boost for legibility
1271 image[k + 2] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 0] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1272 image[k + 1] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 1] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1273 image[k + 0] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 2] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1274 }
1275}
1276
1277static void _paint_waveform(cairo_t *cr, uint8_t *const restrict image, const int width, const int height,
1278 const size_t img_width, const size_t img_height, const size_t tone_bins,
1279 const gboolean vertical)
1280{
1281 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1283 const double scale_w = (vertical) ? (double)width / (double)tone_bins
1284 : (double)width / (double)img_width;
1285 const double scale_h = (vertical) ? (double)height / (double)img_height
1286 : (double)height / (double)tone_bins;
1291 cairo_paint(cr);
1293}
1294
1295
1296static void _process_waveform(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1297 const int height, const gboolean vertical, const gboolean parade,
1299{
1300 struct dt_pixel_cache_entry_t *entry = NULL;
1301 void *data = NULL;
1302 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1303 if(IS_NULL_PTR(piece)) return;
1304 if(!IS_NULL_PTR(d))
1305 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1306 if(!dt_dev_pixelpipe_cache_peek_gui(dt_dev_get_global()->preview_pipe, piece, &data, &entry,
1307 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
1309 !IS_NULL_PTR(d) ? d->module : NULL))
1310 {
1311 return;
1312 }
1313
1315
1317
1318 /* The value axis must match the final drawing axis: widget height for horizontal scopes and
1319 * widget width for vertical scopes. Keeping it fixed forces Cairo to stretch sparse
1320 * value rows/columns, which shows as hatching when the scope is resized. */
1321 const size_t tone_bins = MAX(2, (vertical) ? width : height);
1322 /* The source-position axis must also match the final raster size. Otherwise Cairo stretches the
1323 * preview-pipe dimensions to the scope allocation, which can leave visible regular stripes. */
1324 const size_t raster_extent = MAX(2, (vertical)
1325 ? (parade ? (height + 2) / 3 : height)
1326 : (parade ? (width + 2) / 3 : width));
1327 const size_t binning_size = 4 * tone_bins * raster_extent;
1328 const size_t img_width = (parade) ? width : ((vertical) ? tone_bins : raster_extent);
1329 const size_t img_height = (parade) ? height : ((vertical) ? raster_extent : tone_bins);
1330 const size_t image_size = 4 * img_width * img_height;
1331 const size_t source_axis = (vertical) ? oriented.height : oriented.width;
1332 float source_min = INFINITY;
1333 float source_max = -INFINITY;
1334 size_t source_clipped = 0;
1335 size_t source_nan = 0;
1336 const size_t source_step = MAX((size_t)1, (oriented.width * oriented.height) / 4096);
1337 for(size_t pixel = 0; pixel < oriented.width * oriented.height; pixel += source_step)
1338 for(size_t c = 0; c < 3; c++)
1339 {
1340 const float value = oriented.data[pixel * 4 + c];
1341 if(isnan(value))
1342 source_nan++;
1343 else
1344 {
1347 source_clipped += (value < 0.f || value > 1.f);
1348 }
1349 }
1350 uint32_t *smooth_bins = NULL;
1351
1354 "[histogram/scope] waveform setup op=%s parade=%d vertical=%d widget=%dx%d backbuf=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT " "
1355 "tone_bins=%" G_GSIZE_FORMAT " raster_extent=%" G_GSIZE_FORMAT " source_axis=%" G_GSIZE_FORMAT
1356 " source_per_raster=%.4f binning_size=%" G_GSIZE_FORMAT " "
1357 "source_value=%0.6f..%0.6f clipped=%" G_GSIZE_FORMAT " nan=%" G_GSIZE_FORMAT " sample_step=%" G_GSIZE_FORMAT "\n",
1358 op, parade, vertical, width, height, oriented.width, oriented.height,
1362 binning_size * sizeof(uint32_t),
1363 0);
1364 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1365 image_size * sizeof(uint8_t),
1366 0);
1367 if(IS_NULL_PTR(image) || IS_NULL_PTR(bins))
1368 {
1371 "[histogram/scope] waveform allocation failed bins=%p image=%p binning_size=%" G_GSIZE_FORMAT
1372 " image_size=%" G_GSIZE_FORMAT "\n",
1373 (void *)bins, (void *)image, binning_size, image_size);
1374 goto error;
1375 }
1376
1377 // 1. Pixel binning along columns/rows, aka compute a column/row-wise histogram
1380
1381 const uint32_t *render_bins = bins;
1382 int smoothing_passes = 0;
1383#if DT_LIB_HISTOGRAM_SCOPE_ENABLE_SMOOTHING \
1384 && (DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES > 0 \
1385 || DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES > 0)
1389 {
1390 /* The waveform/parade is a spatially-indexed histogram. Even after fractional resampling,
1391 * isolated source-column content can create one-pixel density modulation that reads as stries.
1392 * The passes below are explicit at call site because the source/destination ownership matters:
1393 * bins and smooth_bins alternate roles, and render_bins always records the last valid raster. */
1394 const uint32_t original_force = 256u - smoothing_force;
1395
1397 {
1398 const uint32_t *const source_bins = render_bins;
1399 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1400
1401 if(vertical)
1402 {
1403 for(size_t axis = 0; axis < raster_extent; axis++)
1404 {
1405 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1406 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1407 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1408 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1409 for(size_t tone = 0; tone < tone_bins; tone++)
1410 for(size_t c = 0; c < 4; c++)
1411 {
1412 const size_t index = (axis * tone_bins + tone) * 4 + c;
1413 const uint64_t smoothed = source_bins[(axis_m2 * tone_bins + tone) * 4 + c]
1414 + 4u * source_bins[(axis_m1 * tone_bins + tone) * 4 + c]
1415 + 6u * source_bins[index]
1416 + 4u * source_bins[(axis_p1 * tone_bins + tone) * 4 + c]
1417 + source_bins[(axis_p2 * tone_bins + tone) * 4 + c];
1419 + smoothing_force * ((smoothed + 8u) / 16u);
1420 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1421 }
1422 }
1423 }
1424 else
1425 {
1426 for(size_t tone = 0; tone < tone_bins; tone++)
1427 for(size_t axis = 0; axis < raster_extent; axis++)
1428 {
1429 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1430 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1431 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1432 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1433 for(size_t c = 0; c < 4; c++)
1434 {
1435 const size_t index = (tone * raster_extent + axis) * 4 + c;
1436 const uint64_t smoothed = source_bins[(tone * raster_extent + axis_m2) * 4 + c]
1437 + 4u * source_bins[(tone * raster_extent + axis_m1) * 4 + c]
1438 + 6u * source_bins[index]
1439 + 4u * source_bins[(tone * raster_extent + axis_p1) * 4 + c]
1440 + source_bins[(tone * raster_extent + axis_p2) * 4 + c];
1442 + smoothing_force * ((smoothed + 8u) / 16u);
1443 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1444 }
1445 }
1446 }
1447
1450 }
1451
1453 {
1454 const uint32_t *const source_bins = render_bins;
1455 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1456
1457 if(vertical)
1458 {
1459 for(size_t axis = 0; axis < raster_extent; axis++)
1460 for(size_t tone = 0; tone < tone_bins; tone++)
1461 {
1462 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1463 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1464 for(size_t c = 0; c < 4; c++)
1465 {
1466 const size_t index = (axis * tone_bins + tone) * 4 + c;
1467 const uint64_t smoothed = source_bins[(axis * tone_bins + tone_m1) * 4 + c]
1468 + 2u * source_bins[index]
1469 + source_bins[(axis * tone_bins + tone_p1) * 4 + c];
1471 + smoothing_force * ((smoothed + 2u) / 4u);
1472 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1473 }
1474 }
1475 }
1476 else
1477 {
1478 for(size_t tone = 0; tone < tone_bins; tone++)
1479 {
1480 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1481 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1482 for(size_t axis = 0; axis < raster_extent; axis++)
1483 for(size_t c = 0; c < 4; c++)
1484 {
1485 const size_t index = (tone * raster_extent + axis) * 4 + c;
1486 const uint64_t smoothed = source_bins[(tone_m1 * raster_extent + axis) * 4 + c]
1487 + 2u * source_bins[index]
1488 + source_bins[(tone_p1 * raster_extent + axis) * 4 + c];
1490 + smoothing_force * ((smoothed + 2u) / 4u);
1491 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1492 }
1493 }
1494 }
1495
1498 }
1499 }
1500#endif
1501
1502 // 2. Paint image.
1503 // In a 1D histogram, pixel frequencies are shown as height (y axis) for each RGB quantum (x axis).
1504 // Here, we do a sort of 2D histogram : pixel frequencies are shown as opacity ("z" axis),
1505 // for each image column (x axis), for each RGB quantum (y axis)
1507 size_t empty_axis = 0;
1509 uint64_t max_axis = 0;
1510 uint32_t min_axis_peak = UINT32_MAX;
1511 uint32_t max_axis_peak = 0;
1512 for(size_t axis = 0; axis < raster_extent; axis++)
1513 {
1514 uint64_t axis_total = 0;
1515 uint32_t axis_peak = 0;
1516 for(size_t tone = 0; tone < tone_bins; tone++)
1517 {
1518 const size_t pixel = (vertical) ? axis * tone_bins + tone : tone * raster_extent + axis;
1519 for(size_t c = 0; c < 3; c++)
1520 {
1521 const uint32_t value = render_bins[pixel * 4 + c];
1522 axis_total += value;
1524 }
1525 }
1526
1527 if(axis_total == 0) empty_axis++;
1532 }
1533 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1535 "[histogram/scope] waveform raster op=%s parade=%d vertical=%d image=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT
1536 " stride=%" G_GSIZE_FORMAT " overall_max=%u empty_axis=%" G_GSIZE_FORMAT " axis_total=%" PRIu64 "..%" PRIu64
1537 " axis_peak=%u..%u smooth=%d scale=%0.4fx%0.4f\n",
1538 op, parade, vertical, img_width, img_height, stride, overall_max_hist,
1540 (double)width / (double)img_width, (double)height / (double)img_height);
1541
1542 // 3. Send everything to GUI buffer.
1543 if(overall_max_hist > 0)
1544 {
1545 if(parade)
1546 {
1547 /* Build the parade directly in the final widget raster. This keeps channel thirds on integer
1548 * device pixels and removes all Cairo source-offset/scaling interactions from the diagnostic
1549 * path: if stries remain, they are in the histogram bins, not in channel painting. */
1550 for(size_t k = 0; k < image_size; k++) image[k] = 0;
1551
1552 for(size_t c = 0; c < 3; c++)
1553 {
1554 const size_t x0 = vertical ? 0 : c * width / 3;
1555 const size_t x1 = vertical ? width : (c + 1) * width / 3;
1556 const size_t y0 = vertical ? c * height / 3 : 0;
1557 const size_t y1 = vertical ? (c + 1) * height / 3 : height;
1558 const size_t channel_width = x1 - x0;
1559 const size_t channel_height = y1 - y0;
1560 if(channel_width == 0 || channel_height == 0) continue;
1561
1562 for(size_t y = y0; y < y1; y++)
1563 for(size_t x = x0; x < x1; x++)
1564 {
1565 const size_t axis = vertical
1566 ? (y - y0) * raster_extent / channel_height
1567 : (x - x0) * raster_extent / channel_width;
1568 const size_t tone = vertical ? x : y;
1569 const size_t bin_index = vertical ? (axis * tone_bins + tone) * 4 + c
1570 : (tone * raster_extent + axis) * 4 + c;
1571 const uint8_t value = (uint8_t)CLAMP(roundf(powf((float)render_bins[bin_index] / (float)overall_max_hist, GAMMA) * 255.f), 0, 255);
1572 const size_t image_index = (y * img_width + x) * 4;
1573 image[image_index + 2 - c] = value;
1574 image[image_index + 3] = 255;
1575 }
1576 }
1577 }
1578 else
1579 _create_waveform_image(render_bins, image, overall_max_hist, img_width, img_height);
1580
1581 cairo_save(cr);
1582
1583 // Paint background - Color not exposed to user theme because this is tricky
1584 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1585 cairo_paint(cr);
1586
1600 cairo_set_source_rgb(cr, 0.21, 0.21, 0.21);
1601 const gboolean raw_stage = (!strcmp(op, "initialscale") || !strcmp(op, "demosaic"));
1602 if(raw_stage)
1603 {
1604 /* The regular 4x4 grid has a tonal guide at 0.75, which is not an integer raw bit stop.
1605 * In raw mode, non-parade scopes keep only the global spatial guides here; the tone guides
1606 * are drawn below from powers of two. Parade intentionally has no spatial guides. */
1607 for(int k = 1; !parade && k < 4; k++)
1608 {
1609 if(vertical)
1610 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1611 else
1612 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1613 cairo_stroke(cr);
1614 }
1615 }
1616 else if(parade)
1617 {
1618 /* Parade already splits the spatial axis into RGB strips. Keep only the regular tone-axis
1619 * guides here so the grid does not add misleading source-position lines inside each strip. */
1620 for(int k = 1; k < 4; k++)
1621 {
1622 if(vertical)
1623 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1624 else
1625 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1626 cairo_stroke(cr);
1627 }
1628 }
1629 else
1630 dt_draw_grid(cr, 4, 0, 0, width, height);
1631
1632 if(raw_stage)
1633 {
1634 /* Raw data is linear, so integer bit stops are powers of two in normalized tone space.
1635 * Drawing those stops on the tone axis makes exposure clipping and low-bit detail readable
1636 * without depending on the current scope height. */
1637 for(int stop = 0; stop <= 8; stop++)
1638 {
1639 const double value = 1.0 / (double)(1u << stop);
1640 if(value < (double)DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE) break;
1641
1642 if(vertical)
1643 {
1644 const double x = value * (double)width;
1645 dt_draw_line(cr, x, 0, x, height);
1646 }
1647 else
1648 {
1649 const double y = (1.0 - value) * (double)height;
1650 dt_draw_line(cr, 0, y, width, y);
1651 }
1652 cairo_stroke(cr);
1653 }
1654 }
1655
1656 _paint_waveform(cr, image, width, height, img_width, img_height, tone_bins, vertical);
1657
1658 cairo_restore(cr);
1659 }
1660
1661error:;
1665 if(oriented.owned) dt_free_align(oriented.data);
1667}
1668
1669static float _Luv_to_vectorscope_coord_zoom(const float value, const float zoom)
1670{
1671 // Convert u, v coordinates of Luv vectors into x, y coordinates
1672 // into the space of the vectorscope square buffer
1673 return (value + zoom) * (HISTOGRAM_BINS - 1) / (2.f * zoom);
1674}
1675
1676static float _vectorscope_coord_zoom_to_Luv(const float value, const float zoom)
1677{
1678 // Inverse of the above
1679 return value * (2.f * zoom) / (HISTOGRAM_BINS - 1) - zoom;
1680}
1681
1682static void _bin_pixels_vectorscope_in_roi(const float *const restrict image, uint32_t *const restrict vectorscope,
1683 const size_t min_x, const size_t max_x, const size_t min_y,
1684 const size_t max_y, const size_t width, const float zoom,
1686{
1687#ifdef _OPENMP
1688#ifndef _WIN32
1689#pragma omp parallel for default(firstprivate) \
1690 reduction(+: vectorscope[0: HISTOGRAM_BINS * HISTOGRAM_BINS])
1691#else
1692#pragma omp parallel for default(firstprivate) \
1693 shared(vectorscope)
1694#endif
1695#endif
1696 for(size_t i = min_y; i < max_y; i++)
1697 for(size_t j = min_x; j < max_x; j++)
1698 {
1699 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1700 dt_aligned_pixel_t xyY = { 0.f };
1701 dt_aligned_pixel_t Luv = { 0.f };
1702 _scope_pixel_to_xyz(image + (i * width + j) * 4, XYZ_D50, d);
1705
1706 // Luv is sampled between 0 and 100.0f, u and v between +/- 220.f
1707 const size_t U_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[1], zoom)), 0, HISTOGRAM_BINS - 1);
1708 const size_t V_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[2], zoom)), 0, HISTOGRAM_BINS - 1);
1709
1710 // We put V = 0 at the bottom of the image.
1712 }
1713}
1714
1715static inline void _bin_pickers_vectorscope(const float *const restrict image,
1716 uint32_t *const restrict vectorscope, const size_t width,
1717 const size_t height, const float zoom, dt_lib_histogram_t *d,
1718 const dt_colorpicker_sample_t *const sample)
1719{
1720 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1721 {
1722 float image_box[4] = { 0.0f };
1724 const size_t box[4] = {
1725 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1726 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1727 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1728 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1729 };
1730 _bin_pixels_vectorscope_in_roi(image, vectorscope, box[0], box[2], box[1], box[3], width, zoom, d);
1731 }
1732 else
1733 {
1734 float image_point[2] = { 0.0f };
1736 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1737 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1738 _bin_pixels_vectorscope_in_roi(image, vectorscope, x, x + 1, y, y + 1, width, zoom, d);
1739 }
1740}
1741
1742
1743static void _create_vectorscope_image(const uint32_t *const restrict vectorscope, uint8_t *const restrict image,
1744 const uint32_t max_hist, const float zoom)
1745{
1748 for(size_t i = 0; i < HISTOGRAM_BINS; i++)
1749 for(size_t j = 0; j < HISTOGRAM_BINS; j++)
1750 {
1751 const size_t index = (HISTOGRAM_BINS - 1 - i) * HISTOGRAM_BINS + j;
1752 const float value = sqrtf((float)vectorscope[index] / (float)max_hist);
1753 dt_aligned_pixel_t RGB = { 0.f };
1754 // RGB gamuts tend to have a max chroma around L = 67
1756 dt_aligned_pixel_t xyY = { 0.f };
1757 dt_aligned_pixel_t XYZ = { 0.f };
1759 for(int c = 0; c < 2; c++) xyY[c] = fmaxf(xyY[c], 0.f);
1761 for(int c = 0; c < 3; c++) XYZ[c] = fmaxf(XYZ[c], 0.f);
1763
1764 // We will normalize RGB to get closer to display peak emission
1765 for(int c = 0; c < 3; c++) RGB[c] = fmaxf(RGB[c], 0.f);
1766 const float max_RGB = fmax(RGB[0], fmaxf(RGB[1], RGB[2]));
1767 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1768
1769 image[index * 4 + 3] = (uint8_t)roundf(value * 255.f); // alpha
1770 // Premultiply alpha
1771 image[index * 4 + 2] = (uint8_t)roundf(powf(RGB[0] * value, 1.f / 2.2f) * 255.f);
1772 image[index * 4 + 1] = (uint8_t)roundf(powf(RGB[1] * value, 1.f / 2.2f) * 255.f);
1773 image[index * 4 + 0] = (uint8_t)roundf(powf(RGB[2] * value, 1.f / 2.2f) * 255.f);
1774 }
1775}
1776
1777static void _bin_vectorscope(const float *const restrict image, uint32_t *const vectorscope,
1778 const size_t width, const size_t height,
1779 const float zoom, dt_lib_histogram_t *d)
1780{
1782 for(size_t k = 0; k < HISTOGRAM_BINS * HISTOGRAM_BINS; k++) vectorscope[k] = 0;
1783
1784 if(_is_restricted(d))
1785 {
1786 // Bin only areas within color pickers
1788 while(samples)
1789 {
1790 dt_colorpicker_sample_t *sample = samples->data;
1791 _bin_pickers_vectorscope(image, vectorscope, width, height, zoom, d, sample);
1792 samples = g_slist_next(samples);
1793 }
1794
1795 if(dt_dev_get_global()->color_picker.picker)
1797 dt_dev_get_global()->color_picker.primary_sample);
1798 }
1799 else
1800 {
1801 // Bin the whole image
1803 }
1804}
1805
1806
1807static void _process_vectorscope(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1808 const int height, const float zoom, dt_lib_histogram_t *d)
1809{
1811 if(IS_NULL_PTR(profile) || IS_NULL_PTR(d)) return;
1812
1813 struct dt_pixel_cache_entry_t *entry = NULL;
1814 void *data = NULL;
1815 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1816 if(IS_NULL_PTR(piece)) return;
1817 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1818 if(!dt_dev_pixelpipe_cache_peek_gui(dt_dev_get_global()->preview_pipe, piece, &data, &entry, &d->scope_wait,
1820 {
1821 return;
1822 }
1823
1825
1827 HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint32_t),
1828 0);
1829 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1830 4 * HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint8_t),
1831 0);
1832 if(IS_NULL_PTR(vectorscope) || IS_NULL_PTR(image)) goto error;
1833
1834 _bin_vectorscope(data, vectorscope, backbuf->width, backbuf->height, zoom, d);
1835
1838
1839 // 2. Draw
1840 if(max_hist > 0)
1841 {
1844 const int side = MIN(width, height);
1845 cairo_translate(cr, (double)(width - side) / 2., (double)(height - side) / 2.);
1846 cairo_scale(cr, (double)side / HISTOGRAM_BINS, (double)side / HISTOGRAM_BINS);
1847
1848 const double radius = (float)(HISTOGRAM_BINS - 1) / 2 - DT_PIXEL_APPLY_DPI(1.);
1849 const double x_center = (float)(HISTOGRAM_BINS - 1) / 2;
1850
1851 // Background circle - Color will not be exposed to user theme because this is tricky
1852 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1853 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1854 cairo_fill(cr);
1855
1856 // Center circle
1857 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1858 cairo_arc(cr, x_center, x_center, 2., 0., 2. * M_PI);
1859 cairo_fill(cr);
1860
1861 // Concentric circles
1862 for(int k = 0; k < 4; k++)
1863 {
1864 cairo_arc(cr, x_center, x_center, (double)k * HISTOGRAM_BINS / 8., 0., 2. * M_PI);
1865 cairo_stroke(cr);
1866 }
1867
1868 // RGB space primaries and secondaries
1869 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 },
1870 { 0.f, 1.f, 1.f, 0.f }, { 0.f, 0.f, 1.f, 0.f }, { 1.f, 0.f, 1.f, 0.f } };
1871
1872 cairo_save(cr);
1873 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1874 cairo_clip(cr);
1875
1876 for(size_t k = 0; k < 6; k++)
1877 {
1878 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1879 dt_aligned_pixel_t xyY = { 0.f };
1880 dt_aligned_pixel_t Luv = { 0.f };
1881 dt_ioppr_rgb_matrix_to_xyz(colors[k], XYZ_D50, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
1882 profile->lutsize, profile->nonlinearlut);
1885
1886 const double x = _Luv_to_vectorscope_coord_zoom(Luv[1], zoom);
1887 // Remember v = 0 is at the bottom of the square while Cairo puts y = 0 on top
1888 const double y = HISTOGRAM_BINS - 1 - _Luv_to_vectorscope_coord_zoom(Luv[2], zoom);
1889
1890 // First, draw hue angles
1891 dt_aligned_pixel_t Lch = { 0.f };
1893
1894 const double delta_x = radius * cosf(Lch[2]);
1895 const double delta_y = radius * sinf(Lch[2]);
1896 const double destination_x = x_center + delta_x;
1897 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1900 cairo_set_source_rgba(cr, colors[k][0], colors[k][1], colors[k][2], 0.5);
1901 cairo_stroke(cr);
1902
1903 // Then draw color squares and ensure center is filled with scope background color
1904 const double half_square = DT_PIXEL_APPLY_DPI(4);
1905 cairo_arc(cr, x, y, half_square, 0, 2. * M_PI);
1906 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1908 cairo_set_source_rgb(cr, colors[k][0], colors[k][1], colors[k][2]);
1909 cairo_stroke(cr);
1910 }
1911 cairo_restore(cr);
1912
1913 // Hues ring
1914 cairo_save(cr);
1915 cairo_arc(cr, x_center, x_center, radius - DT_PIXEL_APPLY_DPI(1.), 0., 2. * M_PI);
1916 cairo_set_source_rgb(cr, 0.33, 0.33, 0.33);
1918 cairo_stroke(cr);
1919 cairo_restore(cr);
1920
1921 for(size_t h = 0; h < 180; h++)
1922 {
1923 dt_aligned_pixel_t Lch = { 50.f, 110.f, h / 180.f * 2.f * M_PI_F, 1.f };
1924 dt_aligned_pixel_t Luv = { 0.f };
1925 dt_aligned_pixel_t xyY = { 0.f };
1926 dt_aligned_pixel_t XYZ = { 0.f };
1927 dt_aligned_pixel_t RGB = { 0.f };
1932 const float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
1933 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1934 const double delta_x = (radius - DT_PIXEL_APPLY_DPI(1.)) * cosf(Lch[2]);
1935 const double delta_y = (radius - DT_PIXEL_APPLY_DPI(1.)) * sinf(Lch[2]);
1936 const double destination_x = x_center + delta_x;
1937 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1938 cairo_set_source_rgba(cr, RGB[0], RGB[1], RGB[2], 0.7);
1940 cairo_fill(cr);
1941 }
1942
1943 // Actual vectorscope
1944 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1945 cairo_clip(cr);
1948 cairo_paint(cr);
1950
1951 // Draw the skin tones area
1952 // Values obtained with :
1953 // get_skin_tones_range();
1954 const float max_c = 49.34f;
1955 const float min_c = 9.00f;
1956 const float max_h = 0.99f;
1957 const float min_h = 0.26f;
1958
1959 const float n_w_x = min_c * cosf(max_h);
1960 const float n_w_y = min_c * sinf(max_h);
1961 const float n_e_x = max_c * cosf(max_h);
1962 const float n_e_y = max_c * sinf(max_h);
1963 const float s_e_x = max_c * cosf(min_h);
1964 const float s_e_y = max_c * sinf(min_h);
1965 const float s_w_x = min_c * cosf(min_h);
1966 const float s_w_y = min_c * sinf(min_h);
1977 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1978 cairo_stroke(cr);
1979 }
1980
1981error:;
1985}
1986
1987gboolean _needs_recompute(dt_lib_histogram_t *d, const int width, const int height)
1988{
1989 // Check if cache is up-to-date
1991 gboolean hash_match = (d->cache.hash == dt_dev_backbuf_get_hash(d->backbuf));
1992 gboolean size_match = (d->cache.width == width && d->cache.height == height);
1993 gboolean zoom_match = (d->cache.zoom == d->zoom);
1994 gboolean view_match = (d->cache.view == view);
1995 gboolean has_surface = (!IS_NULL_PTR(d->cst));
1997}
1998
1999
2000static gboolean _draw_callback(GtkWidget *widget, cairo_t *crf, gpointer user_data)
2001{
2002 // Note: the draw callback is called from our own callback (mapped to "preview pipe finished recomputing" signal)
2003 // but is also called by Gtk when the main window is resized, exposed, etc.
2004 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2005 if(IS_NULL_PTR(d->cst)) return 1;
2006 cairo_set_source_surface(crf, d->cst, 0, 0);
2008 return 0;
2009}
2010
2011
2013{
2014 GtkAllocation allocation;
2015 gtk_widget_get_allocation(d->scope_draw, &allocation);
2016 *width = allocation.width;
2017 *height = allocation.height;
2018}
2019
2044{
2045 if(_is_restricted(d))
2046 {
2047 cairo_save(cr);
2049
2054 pango_layout_set_text(layout, _("Restricted"), -1);
2055
2056 PangoRectangle ink;
2057 PangoRectangle logical;
2059
2060 const double padding = DT_PIXEL_APPLY_DPI(2.);
2061 const double text_padding = DT_PIXEL_APPLY_DPI(1.5);
2062 const double label_x = padding;
2063 const double label_y = MAX(padding, height - logical.height - 2. * text_padding - padding);
2064
2065 set_color(cr, dt_bauhaus_get_global()->graph_scope_restricted);
2067 pango_cairo_show_layout(cr, layout);
2068
2070 g_object_unref(layout);
2071 cairo_restore(cr);
2072 }
2073}
2074
2076{
2077 if(IS_NULL_PTR(d->cst)) return 1;
2078
2079 dt_times_t start;
2080 dt_get_times(&start);
2081
2082 int width, height;
2084
2085 // Save cache integrity
2086 d->cache.hash = dt_dev_backbuf_get_hash(d->backbuf);
2087 d->cache.width = width;
2088 d->cache.height = height;
2089 d->cache.zoom = d->zoom;
2090 d->cache.view = d->scope;
2091
2092 cairo_t *cr = cairo_create(d->cst);
2093
2094 // Paint background
2096 cairo_set_line_width(cr, 1.); // we want exactly 1 px no matter the resolution
2097
2098 // Paint content
2099 switch(d->scope)
2100 {
2102 {
2103 _process_histogram(d->backbuf, d->op, cr, width, height, d);
2104 break;
2105 }
2107 {
2108 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, FALSE, d);
2109 break;
2110 }
2112 {
2113 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, FALSE, d);
2114 break;
2115 }
2117 {
2118 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, TRUE, d);
2119 break;
2120 }
2122 {
2123 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, TRUE, d);
2124 break;
2125 }
2127 {
2128 _process_vectorscope(d->backbuf, d->op, cr, width, height, d->zoom, d);
2129 break;
2130 }
2131 default:
2132 break;
2133 }
2134
2136
2137 cairo_destroy(cr);
2138 dt_show_times_f(&start, "[histogram]", "redraw");
2139 return 0;
2140}
2141
2143{
2144 if(d->cst && cairo_surface_get_reference_count(d->cst) > 0) cairo_surface_destroy(d->cst);
2145 if(d->cst && cairo_surface_get_reference_count(d->cst) == 0) d->cst = NULL;
2146}
2147
2149{
2150 int width, height;
2152
2154 {
2156 // If width and height have changed, we need to recreate the surface.
2157 // Recreate it anyway.
2160 // Don't send gtk_queue_redraw event from here, catch the return value and do it in the calling function
2161 //fprintf(stdout, "recreate histograms\n");
2162 return 1;
2163 }
2164
2165 return 0;
2166}
2167
2168static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op)
2169{
2170 dt_develop_t *const dev = dt_dev_get_global();
2171 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2172 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(op) || IS_NULL_PTR(backbuf)) return NULL;
2173
2174 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2175 if(IS_NULL_PTR(module)) return NULL;
2176
2178 if(IS_NULL_PTR(piece)) return NULL;
2179
2180 if(!strcmp(op, "gamma"))
2181 piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
2182
2183 if(IS_NULL_PTR(piece) || piece->global_hash != dt_dev_backbuf_get_hash(backbuf)) return NULL;
2184 return piece;
2185}
2186
2209static gboolean _resolve_backbuf_sampling_source(const char *const op, const dt_backbuf_t *const backbuf,
2210 dt_iop_roi_t *const roi, dt_iop_buffer_dsc_t *const dsc,
2211 double *const iop_order, int *const direction)
2212{
2213 dt_develop_t *const dev = dt_dev_get_global();
2214 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2215 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;
2216 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read live module state
2217 * without blocking and let the cache peek manager handle synchronization. */
2218 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2219 if(IS_NULL_PTR(module)) return FALSE;
2220
2222 if(IS_NULL_PTR(piece)) return FALSE;
2223
2224 uint64_t hash = piece->global_hash;
2225 *roi = piece->roi_out;
2226 *dsc = piece->dsc_out;
2227 *iop_order = module->iop_order;
2229
2230 if(!strcmp(op, "gamma"))
2231 {
2233 if(IS_NULL_PTR(previous_piece)) return FALSE;
2234
2235 hash = previous_piece->global_hash;
2236 *roi = previous_piece->roi_out;
2237 *dsc = previous_piece->dsc_out;
2239 }
2240
2241 const gboolean valid = hash == dt_dev_backbuf_get_hash(backbuf);
2242 return valid;
2243}
2244
2245static void _pixelpipe_pick_from_image(const dt_backbuf_t *const backbuf,
2247 const char *op)
2248{
2249 struct dt_pixel_cache_entry_t *entry = NULL;
2250 void *data = NULL;
2252 if(IS_NULL_PTR(source_piece)) return;
2253 dt_dev_pixelpipe_cache_wait_set_owner(&d->picker_wait, "histogram-picker", d->module);
2255 &d->picker_wait, _histogram_restart_cache_wait, d->module))
2256 {
2257 return;
2258 }
2259
2261
2262 const float *const pixel = data;
2263 dt_iop_buffer_dsc_t dsc = {
2264 .channels = 4,
2265 .datatype = TYPE_FLOAT,
2266 .bpp = 4 * sizeof(float),
2267 .cst = IOP_CS_RGB
2268 };
2269 dt_iop_roi_t roi = {
2270 .x = 0,
2271 .y = 0,
2272 .width = (int)backbuf->width,
2273 .height = (int)backbuf->height,
2274 .scale = 1.0
2275 };
2276 double iop_order = 0.0;
2278 const gboolean have_live_stage = _resolve_backbuf_sampling_source(op, backbuf, &roi, &dsc, &iop_order, &direction);
2279 int box[4];
2280
2281 if(have_live_stage)
2282 {
2283 dt_boundingbox_t fbox = { 0.0f };
2284
2285 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2286 {
2289 }
2290 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2291 {
2294 fbox[2] = fbox[0];
2295 fbox[3] = fbox[1];
2296 }
2297
2298 dt_dev_distort_backtransform_plus(dt_dev_get_global()->preview_pipe, iop_order,
2299 direction, fbox, 2);
2300
2301 /* The backtransform returns stage coordinates in the sampled piece space. Shift them by the
2302 stage ROI before clamping so the picker box matches the published cache window exactly. */
2303 fbox[0] -= roi.x;
2304 fbox[1] -= roi.y;
2305 fbox[2] -= roi.x;
2306 fbox[3] -= roi.y;
2307
2308 box[0] = fminf(fbox[0], fbox[2]);
2309 box[1] = fminf(fbox[1], fbox[3]);
2310 box[2] = fmaxf(fbox[0], fbox[2]);
2311 box[3] = fmaxf(fbox[1], fbox[3]);
2312
2313 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2314 {
2315 box[2] += 1;
2316 box[3] += 1;
2317 }
2318
2319 box[0] = MIN(roi.width - 1, MAX(0, box[0]));
2320 box[1] = MIN(roi.height - 1, MAX(0, box[1]));
2321 box[2] = MIN(roi.width - 1, MAX(0, box[2]));
2322 box[3] = MIN(roi.height - 1, MAX(0, box[3]));
2323 }
2324 else
2325 {
2327 return;
2328 }
2329
2330 dt_aligned_pixel_t mean = { 0.0f };
2333
2334 dt_color_picker_helper(&dsc, pixel, &roi, box, mean, min, max, dsc.cst, IOP_CS_RGB, NULL);
2335
2337 {
2338 sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k] = mean[k];
2341 }
2342
2343 dt_lib_histogram_t scope = *d;
2344 scope.op = op;
2345
2347 {
2348 _scope_pixel_to_display_rgb(sample->scope[k], sample->display[k], &scope);
2349
2350 dt_aligned_pixel_t XYZ = { 0.f };
2351 _scope_pixel_to_xyz(sample->scope[k], XYZ, &scope);
2352 dt_XYZ_to_Lab(XYZ, sample->lab[k]);
2353 }
2354
2356}
2357
2359{
2361 while(samples)
2362 {
2363 dt_colorpicker_sample_t *sample = samples->data;
2364 if(!sample->locked)
2365 {
2366 const char *const op = sample->backbuf_op[0] ? sample->backbuf_op : d->op;
2368 if(backbuf) _pixelpipe_pick_from_image(backbuf, sample, d, op);
2369 }
2370 samples = g_slist_next(samples);
2371 }
2372
2373 if(dt_dev_get_global()->color_picker.picker)
2374 _pixelpipe_pick_from_image(d->backbuf, dt_dev_get_global()->color_picker.primary_sample, d, d->op);
2375}
2376
2377
2379{
2381
2383 {
2385 }
2386
2388
2389 for(GSList *samples = dt_dev_get_global()->color_picker.samples;
2390 samples;
2391 samples = g_slist_next(samples))
2392 {
2393 _update_sample_label(self, samples->data);
2394 }
2395
2397
2398 // allow live sample button to work for iop samples
2399 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button),
2400 !IS_NULL_PTR(dt_dev_get_global()->color_picker.picker));
2401}
2402
2404{
2405 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return FALSE;
2406
2408 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
2409
2410 if(!_is_backbuf_ready(d)) return FALSE;
2411
2412 /* In restricted mode the scope bins only picker rectangles/points. Moving the
2413 * global picker does not change the preview cache hash, so invalidate the
2414 * Cairo scope cache explicitly before recomputing from the same backbuffer. */
2415 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2416 _reset_cache(d);
2417
2418 _update_everything(self);
2419 return TRUE;
2420}
2421
2422
2423static void _lib_histogram_history_resync_callback(gpointer instance, dt_lib_module_t *self)
2424{
2425 (void)instance;
2427}
2428
2429static void _lib_histogram_cacheline_ready_callback(gpointer instance, const guint64 hash,
2430 const guint64 producer_node_key, dt_lib_module_t *self)
2431{
2432 (void)instance;
2435 if(IS_NULL_PTR(d) || !_remove_pending_hash(d, hash)) return;
2436
2438}
2439
2440// this is only called in darkroom view when history was resynchronized
2455
2477
2478
2479static void _set_stage(dt_lib_module_t *self, int value)
2480{
2483 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2484
2485 // Vectorscope is meaningless for raw Bayer data
2486 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2487 {
2489 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2490 }
2491
2494}
2495
2497{
2499 d->scope = scope;
2500 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2502}
2503
2504
2506{
2507 _reset_cache(d);
2509 // Don't start a redraw from here, Gtk does it automatically on resize event
2510}
2511
2513{
2514 if(d->scope != DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE) return FALSE;
2515
2516 int delta_y = 0;
2517 if(!dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y)) return TRUE;
2518
2519 const float new_value = 4.f * delta_y + d->zoom;
2520
2522 {
2523 d->zoom = new_value;
2524 dt_conf_set_float("plugin/darkroom/histogram/zoom", new_value);
2525 if(_is_backbuf_ready(d))
2526 {
2529 }
2530 }
2531 return TRUE;
2532}
2533
2534static int _scope_resize_handle_get_size(gpointer user_data)
2535{
2536 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2537 return gtk_widget_get_allocated_height(d->scope_draw);
2538}
2539
2547static int _scope_resize_handle_resize(int requested_size, gboolean finished, gpointer user_data)
2548{
2549 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2550 int window_height;
2552
2554 const int max_height = MAX(min_height, MIN(DT_PIXEL_APPLY_DPI(DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT),
2555 window_height * 3 / 4));
2556 d->scope_height = CLAMP(requested_size, min_height, max_height);
2557
2558 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
2559 gtk_widget_queue_resize(d->scope_draw);
2560 if(finished)
2562
2563 return d->scope_height;
2564}
2565
2567{
2568 d->op = dt_conf_get_string_const("plugin/darkroom/histogram/op");
2569 if(!strcmp(d->op, "demosaic"))
2570 {
2571 d->op = "initialscale";
2572 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2573 }
2574 d->backbuf = _get_histogram_backbuf(dt_dev_get_global(), d->op);
2575 d->zoom = fminf(fmaxf(dt_conf_get_float("plugin/darkroom/histogram/zoom"), 32.f), 252.f);
2576
2578 dt_conf_get_int("plugin/darkroom/histogram/display"), 0, DT_LIB_HISTOGRAM_SCOPE_N - 1);
2579
2580 // Vectorscope is not valid on raw Bayer data; fall back to histogram
2581 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2583}
2584
2585
2587{
2588 const guint width = gtk_widget_get_allocated_width(widget);
2589 const guint height = gtk_widget_get_allocated_height(widget);
2590
2591 set_color(cr, sample->swatch);
2592 cairo_rectangle(cr, 0, 0, width, height);
2593 cairo_fill (cr);
2594
2595 // if the sample is locked we want to add a lock
2596 if(sample->locked)
2597 {
2598 const int border = DT_PIXEL_APPLY_DPI(2);
2599 const int icon_width = width - 2 * border;
2600 const int icon_height = height - 2 * border;
2601 if(icon_width > 0 && icon_height > 0)
2602 {
2603 GdkRGBA fg_color;
2605
2607 dtgtk_cairo_paint_lock(cr, border, border, icon_width, icon_height, 0, NULL);
2608 }
2609 }
2610
2611 return FALSE;
2612}
2613
2615{
2616 dt_lib_histogram_t *d = self->data;
2617 const dt_lib_colorpicker_statistic_t statistic
2619
2620 sample->swatch.red = sample->display[statistic][0];
2621 sample->swatch.green = sample->display[statistic][1];
2622 sample->swatch.blue = sample->display[statistic][2];
2624 sample->label_rgb[ch] = (int)roundf(sample->scope[statistic][ch] * 255.f);
2625
2626 // Setting the output label
2627 char text[128] = { 0 };
2628 dt_aligned_pixel_t alt = { 0 };
2629
2630 switch(d->model)
2631 {
2633 snprintf(text, sizeof(text), "%6d %6d %6d", sample->label_rgb[0], sample->label_rgb[1], sample->label_rgb[2]);
2634 break;
2635
2637 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f",
2638 CLAMP(sample->lab[statistic][0], .0f, 100.0f), sample->lab[statistic][1], sample->lab[statistic][2]);
2639 break;
2640
2642 dt_Lab_2_LCH(sample->lab[statistic], alt);
2643 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", CLAMP(alt[0], .0f, 100.0f), alt[1], alt[2] * 360.f);
2644 break;
2645
2647 dt_RGB_2_HSL(sample->scope[statistic], alt);
2648 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2649 break;
2650
2652 dt_RGB_2_HSV(sample->scope[statistic], alt);
2653 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2654 break;
2655
2656 default:
2658 snprintf(text, sizeof(text), "\342\227\216");
2659 break;
2660 }
2661
2664
2666}
2667
2669{
2670 _update_everything(self);
2671}
2672
2673static void _picker_button_toggled(GtkToggleButton *button, dt_lib_histogram_t *d)
2674{
2675 const gboolean picker_active = gtk_toggle_button_get_active(button);
2676 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), picker_active);
2677
2678 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
2679 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
2680 if(restrict_active)
2681 {
2682 /* The picker state is part of restricted-scope rendering but not of the
2683 * preview backbuffer hash. Invalidate on both activation and deactivation
2684 * so the same cached pixels are rebinned for the newly selected scope. */
2685 _reset_cache(d);
2687 }
2688}
2689
2690/* set sample area proxy impl */
2691
2693{
2695 dt_lib_histogram_t *d = self->data;
2696 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2697 _reset_cache(d);
2698 _update_everything(self);
2699}
2700
2701static void _set_sample_point(dt_lib_module_t *self, const float pos[2])
2702{
2704 dt_lib_histogram_t *d = self->data;
2705 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2706 _reset_cache(d);
2707 _update_everything(self);
2708}
2709
2710
2711static gboolean _sample_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
2713{
2714 gchar **sample_parts = g_malloc0_n(14, sizeof(char*));
2715
2716 sample_parts[3] = g_strdup_printf("%22s(0x%02X%02X%02X)\n<big><b>%14s</b></big>", " ",
2717 CLAMP(sample->label_rgb[0], 0, 255), CLAMP(sample->label_rgb[1], 0, 255),
2718 CLAMP(sample->label_rgb[2], 0, 255), _("RGB"));
2719 sample_parts[7] = g_strdup_printf("\n<big><b>%14s</b></big>", _("Lab"));
2720
2721 for(int i = 0; i < DT_LIB_COLORPICKER_STATISTIC_N; i++)
2722 {
2723 sample_parts[i] = g_strdup_printf("<span background='#%02X%02X%02X'>%32s</span>",
2724 (int)roundf(CLAMP(sample->display[i][0], 0.f, 1.f) * 255.f),
2725 (int)roundf(CLAMP(sample->display[i][1], 0.f, 1.f) * 255.f),
2726 (int)roundf(CLAMP(sample->display[i][2], 0.f, 1.f) * 255.f), " ");
2727
2728 sample_parts[i + 4] = g_strdup_printf("<span foreground='#FF7F7F'>%6d</span> "
2729 "<span foreground='#7FFF7F'>%6d</span> "
2730 "<span foreground='#7F7FFF'>%6d</span> %s",
2731 (int)roundf(sample->scope[i][0] * 255.f),
2732 (int)roundf(sample->scope[i][1] * 255.f),
2733 (int)roundf(sample->scope[i][2] * 255.f),
2735
2736 sample_parts[i + 8] = g_strdup_printf("%6.02f %6.02f %6.02f %s",
2737 sample->lab[i][0], sample->lab[i][1], sample->lab[i][2],
2739 }
2740
2743 sample_parts[11] = g_strdup_printf("\n<big><b>%14s</b></big>", _("color"));
2747
2748 gchar *tooltip_text = g_strjoinv("\n", sample_parts);
2750
2752
2754
2755 return TRUE;
2756}
2757
2759{
2760 dt_lib_histogram_t *d = self->data;
2761 d->statistic = dt_bauhaus_combobox_get(widget);
2762 dt_dev_get_global()->color_picker.statistic = (int)d->statistic;
2763 dt_conf_set_string("ui_last/colorpicker_mode", dt_lib_colorpicker_statistic_names[d->statistic]);
2764 _update_everything(self);
2765}
2766
2768{
2769 dt_lib_histogram_t *d = self->data;
2770 d->model = dt_bauhaus_combobox_get(widget);
2771 dt_conf_set_string("ui_last/colorpicker_model", dt_lib_colorpicker_model_names[d->model]);
2772 _update_everything(self);
2773}
2774
2794
2795static gboolean _sample_enter_callback(GtkWidget *widget, GdkEvent *event, dt_colorpicker_sample_t *sample)
2796{
2797 if(dt_dev_get_global()->color_picker.picker)
2798 {
2801 }
2802
2803 return FALSE;
2804}
2805
2806static gboolean _sample_leave_callback(GtkWidget *widget, GdkEvent *event, gpointer data)
2807{
2808 if(event->crossing.detail == GDK_NOTIFY_INFERIOR) return FALSE;
2809
2810 if(dt_dev_get_global()->color_picker.selected_sample)
2811 {
2814 }
2815
2816 return FALSE;
2817}
2818
2820{
2823 = g_slist_remove(dt_dev_get_global()->color_picker.samples, (gpointer)sample);
2824 dt_free(sample);
2825}
2826
2827static void _remove_sample_cb(GtkButton *widget, dt_colorpicker_sample_t *sample)
2828{
2830 dt_lib_histogram_t *d = self ? self->data : NULL;
2831 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram") && !IS_NULL_PTR(d))
2832 _reset_cache(d);
2833
2834 _remove_sample(sample);
2836 if(!IS_NULL_PTR(self))
2837 _update_everything(self);
2838}
2839
2841{
2842 if(event->button == 1)
2843 {
2844 sample->locked = !sample->locked;
2845 gtk_widget_queue_draw(widget);
2846 }
2847 else if(event->button == 3)
2848 {
2849 // copy to active picker
2852
2853 // no active picker, too much iffy GTK work to activate a default
2854 if(IS_NULL_PTR(picker)) return FALSE;
2855
2856 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2857 {
2858 float point[2] = { 0.0f };
2860 _set_sample_point(self, point);
2861 }
2862 else if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2863 {
2864 dt_boundingbox_t box = { 0.0f };
2865 _sample_raw_box_to_image_norm(sample, box);
2866 _set_sample_box_area(self, box);
2867 }
2868 else
2869 return FALSE;
2870
2872 }
2873 return FALSE;
2874}
2875
2876static void _add_sample(GtkButton *widget, dt_lib_module_t *self)
2877{
2878 dt_develop_t *const dev = dt_dev_get_global();
2879 dt_lib_histogram_t *d = self->data;
2880
2882 return;
2883
2885 if(IS_NULL_PTR(sample)) return;
2886
2888 sample->locked = FALSE;
2889 g_strlcpy(sample->backbuf_op, d->op ? d->op : "", sizeof(sample->backbuf_op));
2890
2891 sample->container = gtk_event_box_new();
2893 g_signal_connect(G_OBJECT(sample->container), "enter-notify-event", G_CALLBACK(_sample_enter_callback), sample);
2894 g_signal_connect(G_OBJECT(sample->container), "leave-notify-event", G_CALLBACK(_sample_leave_callback), sample);
2895
2898
2901 gtk_widget_set_tooltip_text(sample->color_patch, _("hover to highlight sample on canvas,\n"
2902 "click to lock sample,\n"
2903 "right-click to load sample area into active color picker"));
2904 g_signal_connect(G_OBJECT(sample->color_patch), "button-press-event", G_CALLBACK(_live_sample_button), sample);
2906
2911
2912 sample->output_label = gtk_label_new("");
2913 dt_gui_add_class(sample->output_label, "dt_monospace");
2917 g_signal_connect(G_OBJECT(sample->output_label), "query-tooltip", G_CALLBACK(_sample_tooltip_callback), sample);
2920
2922 g_signal_connect(G_OBJECT(delete_button), "clicked", G_CALLBACK(_remove_sample_cb), sample);
2923 gtk_box_pack_start(GTK_BOX(container), delete_button, FALSE, FALSE, 0);
2924
2925 gtk_box_pack_start(GTK_BOX(d->samples_container), sample->container, FALSE, FALSE, 0);
2927
2929 = g_slist_append(dev->color_picker.samples, sample);
2930
2931 // remove emphasis on primary sample from mouseover on this button
2933
2934 // Updating the display
2935 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2936 _reset_cache(d);
2937 _update_everything(self);
2938}
2939
2940static void _display_samples_changed(GtkToggleButton *button, dt_lib_module_t *self)
2941{
2942 dt_conf_set_bool("ui_last/colorpicker_display_samples", gtk_toggle_button_get_active(button));
2944 _update_everything(self);
2946}
2947
2948static void _restrict_histogram_changed(GtkToggleButton *button, dt_lib_module_t *self)
2949{
2950 dt_lib_histogram_t *d = self->data;
2951 const gboolean restrict_active = gtk_toggle_button_get_active(button);
2952 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
2954 dt_conf_set_bool("ui_last/colorpicker_restrict_histogram", restrict_active);
2956
2957 if(picker_active)
2958 {
2959 /* Restrict toggling changes the scope bins between full-image and picker
2960 * geometry while the preview backbuffer hash stays identical. Make the
2961 * cached Cairo surface dirty so _update_everything() renders the new mode. */
2962 _reset_cache(d);
2963 }
2964 _update_everything(self);
2965}
2966
2968{
2969 dt_lib_histogram_t *d = self->data;
2971
2973
2974 // Resetting the picked colors
2975 for(int i = 0; i < 3; i++)
2976 {
2977 for(int s = 0; s < DT_LIB_COLORPICKER_STATISTIC_N; s++)
2978 {
2979 primary_sample->display[s][i] = 0.f;
2980 primary_sample->scope[s][i] = 0.f;
2981 primary_sample->lab[s][i] = 0.f;
2982 }
2983 primary_sample->label_rgb[i] = 0;
2984 }
2985 primary_sample->swatch.red = primary_sample->swatch.green
2986 = primary_sample->swatch.blue = 0.0;
2987
2989
2990 // Removing any live samples
2991 while(dt_dev_get_global()->color_picker.samples)
2992 _remove_sample(dt_dev_get_global()->color_picker.samples->data);
2993
2994 // Resetting GUI elements
2995 dt_bauhaus_combobox_set(d->statistic_selector, 0);
2996 dt_bauhaus_combobox_set(d->color_mode_selector, 0);
2997 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box)))
2998 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box), FALSE);
2999
3000 _reset_cache(d);
3001 _set_params(d);
3004
3006}
3007
3008static void _pref_stage_toggled(GtkCheckMenuItem *item, gpointer user_data)
3009{
3010 if(!gtk_check_menu_item_get_active(item)) return;
3012 _set_stage(self, GPOINTER_TO_INT(user_data));
3013}
3014
3015static void _pref_display_toggled(GtkCheckMenuItem *item, gpointer user_data)
3016{
3017 if(!gtk_check_menu_item_get_active(item)) return;
3020}
3021
3022void set_preferences(void *menu, dt_lib_module_t *self)
3023{
3025
3026 // "Show data from" submenu
3027 GtkWidget *mi_stage = gtk_menu_item_new_with_label(_("Show data from"));
3030
3031 const char *stage_labels[] = { N_("Raw image"), N_("Output color profile"), N_("Final display"), NULL };
3032 const int current_stage = _backbuf_op_to_int(d);
3034 for(int i = 0; stage_labels[i]; i++)
3035 {
3039 g_object_set_data(G_OBJECT(item), "self", self);
3042 }
3045
3046 // "Display" submenu
3050
3051 const char *display_labels[] = {
3052 N_("Histogram"), N_("Waveform (horizontal)"), N_("Waveform (vertical)"),
3053 N_("Parade (horizontal)"), N_("Parade (vertical)"), N_("Vectorscope"), NULL
3054 };
3055 const gboolean vectorscope_ok = strcmp(d->op, "initialscale");
3057 for(int i = 0; display_labels[i]; i++)
3058 {
3061 if((dt_lib_histogram_scope_type_t)i == d->scope)
3065 g_object_set_data(G_OBJECT(item), "self", self);
3068 }
3071}
3072
3073void gui_init(dt_lib_module_t *self)
3074{
3075 dt_develop_t *const dev = dt_dev_get_global();
3076 /* initialize ui widgets */
3078 if(d) memset(d, 0, sizeof(dt_lib_histogram_t));
3079 self->data = (void *)d;
3080 d->module = self;
3081 d->cst = NULL;
3082 d->pending_hashes = g_array_new(FALSE, FALSE, sizeof(uint64_t));
3083 d->refresh_idle_source = 0;
3084
3086 d->scope_draw = gtk_drawing_area_new();
3091 d->scope_height = CLAMP(d->scope_height,
3094 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
3095 g_signal_connect(G_OBJECT(d->scope_draw), "draw", G_CALLBACK(_draw_callback), d);
3096 g_signal_connect(G_OBJECT(d->scope_draw), "scroll-event", G_CALLBACK(_area_scrolled_callback), d);
3097 g_signal_connect(G_OBJECT(d->scope_draw), "size-allocate", G_CALLBACK(_resize_callback), d);
3098
3099 // The grip floats on the scope's bottom edge (overlay) so it takes no layout space and leaves no
3100 // margin-like gap; invisible until hovered.
3104
3118 _("Drag to resize the scope vertically"),
3121 gtk_overlay_add_overlay(GTK_OVERLAY(scope_overlay), d->scope_resize_handle);
3122
3124 (&d->cs,
3125 "plugins/darkroom/colorpicker/show",
3126 _("Color picker"),
3127 GTK_BOX(self->widget), GTK_PACK_END);
3128
3129 // The develop module owns the picker state because both the preview pipe and the GUI need to
3130 // observe it. Histogram only binds its widgets to that shared state.
3131 dev->color_picker.histogram_module = self;
3133 dev->color_picker.display_samples = dt_conf_get_bool("ui_last/colorpicker_display_samples");
3134 dev->color_picker.restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
3135 dev->color_picker.primary_sample->swatch.alpha = 1.0;
3136
3137 const char *str = dt_conf_get_string_const("ui_last/colorpicker_model");
3140 if(g_strcmp0(str, *names) == 0)
3141 d->model = i;
3142
3143 str = dt_conf_get_string_const("ui_last/colorpicker_mode");
3146 if(g_strcmp0(str, *names) == 0)
3147 d->statistic = i;
3148
3149 // The color patch
3151 gtk_widget_set_name(GTK_WIDGET(color_patch_wrapper), "color-picker-area");
3152
3153 // The picker button, mode and statistic combo boxes
3155 gtk_box_pack_start(GTK_BOX(d->cs.container), picker_row, TRUE, TRUE, 0);
3156
3157 d->statistic_selector = dt_bauhaus_combobox_new_full(
3158 dt_bauhaus_get_global(), NULL, NULL, _("select which statistic to show"), d->statistic,
3161 dt_bauhaus_widget_set_label(d->statistic_selector, NULL);
3162 gtk_widget_set_valign(d->statistic_selector, GTK_ALIGN_CENTER);
3163 gtk_box_pack_start(GTK_BOX(picker_row), d->statistic_selector, TRUE, TRUE, 0);
3164
3165 d->color_mode_selector = dt_bauhaus_combobox_new_full(
3166 dt_bauhaus_get_global(), NULL, NULL, _("select which color mode to use"), d->model,
3169 dt_bauhaus_widget_set_label(d->color_mode_selector, NULL);
3170 gtk_widget_set_valign(d->color_mode_selector, GTK_ALIGN_CENTER);
3171 gtk_box_pack_start(GTK_BOX(picker_row), d->color_mode_selector, TRUE, TRUE, 0);
3172
3174 gtk_widget_set_tooltip_text(d->picker_button, _("turn on color picker\nctrl+click or right-click to select an area"));
3175 gtk_widget_set_name(GTK_WIDGET(d->picker_button), "color-picker-button");
3176 g_signal_connect(G_OBJECT(d->picker_button), "toggled", G_CALLBACK(_picker_button_toggled), d);
3177
3178 // The small sample, label and add button
3185 gtk_box_pack_start(GTK_BOX(d->cs.container), sample_row_events, TRUE, TRUE, 0);
3186
3189
3193
3198
3203 dt_gui_add_class(label, "dt_monospace");
3210
3211 d->add_sample_button = dtgtk_button_new(dtgtk_cairo_paint_square_plus, 0, NULL);
3212 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), FALSE);
3213 g_signal_connect(G_OBJECT(d->add_sample_button), "clicked", G_CALLBACK(_add_sample), self);
3214 gtk_box_pack_end(GTK_BOX(sample_row), d->add_sample_button, FALSE, FALSE, 0);
3215
3216 // Adding the live samples section
3217 label = dt_ui_section_label_new(_("Live samples"));
3218 gtk_box_pack_start(GTK_BOX(d->cs.container), label, TRUE, TRUE, 0);
3219
3221 gtk_box_pack_start(GTK_BOX(d->cs.container),
3222 dt_ui_scroll_wrap(d->samples_container, 1, "plugins/darkroom/colorpicker/windowheight",
3224
3225 d->display_samples_check_box = gtk_check_button_new_with_label(_("Display samples on image"));
3226 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->display_samples_check_box))),
3228 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box),
3229 dt_conf_get_bool("ui_last/colorpicker_display_samples"));
3230 g_signal_connect(G_OBJECT(d->display_samples_check_box), "toggled",
3232 gtk_box_pack_start(GTK_BOX(d->cs.container), d->display_samples_check_box, TRUE, TRUE, 0);
3233
3234 d->restrict_button = gtk_check_button_new_with_label(_("Restrict scope to selection"));
3236 gboolean restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
3237 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->restrict_button), restrict_histogram);
3238 g_signal_connect(G_OBJECT(d->restrict_button), "toggled", G_CALLBACK(_restrict_histogram_changed), self);
3239 gtk_box_pack_start(GTK_BOX(d->cs.container), d->restrict_button, TRUE, TRUE, 0);
3240
3241 _reset_cache(d);
3242 _set_params(d);
3243}
3244
3246{
3247 dt_develop_t *const dev = dt_dev_get_global();
3248 if(IS_NULL_PTR(self->data)) return;
3249 dt_lib_histogram_t *d = self->data;
3250 dt_dev_pixelpipe_cache_wait_cleanup(&d->scope_wait, "histogram-gui-cleanup-scope");
3251 dt_dev_pixelpipe_cache_wait_cleanup(&d->picker_wait, "histogram-gui-cleanup-picker");
3252 dt_dev_pixelpipe_cache_wait_cleanup(&d->module_wait, "histogram-gui-cleanup-module");
3255 if(d->refresh_idle_source != 0)
3256 {
3257 g_source_remove(d->refresh_idle_source);
3258 d->refresh_idle_source = 0;
3259 }
3260 if(d->pending_hashes) g_array_free(d->pending_hashes, TRUE);
3263
3266 while(dev->color_picker.samples)
3268
3270 self->data = NULL;
3271}
3272
3273// clang-format off
3274// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3275// vim: shiftwidth=2 expandtab tabstop=2 cindent
3276// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3277// 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:1705
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:1872
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
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:924
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:969
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:621
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#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_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1291
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1278
void dt_dev_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:2134
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:1826
@ 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:216
@ ORIENTATION_SWAP_XY
Definition image.h:221
@ ORIENTATION_NONE
Definition image.h:218
const char * tooltip
Definition image.h:310
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:1839
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:1182
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1229
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1251
dt_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:2085
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_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
size_t dt_pixel_cache_entry_get_size(dt_pixel_cache_entry_t *entry)
Peek the size (in bytes) reserved for the host buffer of a cache entry.
void dt_dev_pixelpipe_cache_unref_hash(const uint64_t hash)
Find the entry matching hash, and decrease its ref_count if found.
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:42
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:212
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:188
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
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
dt_dev_request_flags_t request_histogram
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
dt_dev_histogram_collection_params_t histogram_params
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
uint64_t hash
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