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
56#include "bauhaus/bauhaus.h"
57#include "common/atomic.h"
58#include "common/color_picker.h"
60#include "common/darktable.h"
61#include "common/debug.h"
62#include "common/histogram.h"
63#include "common/iop_profile.h"
64#include "common/imagebuf.h"
65#include "common/image_cache.h"
66#include "common/math.h"
67#include "control/conf.h"
68#include "control/control.h"
69#include "control/signal.h"
71#include "develop/develop.h"
73#include "dtgtk/drawingarea.h"
74#include "dtgtk/button.h"
76#include "gui/draw.h"
77#include "gui/gtk.h"
78#include "libs/lib.h"
79#include "libs/lib_api.h"
80#include "libs/colorpicker.h"
81
82#ifdef GDK_WINDOWING_QUARTZ
83#include "osx/osx.h"
84#endif
85
86#define HISTOGRAM_BINS 256
87#define GAMMA 1.f / 2.f
88#define DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE (1.f / 256.f)
89#define DT_LIB_HISTOGRAM_SCOPE_ENABLE_SMOOTHING 1
90#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES 1
91#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES 4
92#define DT_LIB_HISTOGRAM_SCOPE_SMOOTH_FORCE (256 * 0.33)
93#define DT_LIB_HISTOGRAM_SCOPE_RESTRICTED_LABEL_OPERATOR CAIRO_OPERATOR_ADD
94#define DT_LIB_HISTOGRAM_SCOPE_DEFAULT_HEIGHT 250
95#define DT_LIB_HISTOGRAM_SCOPE_MIN_HEIGHT 120
96#define DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT 500
97#define DT_LIB_HISTOGRAM_SCOPE_HANDLE_HEIGHT 8
98#define DT_LIB_HISTOGRAM_SCOPE_HEIGHT_CONF "plugin/darkroom/histogram/scope_height"
99
100DT_MODULE(1)
101
112
113
115{
116 // If any of those params changes, we need to recompute the Cairo buffer.
117 float zoom;
118 int width;
123
133
135 = { N_("RGB"), N_("Lab"), N_("LCh"), N_("HSL"), N_("HSV"), N_("none"), NULL };
137 = { N_("mean"), N_("min"), N_("max"), NULL };
138
139
140typedef struct dt_lib_histogram_t
141{
142 struct dt_lib_module_t *module;
143 GtkWidget *scope_draw; // GtkDrawingArea -- scope, scale, and draggable overlays
144 GtkWidget *scope_resize_handle; // GtkDrawingArea -- vertical resize grip kept outside the rendered scope
145 dt_backbuf_t *backbuf; // reference to the dev backbuf currently in use
146 const char *op; // pipeline stage ("initialscale" | "colorout" | "gamma")
147 dt_lib_histogram_scope_type_t scope; // which scope/display type is active
148 float zoom; // zoom level for the vectorscope
150
152 cairo_surface_t *cst;
153
168
170
172
178
186
191static void _reset_cache(dt_lib_histogram_t *d);
192
193static void _histogram_restart_cache_wait(gpointer user_data)
194{
195 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
196 if(IS_NULL_PTR(self)) return;
198}
199
208static void _histogram_restart_scope_cache_wait(gpointer user_data)
209{
210 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
211 dt_lib_histogram_t *d = !IS_NULL_PTR(self) ? self->data : NULL;
212 if(IS_NULL_PTR(d)) return;
213
216}
217
226
228 const float *pixel, dt_iop_module_t *module)
229{
231 dt_histogram_roi_t histogram_roi;
232
233 if(IS_NULL_PTR(histogram_params.roi))
234 {
235 histogram_roi = (dt_histogram_roi_t){
236 .width = piece->roi_in.width, .height = piece->roi_in.height,
237 .crop_x = 0, .crop_y = 0, .crop_width = 0, .crop_height = 0
238 };
239 histogram_params.roi = &histogram_roi;
240 }
241
243 dt_histogram_helper(&histogram_params, &module->histogram_stats, piece->dsc_in.cst, module->histogram_cst,
244 pixel, &module->histogram, module->histogram_middle_grey,
247 &module->histogram, module->histogram_max);
249
250 if(module->widget) dt_control_queue_redraw_widget(module->widget);
251}
252
254{
255 if(IS_NULL_PTR(dev) || IS_NULL_PTR(op)) return NULL;
256
257 if(!strcmp(op, "initialscale"))
258 return &dev->raw_histogram;
259 else if(!strcmp(op, "colorout"))
260 return &dev->output_histogram;
261 else if(!strcmp(op, "gamma"))
262 return &dev->display_histogram;
263 else
264 return NULL;
265}
266
268{
269 if(IS_NULL_PTR(backbuf)) return;
270
271 /* Global histogram backbuffers keep one structural ref on top of the module-output lifetime.
272 * Clearing that published view therefore means releasing the extra GUI-side keepalive ref here. */
275}
276
277static gboolean _refresh_global_histogram_backbuf_for_hash(dt_develop_t *dev, const char *op,
278 const uint64_t expected_hash)
279{
280 dt_backbuf_t *const backbuf = _get_histogram_backbuf(dev, op);
281 if(IS_NULL_PTR(backbuf) || IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe)) return FALSE;
282
285 if(IS_NULL_PTR(piece))
286 {
288 return FALSE;
289 }
290
291 const dt_dev_pixelpipe_iop_t *const previous_piece
293
294 const dt_iop_roi_t *roi = &piece->roi_out;
295 const dt_iop_buffer_dsc_t *dsc = &piece->dsc_out;
296 uint64_t hash = piece->global_hash;
297
298 if(!strcmp(op, "gamma"))
299 {
300 if(IS_NULL_PTR(previous_piece))
301 {
303 return FALSE;
304 }
305
306 roi = &previous_piece->roi_out;
307 dsc = &previous_piece->dsc_out;
308 hash = previous_piece->global_hash;
309 }
310
311 if(expected_hash != DT_PIXELPIPE_CACHE_HASH_INVALID && hash != expected_hash) return FALSE;
312
313 dt_pixel_cache_entry_t *entry = NULL;
315 || !dt_dev_pixelpipe_cache_peek_gui(dev->preview_pipe, !strcmp(op, "gamma") ? previous_piece : piece,
316 NULL, &entry, NULL, NULL, NULL))
317 {
319 return FALSE;
320 }
321
322 const uint64_t previous_hash = dt_dev_backbuf_get_hash(backbuf);
323 if(previous_hash != hash)
324 {
325 /* The module output already owns its producer ref. Tagging it as a global histogram backbuffer
326 * reserves one additional consumer ref so GUI readers only need `peek()` and read locks later. */
329 }
330
331 dt_dev_set_backbuf(backbuf, roi->width, roi->height, dsc->bpp, hash, DT_PIXELPIPE_CACHE_HASH_INVALID);
332 return TRUE;
333}
334
336 const uint64_t expected_hash)
337{
338 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->preview_pipe) || IS_NULL_PTR(module)) return FALSE;
339
341 if(IS_NULL_PTR(piece) || !(piece->request_histogram & DT_REQUEST_ON)) return FALSE;
342 if((piece->request_histogram & DT_REQUEST_ONLY_IN_GUI) && !dev->gui_attached) return FALSE;
343
344 const dt_dev_pixelpipe_iop_t *const previous_piece
346 if(IS_NULL_PTR(previous_piece) || previous_piece->global_hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
347 if(expected_hash != DT_PIXELPIPE_CACHE_HASH_INVALID && previous_piece->global_hash != expected_hash) return FALSE;
348 if(previous_piece->dsc_out.datatype != TYPE_FLOAT) return FALSE;
349
350 void *input = NULL;
351 dt_pixel_cache_entry_t *input_entry = NULL;
353 dt_lib_histogram_t *const d = !IS_NULL_PTR(histogram_module) ? histogram_module->data : NULL;
354 if(!IS_NULL_PTR(d))
355 dt_dev_pixelpipe_cache_wait_set_owner(&d->module_wait, "histogram-module-refresh", module);
356 if(!dt_dev_pixelpipe_cache_peek_gui(dev->preview_pipe, previous_piece, &input, &input_entry,
357 !IS_NULL_PTR(d) ? &d->module_wait : NULL,
358 _histogram_restart_cache_wait, histogram_module))
359 return FALSE;
360
363
364 const float *histogram_input = input;
365 float *transformed_input = NULL;
366 dt_iop_buffer_dsc_t input_dsc = previous_piece->dsc_out;
367
368 if(input_dsc.cst != piece->dsc_in.cst
370 {
371 const size_t pixels = (size_t)piece->roi_in.width * (size_t)piece->roi_in.height;
372 const size_t bytes = pixels * (size_t)piece->dsc_in.channels * sizeof(float);
373 transformed_input = dt_alloc_align(bytes);
374
375 if(IS_NULL_PTR(transformed_input))
376 {
379 return FALSE;
380 }
381
382 memcpy(transformed_input, input, bytes);
383 dt_ioppr_transform_image_colorspace(module, transformed_input, transformed_input,
384 piece->roi_in.width, piece->roi_in.height,
385 input_dsc.cst, piece->dsc_in.cst, &input_dsc.cst,
387 histogram_input = transformed_input;
388 }
389 else if(input_dsc.cst != piece->dsc_in.cst)
390 {
391 input_dsc.cst = piece->dsc_in.cst;
392 }
393
394 _refresh_module_histogram(dev->preview_pipe, piece, histogram_input, module);
395
398 dt_free_align(transformed_input);
399
400 return TRUE;
401}
402
404{
405 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
406 if(!dev->preview_pipe->gui_observable_source) return;
407 /* Do not grab preview_pipe->busy_mutex from GUI thread: it may be held by
408 * the pipeline worker for long recomputations and would freeze the UI.
409 * We rely on non-blocking cache probes and the cache-wait manager instead. */
410
411 const char *ops[] = { "initialscale", "colorout", "gamma" };
412 uint64_t *pending_hashes[] = {
416 };
417
418 for(size_t i = 0; i < G_N_ELEMENTS(ops); i++)
419 {
422 if(IS_NULL_PTR(piece))
423 {
425 continue;
426 }
427
428 uint64_t hash = piece->global_hash;
429 if(!strcmp(ops[i], "gamma"))
430 {
431 const dt_dev_pixelpipe_iop_t *const previous_piece
433 hash = previous_piece ? previous_piece->global_hash : DT_PIXELPIPE_CACHE_HASH_INVALID;
434 }
435
437 {
439 *pending_hashes[i] = DT_PIXELPIPE_CACHE_HASH_INVALID;
440 continue;
441 }
442
443 // Avoid re-requesting the same cacheline on every history resync.
444 // While the same hash is pending, we wait for DT_SIGNAL_CACHELINE_READY
445 // to complete the refresh instead of feeding CACHE_REQUEST in a loop.
446 if(*pending_hashes[i] == hash) continue;
447
449 *pending_hashes[i] = DT_PIXELPIPE_CACHE_HASH_INVALID;
450 else
451 *pending_hashes[i] = hash;
452 }
453
454 GHashTable *seen_modules = g_hash_table_new(g_direct_hash, g_direct_equal);
455 for(GList *node = g_list_first(dev->preview_pipe->nodes); node; node = g_list_next(node))
456 {
457 dt_dev_pixelpipe_iop_t *piece = node->data;
458 if(IS_NULL_PTR(piece) || !piece->enabled) continue;
459 if(!(piece->request_histogram & DT_REQUEST_ON)) continue;
460 if((piece->request_histogram & DT_REQUEST_ONLY_IN_GUI) && !dev->gui_attached) continue;
461
462 g_hash_table_insert(seen_modules, piece->module, piece->module);
463
464 const dt_dev_pixelpipe_iop_t *const previous_piece
466 if(!IS_NULL_PTR(previous_piece)
467 && !_refresh_preview_module_histogram_for_hash(dev, piece->module, previous_piece->global_hash))
468 {
469 gboolean found = FALSE;
470 for(GList *l = _preview_refresh_state.module_histograms; l; l = g_list_next(l))
471 {
472 dt_histogram_pending_module_refresh_t *pending = l->data;
473 if(IS_NULL_PTR(pending) || pending->module != piece->module) continue;
474 pending->hash = previous_piece->global_hash;
475 found = TRUE;
476 break;
477 }
478
479 if(!found)
480 {
481 dt_histogram_pending_module_refresh_t *pending = g_malloc0(sizeof(*pending));
482 pending->module = piece->module;
483 pending->hash = previous_piece->global_hash;
485 = g_list_prepend(_preview_refresh_state.module_histograms, pending);
486 }
487 }
488 else
489 {
490 for(GList *l = _preview_refresh_state.module_histograms; l;)
491 {
492 GList *next = g_list_next(l);
493 dt_histogram_pending_module_refresh_t *pending = l->data;
494 if(pending && pending->module == piece->module)
495 {
497 = g_list_delete_link(_preview_refresh_state.module_histograms, l);
498 g_free(pending);
499 }
500 l = next;
501 }
502 }
503 }
504
505 // Remove stale pending module refreshes.
506 for(GList *l = _preview_refresh_state.module_histograms; l;)
507 {
508 GList *next = g_list_next(l);
509 dt_histogram_pending_module_refresh_t *pending = l->data;
510 if(IS_NULL_PTR(pending) || !g_hash_table_contains(seen_modules, pending->module))
511 {
513 = g_list_delete_link(_preview_refresh_state.module_histograms, l);
514 g_free(pending);
515 }
516 l = next;
517 }
518 g_hash_table_destroy(seen_modules);
519}
520
521static void _preview_history_resync_callback(gpointer instance, gpointer user_data)
522{
523 (void)instance;
524 (void)user_data;
526}
527
528static void _preview_cacheline_ready_callback(gpointer instance, const guint64 hash,
529 const guint64 producer_node_key, gpointer user_data)
530{
531 (void)instance;
532 (void)producer_node_key; // scope/module/picker buffer fetches match by producer node through
533 // the cache-wait manager; this backbuf-hash refresh trigger is exact-hash.
534 (void)user_data;
535
536 dt_develop_t *const dev = darktable.develop;
537 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
539 {
540 _refresh_global_histogram_backbuf_for_hash(dev, "initialscale", hash);
542 }
543
545 {
546 _refresh_global_histogram_backbuf_for_hash(dev, "colorout", hash);
548 }
549
551 {
554 }
555
556 for(GList *l = _preview_refresh_state.module_histograms; l;)
557 {
558 GList *next = g_list_next(l);
559 dt_histogram_pending_module_refresh_t *pending = l->data;
560 if(pending->hash == hash)
561 {
562 _refresh_preview_module_histogram_for_hash(dev, pending->module, pending->hash);
564 g_free(pending);
565 }
566 l = next;
567 }
568}
569
570const char *name(struct dt_lib_module_t *self)
571{
572 return _("scopes");
573}
574
575const char **views(dt_lib_module_t *self)
576{
577 static const char *v[] = {"darkroom", "studio_capture", NULL};
578 return v;
579}
580
582{
584}
585
587{
588 return 1;
589}
590
592{
593 return 1000;
594}
595
596static void _update_picker_output(dt_lib_module_t *self);
598static void _update_everything(dt_lib_module_t *self);
599
600
602{
603 switch(value)
604 {
605 case 0:
606 {
607 d->op = "initialscale";
608 break;
609 }
610 case 1:
611 {
612 d->op = "colorout";
613 break;
614 }
615 case 2:
616 {
617 d->op = "gamma";
618 }
619 }
620}
621
622
624{
625 if(!strcmp(d->op, "initialscale") || !strcmp(d->op, "demosaic")) return 0;
626 if(!strcmp(d->op, "colorout")) return 1;
627 if(!strcmp(d->op, "gamma")) return 2;
628 return 2;
629}
630
631
632#ifdef _OPENMP
633#pragma omp declare simd \
634 aligned(rgb_in, xyz_out:64) \
635 uniform(rgb_in, xyz_out)
636#endif
638{
639 if(_backbuf_op_to_int(d) > 0)
640 {
641 // We are in display RGB
643 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
644 profile->lutsize, profile->nonlinearlut);
645 }
646 else
647 {
648 // We are in sensor RGB
650 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
651 profile->lutsize, profile->nonlinearlut);
652 }
653}
654
655
656#ifdef _OPENMP
657#pragma omp declare simd \
658 aligned(rgb_in, rgb_out:64) \
659 uniform(rgb_in, rgb_out)
660#endif
662{
663 if(_backbuf_op_to_int(d) > 0)
664 {
665 // We are in display RGB
667 rgb_out[c] = rgb_in[c];
668 }
669 else
670 {
671 // We are in the sensor/input RGB profile. Convert directly between the
672 // cached stage profile and the display profile so the swatch patch uses
673 // the same authoritative ICC path as the rest of the GUI.
676 dt_ioppr_transform_image_colorspace_rgb(rgb_in, rgb_out, 1, 1, profile_in, profile_out,
677 "[histogram] sample swatch");
678 }
679}
680
682{
683 d->cache.view = DT_LIB_HISTOGRAM_SCOPE_N;
684 d->cache.width = -1;
685 d->cache.height = -1;
686 d->cache.hash = (uint64_t)-1;
687 d->cache.zoom = -1.;
688}
689
691{
692 if(IS_NULL_PTR(d) || !d->pending_hashes) return;
693 g_array_set_size(d->pending_hashes, 0);
694}
695
696static gboolean _has_pending_hash(const dt_lib_histogram_t *d, const uint64_t hash)
697{
698 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
699
700 for(guint i = 0; i < d->pending_hashes->len; i++)
701 if(g_array_index(d->pending_hashes, uint64_t, i) == hash) return TRUE;
702
703 return FALSE;
704}
705
707{
708 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID || _has_pending_hash(d, hash)) return;
709 g_array_append_val(d->pending_hashes, hash);
710}
711
713{
714 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
715
716 for(guint i = 0; i < d->pending_hashes->len; i++)
717 {
718 if(g_array_index(d->pending_hashes, uint64_t, i) == hash)
719 {
720 g_array_remove_index(d->pending_hashes, i);
721 return TRUE;
722 }
723 }
724
725 return FALSE;
726}
727
728static uint64_t _get_live_histogram_hash(const char *op)
729{
730 dt_develop_t *const dev = darktable.develop;
731 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
733 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read the live module
734 * hashes without the mutex and rely on the cache peek manager to handle
735 * synchronization and retries for missing cachelines. */
736 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
738
741
742 if(!strcmp(op, "gamma"))
743 piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
744
745 const uint64_t hash = piece ? piece->global_hash : DT_PIXELPIPE_CACHE_HASH_INVALID;
746 return hash;
747}
748
749static gboolean _histogram_refresh_idle(gpointer user_data)
750{
751 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
752 dt_lib_histogram_t *d = self ? self->data : NULL;
753 if(IS_NULL_PTR(d)) return G_SOURCE_REMOVE;
754
755 d->refresh_idle_source = 0;
756 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
757 _update_everything(self);
758 return G_SOURCE_REMOVE;
759}
760
762{
763 dt_lib_histogram_t *d = self ? self->data : NULL;
764 if(IS_NULL_PTR(d) || d->refresh_idle_source != 0) return;
765 d->refresh_idle_source = g_idle_add(_histogram_refresh_idle, self);
766}
767
769{
771 if(IS_NULL_PTR(d)) return;
772
775
776 for(GSList *samples = darktable.develop->color_picker.samples; samples; samples = g_slist_next(samples))
777 {
778 dt_colorpicker_sample_t *sample = samples->data;
779 if(sample->locked) continue;
781 }
782
785
786 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
787 _update_everything(self);
788}
789
790
791static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op);
792
807{
808 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->backbuf)) return FALSE;
809
810 return !IS_NULL_PTR(_get_backbuf_source_piece(d->backbuf, d->op));
811}
812
814{
815 gtk_widget_queue_draw(d->scope_draw);
816}
817
818uint32_t _find_max_histogram(const uint32_t *const restrict bins, const size_t binning_size)
819{
820 uint32_t max_hist = 0;
821 __OMP_PARALLEL_FOR_SIMD__(aligned(bins: 64) reduction(max: max_hist) )
822 for(size_t k = 0; k < binning_size; k++) if(bins[k] > max_hist) max_hist = bins[k];
823
824 return max_hist;
825}
826
827static inline void _sample_raw_box_to_image_norm(const dt_colorpicker_sample_t *const sample, float box[4])
828{
829 memcpy(box, sample->box, sizeof(float) * 4);
831}
832
833static inline void _sample_raw_point_to_image_norm(const dt_colorpicker_sample_t *const sample, float point[2])
834{
835 point[0] = sample->point[0];
836 point[1] = sample->point[1];
838}
839
840
841static inline void _bin_pixels_histogram_in_roi(const float *const restrict image, uint32_t *const restrict bins,
842 const size_t min_x, const size_t max_x,
843 const size_t min_y, const size_t max_y,
844 const size_t width)
845{
846 //fprintf(stdout, "computing histogram from x = [%lu;%lu], y = [%lu;%lu]\n", min_x, max_x, min_y, max_y);
847 // Process
848#ifdef _OPENMP
849#ifndef _WIN32
850__OMP_PARALLEL_FOR__(reduction(+: bins[0: HISTOGRAM_BINS * 4]) collapse(3))
851#else
852__OMP_PARALLEL_FOR__(shared(bins) collapse(3))
853#endif
854#endif
855 for(size_t i = min_y; i < max_y; i++)
856 for(size_t j = min_x; j < max_x; j++)
857 for(size_t c = 0; c < 3; c++)
858 {
859 const float value = image[(i * width + j) * 4 + c];
860 const size_t index = (size_t)CLAMP(roundf(value * (HISTOGRAM_BINS - 1)), 0, HISTOGRAM_BINS - 1);
861 bins[index * 4 + c]++;
862 }
863}
864
865
866static inline void _bin_pickers_histogram(const float *const restrict image,
867 const size_t width, const size_t height,
868 uint32_t *bins,
870{
871 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
872 {
873 float image_box[4] = { 0.0f };
874 _sample_raw_box_to_image_norm(sample, image_box);
875 const size_t box[4] = {
876 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
877 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
878 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
879 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
880 };
881 _bin_pixels_histogram_in_roi(image, bins, box[0], box[2], box[1], box[3], width);
882 }
883 else
884 {
885 float image_point[2] = { 0.0f };
886 _sample_raw_point_to_image_norm(sample, image_point);
887 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
888 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
889 _bin_pixels_histogram_in_roi(image, bins, x, x + 1, y, y + 1, width);
890 }
891}
892
894{
895 if(IS_NULL_PTR(d)) return FALSE;
896 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
897 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
898 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
899 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->picker_button));
900 return restrict_active && picker_active;
901}
902
903static void _process_histogram(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
904 const int height, dt_lib_histogram_t *d)
905{
906 // Histogram backbuffers already own their keepalive ref in the pipeline state. Drawing only borrows them.
907 struct dt_pixel_cache_entry_t *entry = NULL;
908 void *data = NULL;
909 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
910 if(IS_NULL_PTR(piece)) return;
911 if(!IS_NULL_PTR(d))
912 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
914 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
916 !IS_NULL_PTR(d) ? d->module : NULL))
917 {
918 return;
919 }
920
922
923 uint32_t *bins = calloc(4 * HISTOGRAM_BINS, sizeof(uint32_t));
924 if(IS_NULL_PTR(bins))
925 {
927 return;
928 }
929
930 const gboolean restrict_mode = _is_restricted(d);
931 if(restrict_mode)
932 {
933 // Bin only areas within color pickers
934 GSList *samples = darktable.develop->color_picker.samples;
935 while(samples)
936 {
937 dt_colorpicker_sample_t *sample = samples->data;
938 _bin_pickers_histogram(data, backbuf->width, backbuf->height,
939 bins, sample);
940 samples = g_slist_next(samples);
941 }
942
944 _bin_pickers_histogram(data, backbuf->width, backbuf->height,
946 }
947 else
948 {
949 _bin_pixels_histogram_in_roi(data, bins, 0, backbuf->width, 0, backbuf->height, width);
950 }
951
953
954 uint32_t overall_histogram_max = _find_max_histogram(bins, 4 * HISTOGRAM_BINS);
955
956 // Draw thingy
957 if(overall_histogram_max > 0)
958 {
959 // Paint background
960 cairo_rectangle(cr, 0, 0, width, height);
962 cairo_fill(cr);
963
965 dt_draw_grid(cr, 4, 0, 0, width, height);
966
967 cairo_save(cr);
968 cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR);
969 cairo_translate(cr, 0, height);
970 cairo_scale(cr, width / 255.0, - (double)height / (double)(1. + log(overall_histogram_max)));
971 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
972
973 for(int k = 0; k < 3; k++)
974 {
976 dt_draw_histogram_8(cr, bins, 4, k, FALSE);
977 }
978
979 cairo_pop_group_to_source(cr);
980 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
981 cairo_paint_with_alpha(cr, 0.5);
982 cairo_restore(cr);
983 }
984
985 dt_free(bins);
986}
987
988
989static inline void _bin_pixels_waveform_in_roi(const float *const restrict image, uint32_t *const restrict bins,
990 const size_t min_x, const size_t max_x,
991 const size_t min_y, const size_t max_y,
992 const size_t source_width, const size_t source_height,
993 const size_t tone_bins, const size_t raster_extent,
994 const gboolean vertical)
995{
996 if(vertical)
997 {
998 /* In vertical waveform/parade, pixel value is drawn along the widget width and source image
999 * position along the widget height. We therefore loop on final raster rows and collect the
1000 * source rows that map there, so both axes have the same detail as the drawn surface. */
1001 __OMP_PARALLEL_FOR__(shared(bins))
1002 for(size_t raster_y = 0; raster_y < raster_extent; raster_y++)
1003 {
1004 const double source_y0d = (double)raster_y * (double)source_height / (double)raster_extent;
1005 const double source_y1d = (double)(raster_y + 1) * (double)source_height / (double)raster_extent;
1006 const size_t source_y0 = MAX(min_y, (size_t)floor(source_y0d));
1007 const size_t source_y1 = MIN(max_y, (size_t)ceil(source_y1d));
1008 if(source_y0 >= source_y1) continue;
1009
1010 for(size_t i = source_y0; i < source_y1; i++)
1011 {
1012 const double overlap = MIN((double)(i + 1), source_y1d) - MAX((double)i, source_y0d);
1013 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1014 __OMP_PARALLEL_FOR__(shared(bins) collapse(2))
1015 for(size_t j = min_x; j < max_x; j++)
1016 for(size_t c = 0; c < 3; c++)
1017 {
1018 const float value = image[(i * source_width + j) * 4 + c];
1019 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1020 const size_t tone0 = (size_t)floorf(tone_position);
1021 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1022 const float tone_mix = tone_position - (float)tone0;
1023 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1024 const uint32_t weight0 = weight - weight1;
1025 bins[(raster_y * tone_bins + tone0) * 4 + c] += weight0;
1026 bins[(raster_y * tone_bins + tone1) * 4 + c] += weight1;
1027 }
1028 }
1029 }
1030 }
1031 else
1032 {
1033 /* In horizontal waveform/parade, pixel value is drawn along the widget height and source image
1034 * position along the widget width. Each final raster column owns disjoint bins, avoiding races. */
1035 __OMP_PARALLEL_FOR__(shared(bins))
1036 for(size_t raster_x = 0; raster_x < raster_extent; raster_x++)
1037 {
1038 const double source_x0d = (double)raster_x * (double)source_width / (double)raster_extent;
1039 const double source_x1d = (double)(raster_x + 1) * (double)source_width / (double)raster_extent;
1040 const size_t source_x0 = MAX(min_x, (size_t)floor(source_x0d));
1041 const size_t source_x1 = MIN(max_x, (size_t)ceil(source_x1d));
1042 if(source_x0 >= source_x1) continue;
1043
1044 for(size_t j = source_x0; j < source_x1; j++)
1045 {
1046 const double overlap = MIN((double)(j + 1), source_x1d) - MAX((double)j, source_x0d);
1047 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1048 __OMP_PARALLEL_FOR__(shared(bins) collapse(2))
1049 for(size_t i = min_y; i < max_y; i++)
1050 for(size_t c = 0; c < 3; c++)
1051 {
1052 const float value = image[(i * source_width + j) * 4 + c];
1053 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1054 const size_t tone0 = (size_t)floorf(tone_position);
1055 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1056 const float tone_mix = tone_position - (float)tone0;
1057 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1058 const uint32_t weight0 = weight - weight1;
1059 bins[((tone_bins - 1 - tone0) * raster_extent + raster_x) * 4 + c] += weight0;
1060 bins[((tone_bins - 1 - tone1) * raster_extent + raster_x) * 4 + c] += weight1;
1061 }
1062 }
1063 }
1064 }
1065}
1066
1067static inline void _bin_pickers_waveforms(const float *const restrict image, uint32_t *const restrict bins,
1068 const size_t width, const size_t height,
1069 const size_t tone_bins, const size_t raster_extent,
1070 const gboolean vertical, dt_colorpicker_sample_t *sample)
1071{
1072 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1073 {
1074 float image_box[4] = { 0.0f };
1075 _sample_raw_box_to_image_norm(sample, image_box);
1076 const size_t box[4] = {
1077 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1078 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1079 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1080 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1081 };
1082 _bin_pixels_waveform_in_roi(image, bins, box[0], box[2], box[1], box[3],
1083 width, height, tone_bins, raster_extent, vertical);
1084 }
1085 else
1086 {
1087 float image_point[2] = { 0.0f };
1088 _sample_raw_point_to_image_norm(sample, image_point);
1089 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1090 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1091 _bin_pixels_waveform_in_roi(image, bins, x, x + 1, y, y + 1,
1092 width, height, tone_bins, raster_extent, vertical);
1093 }
1094}
1095
1096
1097static inline void _bin_pixels_waveform(const float *const restrict image, uint32_t *const restrict bins,
1098 const size_t width, const size_t height, const size_t binning_size,
1099 const size_t tone_bins, const size_t raster_extent,
1100 const gboolean vertical, const gboolean restricted)
1101{
1102 // Init
1103 __OMP_FOR_SIMD__(aligned(bins: 64) )
1104 for(size_t k = 0; k < binning_size; k++) bins[k] = 0;
1105
1106 if(restricted)
1107 {
1108 // Bin only areas within color pickers
1109 GSList *samples = darktable.develop->color_picker.samples;
1110 while(samples)
1111 {
1112 dt_colorpicker_sample_t *sample = samples->data;
1113 _bin_pickers_waveforms(image, bins, width, height, tone_bins, raster_extent, vertical, sample);
1114 samples = g_slist_next(samples);
1115 }
1116
1118 _bin_pickers_waveforms(image, bins, width, height, tone_bins, raster_extent, vertical,
1120 }
1121 else
1122 {
1123 // Bin the whole image
1124 _bin_pixels_waveform_in_roi(image, bins, 0, width, 0, height,
1125 width, height, tone_bins, raster_extent, vertical);
1126 }
1127}
1128
1129static void _create_waveform_image(const uint32_t *const restrict bins, uint8_t *const restrict image,
1130 const uint32_t max_hist,
1131 const size_t width, const size_t height)
1132{
1133 __OMP_FOR_SIMD__(aligned(image, bins: 64) )
1134 for(size_t k = 0; k < height * width * 4; k += 4)
1135 {
1136 image[k + 3] = 255; // alpha
1137
1138 // We apply a slight "gamma" boost for legibility
1139 image[k + 2] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 0] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1140 image[k + 1] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 1] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1141 image[k + 0] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 2] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1142 }
1143}
1144
1145static void _paint_waveform(cairo_t *cr, uint8_t *const restrict image, const int width, const int height,
1146 const size_t img_width, const size_t img_height, const size_t tone_bins,
1147 const gboolean vertical)
1148{
1149 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1150 cairo_surface_t *background = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_ARGB32, img_width, img_height, stride);
1151 const double scale_w = (vertical) ? (double)width / (double)tone_bins
1152 : (double)width / (double)img_width;
1153 const double scale_h = (vertical) ? (double)height / (double)img_height
1154 : (double)height / (double)tone_bins;
1155 cairo_scale(cr, scale_w, scale_h);
1156 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
1157 cairo_set_source_surface(cr, background, 0., 0.);
1158 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1159 cairo_paint(cr);
1160 cairo_surface_destroy(background);
1161}
1162
1163
1164static void _process_waveform(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1165 const int height, const gboolean vertical, const gboolean parade,
1167{
1168 struct dt_pixel_cache_entry_t *entry = NULL;
1169 void *data = NULL;
1170 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1171 if(IS_NULL_PTR(piece)) return;
1172 if(!IS_NULL_PTR(d))
1173 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1175 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
1177 !IS_NULL_PTR(d) ? d->module : NULL))
1178 {
1179 return;
1180 }
1181
1183
1184 /* The value axis must match the final drawing axis: widget height for horizontal scopes and
1185 * widget width for vertical scopes. Keeping it fixed forces Cairo to stretch sparse
1186 * value rows/columns, which shows as hatching when the scope is resized. */
1187 const size_t tone_bins = MAX(2, (vertical) ? width : height);
1188 /* The source-position axis must also match the final raster size. Otherwise Cairo stretches the
1189 * preview-pipe dimensions to the scope allocation, which can leave visible regular stripes. */
1190 const size_t raster_extent = MAX(2, (vertical)
1191 ? (parade ? (height + 2) / 3 : height)
1192 : (parade ? (width + 2) / 3 : width));
1193 const size_t binning_size = 4 * tone_bins * raster_extent;
1194 const size_t img_width = (parade) ? width : ((vertical) ? tone_bins : raster_extent);
1195 const size_t img_height = (parade) ? height : ((vertical) ? raster_extent : tone_bins);
1196 const size_t image_size = 4 * img_width * img_height;
1197 const size_t source_axis = (vertical) ? backbuf->height : backbuf->width;
1198 float source_min = INFINITY;
1199 float source_max = -INFINITY;
1200 size_t source_clipped = 0;
1201 size_t source_nan = 0;
1202 const size_t source_step = MAX((size_t)1, (backbuf->width * backbuf->height) / 4096);
1203 for(size_t pixel = 0; pixel < backbuf->width * backbuf->height; pixel += source_step)
1204 for(size_t c = 0; c < 3; c++)
1205 {
1206 const float value = ((const float *)data)[pixel * 4 + c];
1207 if(isnan(value))
1208 source_nan++;
1209 else
1210 {
1211 source_min = fminf(source_min, value);
1212 source_max = fmaxf(source_max, value);
1213 source_clipped += (value < 0.f || value > 1.f);
1214 }
1215 }
1216 uint32_t *smooth_bins = NULL;
1217
1220 "[histogram/scope] waveform setup op=%s parade=%d vertical=%d widget=%dx%d backbuf=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT " "
1221 "tone_bins=%" G_GSIZE_FORMAT " raster_extent=%" G_GSIZE_FORMAT " source_axis=%" G_GSIZE_FORMAT
1222 " source_per_raster=%.4f binning_size=%" G_GSIZE_FORMAT " "
1223 "source_value=%0.6f..%0.6f clipped=%" G_GSIZE_FORMAT " nan=%" G_GSIZE_FORMAT " sample_step=%" G_GSIZE_FORMAT "\n",
1224 op, parade, vertical, width, height, backbuf->width, backbuf->height,
1225 tone_bins, raster_extent, source_axis, (double)source_axis / (double)raster_extent,
1226 binning_size, source_min, source_max, source_clipped, source_nan, source_step);
1227 uint32_t *const restrict bins = dt_pixelpipe_cache_alloc_align_cache(
1228 binning_size * sizeof(uint32_t),
1229 0);
1230 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1231 image_size * sizeof(uint8_t),
1232 0);
1233 if(IS_NULL_PTR(image) || IS_NULL_PTR(bins))
1234 {
1237 "[histogram/scope] waveform allocation failed bins=%p image=%p binning_size=%" G_GSIZE_FORMAT
1238 " image_size=%" G_GSIZE_FORMAT "\n",
1239 (void *)bins, (void *)image, binning_size, image_size);
1240 goto error;
1241 }
1242
1243 // 1. Pixel binning along columns/rows, aka compute a column/row-wise histogram
1244 _bin_pixels_waveform(data, bins, backbuf->width, backbuf->height, binning_size,
1245 tone_bins, raster_extent, vertical, _is_restricted(d));
1246
1247 const uint32_t *render_bins = bins;
1248 int smoothing_passes = 0;
1249#if DT_LIB_HISTOGRAM_SCOPE_ENABLE_SMOOTHING \
1250 && (DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES > 0 \
1251 || DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES > 0)
1252 smooth_bins = dt_pixelpipe_cache_alloc_align_cache(binning_size * sizeof(uint32_t), 0);
1253 const uint32_t smoothing_force = CLAMP(DT_LIB_HISTOGRAM_SCOPE_SMOOTH_FORCE, 0, 256);
1254 if(!IS_NULL_PTR(smooth_bins) && smoothing_force > 0)
1255 {
1256 /* The waveform/parade is a spatially-indexed histogram. Even after fractional resampling,
1257 * isolated source-column content can create one-pixel density modulation that reads as stries.
1258 * The passes below are explicit at call site because the source/destination ownership matters:
1259 * bins and smooth_bins alternate roles, and render_bins always records the last valid raster. */
1260 const uint32_t original_force = 256u - smoothing_force;
1261
1262 for(int pass = 0; pass < DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES; pass++)
1263 {
1264 const uint32_t *const source_bins = render_bins;
1265 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1266
1267 if(vertical)
1268 {
1269 for(size_t axis = 0; axis < raster_extent; axis++)
1270 {
1271 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1272 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1273 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1274 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1275 for(size_t tone = 0; tone < tone_bins; tone++)
1276 for(size_t c = 0; c < 4; c++)
1277 {
1278 const size_t index = (axis * tone_bins + tone) * 4 + c;
1279 const uint64_t smoothed = source_bins[(axis_m2 * tone_bins + tone) * 4 + c]
1280 + 4u * source_bins[(axis_m1 * tone_bins + tone) * 4 + c]
1281 + 6u * source_bins[index]
1282 + 4u * source_bins[(axis_p1 * tone_bins + tone) * 4 + c]
1283 + source_bins[(axis_p2 * tone_bins + tone) * 4 + c];
1284 const uint64_t blended = original_force * source_bins[index]
1285 + smoothing_force * ((smoothed + 8u) / 16u);
1286 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1287 }
1288 }
1289 }
1290 else
1291 {
1292 for(size_t tone = 0; tone < tone_bins; tone++)
1293 for(size_t axis = 0; axis < raster_extent; axis++)
1294 {
1295 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1296 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1297 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1298 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1299 for(size_t c = 0; c < 4; c++)
1300 {
1301 const size_t index = (tone * raster_extent + axis) * 4 + c;
1302 const uint64_t smoothed = source_bins[(tone * raster_extent + axis_m2) * 4 + c]
1303 + 4u * source_bins[(tone * raster_extent + axis_m1) * 4 + c]
1304 + 6u * source_bins[index]
1305 + 4u * source_bins[(tone * raster_extent + axis_p1) * 4 + c]
1306 + source_bins[(tone * raster_extent + axis_p2) * 4 + c];
1307 const uint64_t blended = original_force * source_bins[index]
1308 + smoothing_force * ((smoothed + 8u) / 16u);
1309 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1310 }
1311 }
1312 }
1313
1314 render_bins = target_bins;
1315 smoothing_passes++;
1316 }
1317
1318 for(int pass = 0; pass < DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES; pass++)
1319 {
1320 const uint32_t *const source_bins = render_bins;
1321 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1322
1323 if(vertical)
1324 {
1325 for(size_t axis = 0; axis < raster_extent; axis++)
1326 for(size_t tone = 0; tone < tone_bins; tone++)
1327 {
1328 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1329 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1330 for(size_t c = 0; c < 4; c++)
1331 {
1332 const size_t index = (axis * tone_bins + tone) * 4 + c;
1333 const uint64_t smoothed = source_bins[(axis * tone_bins + tone_m1) * 4 + c]
1334 + 2u * source_bins[index]
1335 + source_bins[(axis * tone_bins + tone_p1) * 4 + c];
1336 const uint64_t blended = original_force * source_bins[index]
1337 + smoothing_force * ((smoothed + 2u) / 4u);
1338 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1339 }
1340 }
1341 }
1342 else
1343 {
1344 for(size_t tone = 0; tone < tone_bins; tone++)
1345 {
1346 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1347 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1348 for(size_t axis = 0; axis < raster_extent; axis++)
1349 for(size_t c = 0; c < 4; c++)
1350 {
1351 const size_t index = (tone * raster_extent + axis) * 4 + c;
1352 const uint64_t smoothed = source_bins[(tone_m1 * raster_extent + axis) * 4 + c]
1353 + 2u * source_bins[index]
1354 + source_bins[(tone_p1 * raster_extent + axis) * 4 + c];
1355 const uint64_t blended = original_force * source_bins[index]
1356 + smoothing_force * ((smoothed + 2u) / 4u);
1357 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1358 }
1359 }
1360 }
1361
1362 render_bins = target_bins;
1363 smoothing_passes++;
1364 }
1365 }
1366#endif
1367
1368 // 2. Paint image.
1369 // In a 1D histogram, pixel frequencies are shown as height (y axis) for each RGB quantum (x axis).
1370 // Here, we do a sort of 2D histogram : pixel frequencies are shown as opacity ("z" axis),
1371 // for each image column (x axis), for each RGB quantum (y axis)
1372 const uint32_t overall_max_hist = _find_max_histogram(render_bins, binning_size);
1373 size_t empty_axis = 0;
1374 uint64_t min_axis = UINT64_MAX;
1375 uint64_t max_axis = 0;
1376 uint32_t min_axis_peak = UINT32_MAX;
1377 uint32_t max_axis_peak = 0;
1378 for(size_t axis = 0; axis < raster_extent; axis++)
1379 {
1380 uint64_t axis_total = 0;
1381 uint32_t axis_peak = 0;
1382 for(size_t tone = 0; tone < tone_bins; tone++)
1383 {
1384 const size_t pixel = (vertical) ? axis * tone_bins + tone : tone * raster_extent + axis;
1385 for(size_t c = 0; c < 3; c++)
1386 {
1387 const uint32_t value = render_bins[pixel * 4 + c];
1388 axis_total += value;
1389 axis_peak = MAX(axis_peak, value);
1390 }
1391 }
1392
1393 if(axis_total == 0) empty_axis++;
1394 min_axis = MIN(min_axis, axis_total);
1395 max_axis = MAX(max_axis, axis_total);
1396 min_axis_peak = MIN(min_axis_peak, axis_peak);
1397 max_axis_peak = MAX(max_axis_peak, axis_peak);
1398 }
1399 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1401 "[histogram/scope] waveform raster op=%s parade=%d vertical=%d image=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT
1402 " stride=%" G_GSIZE_FORMAT " overall_max=%u empty_axis=%" G_GSIZE_FORMAT " axis_total=%" PRIu64 "..%" PRIu64
1403 " axis_peak=%u..%u smooth=%d scale=%0.4fx%0.4f\n",
1404 op, parade, vertical, img_width, img_height, stride, overall_max_hist,
1405 empty_axis, min_axis, max_axis, min_axis_peak, max_axis_peak, smoothing_passes,
1406 (double)width / (double)img_width, (double)height / (double)img_height);
1407
1408 // 3. Send everything to GUI buffer.
1409 if(overall_max_hist > 0)
1410 {
1411 if(parade)
1412 {
1413 /* Build the parade directly in the final widget raster. This keeps channel thirds on integer
1414 * device pixels and removes all Cairo source-offset/scaling interactions from the diagnostic
1415 * path: if stries remain, they are in the histogram bins, not in channel painting. */
1416 for(size_t k = 0; k < image_size; k++) image[k] = 0;
1417
1418 for(size_t c = 0; c < 3; c++)
1419 {
1420 const size_t x0 = vertical ? 0 : c * width / 3;
1421 const size_t x1 = vertical ? width : (c + 1) * width / 3;
1422 const size_t y0 = vertical ? c * height / 3 : 0;
1423 const size_t y1 = vertical ? (c + 1) * height / 3 : height;
1424 const size_t channel_width = x1 - x0;
1425 const size_t channel_height = y1 - y0;
1426 if(channel_width == 0 || channel_height == 0) continue;
1427
1428 for(size_t y = y0; y < y1; y++)
1429 for(size_t x = x0; x < x1; x++)
1430 {
1431 const size_t axis = vertical
1432 ? (y - y0) * raster_extent / channel_height
1433 : (x - x0) * raster_extent / channel_width;
1434 const size_t tone = vertical ? x : y;
1435 const size_t bin_index = vertical ? (axis * tone_bins + tone) * 4 + c
1436 : (tone * raster_extent + axis) * 4 + c;
1437 const uint8_t value = (uint8_t)CLAMP(roundf(powf((float)render_bins[bin_index] / (float)overall_max_hist, GAMMA) * 255.f), 0, 255);
1438 const size_t image_index = (y * img_width + x) * 4;
1439 image[image_index + 2 - c] = value;
1440 image[image_index + 3] = 255;
1441 }
1442 }
1443 }
1444 else
1445 _create_waveform_image(render_bins, image, overall_max_hist, img_width, img_height);
1446
1447 cairo_save(cr);
1448
1449 // Paint background - Color not exposed to user theme because this is tricky
1450 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1451 cairo_paint(cr);
1452
1466 cairo_set_source_rgb(cr, 0.21, 0.21, 0.21);
1467 const gboolean raw_stage = (!strcmp(op, "initialscale") || !strcmp(op, "demosaic"));
1468 if(raw_stage)
1469 {
1470 /* The regular 4x4 grid has a tonal guide at 0.75, which is not an integer raw bit stop.
1471 * In raw mode, non-parade scopes keep only the global spatial guides here; the tone guides
1472 * are drawn below from powers of two. Parade intentionally has no spatial guides. */
1473 for(int k = 1; !parade && k < 4; k++)
1474 {
1475 if(vertical)
1476 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1477 else
1478 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1479 cairo_stroke(cr);
1480 }
1481 }
1482 else if(parade)
1483 {
1484 /* Parade already splits the spatial axis into RGB strips. Keep only the regular tone-axis
1485 * guides here so the grid does not add misleading source-position lines inside each strip. */
1486 for(int k = 1; k < 4; k++)
1487 {
1488 if(vertical)
1489 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1490 else
1491 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1492 cairo_stroke(cr);
1493 }
1494 }
1495 else
1496 dt_draw_grid(cr, 4, 0, 0, width, height);
1497
1498 if(raw_stage)
1499 {
1500 /* Raw data is linear, so integer bit stops are powers of two in normalized tone space.
1501 * Drawing those stops on the tone axis makes exposure clipping and low-bit detail readable
1502 * without depending on the current scope height. */
1503 for(int stop = 0; stop <= 8; stop++)
1504 {
1505 const double value = 1.0 / (double)(1u << stop);
1506 if(value < (double)DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE) break;
1507
1508 if(vertical)
1509 {
1510 const double x = value * (double)width;
1511 dt_draw_line(cr, x, 0, x, height);
1512 }
1513 else
1514 {
1515 const double y = (1.0 - value) * (double)height;
1516 dt_draw_line(cr, 0, y, width, y);
1517 }
1518 cairo_stroke(cr);
1519 }
1520 }
1521
1522 _paint_waveform(cr, image, width, height, img_width, img_height, tone_bins, vertical);
1523
1524 cairo_restore(cr);
1525 }
1526
1527error:;
1528 dt_pixelpipe_cache_free_align(smooth_bins);
1532}
1533
1534static float _Luv_to_vectorscope_coord_zoom(const float value, const float zoom)
1535{
1536 // Convert u, v coordinates of Luv vectors into x, y coordinates
1537 // into the space of the vectorscope square buffer
1538 return (value + zoom) * (HISTOGRAM_BINS - 1) / (2.f * zoom);
1539}
1540
1541static float _vectorscope_coord_zoom_to_Luv(const float value, const float zoom)
1542{
1543 // Inverse of the above
1544 return value * (2.f * zoom) / (HISTOGRAM_BINS - 1) - zoom;
1545}
1546
1547static void _bin_pixels_vectorscope_in_roi(const float *const restrict image, uint32_t *const restrict vectorscope,
1548 const size_t min_x, const size_t max_x, const size_t min_y,
1549 const size_t max_y, const size_t width, const float zoom,
1551{
1552#ifdef _OPENMP
1553#ifndef _WIN32
1554#pragma omp parallel for default(firstprivate) \
1555 reduction(+: vectorscope[0: HISTOGRAM_BINS * HISTOGRAM_BINS])
1556#else
1557#pragma omp parallel for default(firstprivate) \
1558 shared(vectorscope)
1559#endif
1560#endif
1561 for(size_t i = min_y; i < max_y; i++)
1562 for(size_t j = min_x; j < max_x; j++)
1563 {
1564 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1565 dt_aligned_pixel_t xyY = { 0.f };
1566 dt_aligned_pixel_t Luv = { 0.f };
1567 _scope_pixel_to_xyz(image + (i * width + j) * 4, XYZ_D50, d);
1570
1571 // Luv is sampled between 0 and 100.0f, u and v between +/- 220.f
1572 const size_t U_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[1], zoom)), 0, HISTOGRAM_BINS - 1);
1573 const size_t V_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[2], zoom)), 0, HISTOGRAM_BINS - 1);
1574
1575 // We put V = 0 at the bottom of the image.
1576 vectorscope[(HISTOGRAM_BINS - 1 - V_index) * HISTOGRAM_BINS + U_index]++;
1577 }
1578}
1579
1580static inline void _bin_pickers_vectorscope(const float *const restrict image,
1581 uint32_t *const restrict vectorscope, const size_t width,
1582 const size_t height, const float zoom, dt_lib_histogram_t *d,
1583 const dt_colorpicker_sample_t *const sample)
1584{
1585 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1586 {
1587 float image_box[4] = { 0.0f };
1588 _sample_raw_box_to_image_norm(sample, image_box);
1589 const size_t box[4] = {
1590 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1591 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1592 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1593 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1594 };
1595 _bin_pixels_vectorscope_in_roi(image, vectorscope, box[0], box[2], box[1], box[3], width, zoom, d);
1596 }
1597 else
1598 {
1599 float image_point[2] = { 0.0f };
1600 _sample_raw_point_to_image_norm(sample, image_point);
1601 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1602 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1603 _bin_pixels_vectorscope_in_roi(image, vectorscope, x, x + 1, y, y + 1, width, zoom, d);
1604 }
1605}
1606
1607
1608static void _create_vectorscope_image(const uint32_t *const restrict vectorscope, uint8_t *const restrict image,
1609 const uint32_t max_hist, const float zoom)
1610{
1612 __OMP_PARALLEL_FOR__(collapse(2))
1613 for(size_t i = 0; i < HISTOGRAM_BINS; i++)
1614 for(size_t j = 0; j < HISTOGRAM_BINS; j++)
1615 {
1616 const size_t index = (HISTOGRAM_BINS - 1 - i) * HISTOGRAM_BINS + j;
1617 const float value = sqrtf((float)vectorscope[index] / (float)max_hist);
1618 dt_aligned_pixel_t RGB = { 0.f };
1619 // RGB gamuts tend to have a max chroma around L = 67
1621 dt_aligned_pixel_t xyY = { 0.f };
1622 dt_aligned_pixel_t XYZ = { 0.f };
1624 for(int c = 0; c < 2; c++) xyY[c] = fmaxf(xyY[c], 0.f);
1626 for(int c = 0; c < 3; c++) XYZ[c] = fmaxf(XYZ[c], 0.f);
1628
1629 // We will normalize RGB to get closer to display peak emission
1630 for(int c = 0; c < 3; c++) RGB[c] = fmaxf(RGB[c], 0.f);
1631 const float max_RGB = fmax(RGB[0], fmaxf(RGB[1], RGB[2]));
1632 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1633
1634 image[index * 4 + 3] = (uint8_t)roundf(value * 255.f); // alpha
1635 // Premultiply alpha
1636 image[index * 4 + 2] = (uint8_t)roundf(powf(RGB[0] * value, 1.f / 2.2f) * 255.f);
1637 image[index * 4 + 1] = (uint8_t)roundf(powf(RGB[1] * value, 1.f / 2.2f) * 255.f);
1638 image[index * 4 + 0] = (uint8_t)roundf(powf(RGB[2] * value, 1.f / 2.2f) * 255.f);
1639 }
1640}
1641
1642static void _bin_vectorscope(const float *const restrict image, uint32_t *const vectorscope,
1643 const size_t width, const size_t height,
1644 const float zoom, dt_lib_histogram_t *d)
1645{
1646 __OMP_FOR_SIMD__(aligned(vectorscope: 64) )
1647 for(size_t k = 0; k < HISTOGRAM_BINS * HISTOGRAM_BINS; k++) vectorscope[k] = 0;
1648
1649 if(_is_restricted(d))
1650 {
1651 // Bin only areas within color pickers
1652 GSList *samples = darktable.develop->color_picker.samples;
1653 while(samples)
1654 {
1655 dt_colorpicker_sample_t *sample = samples->data;
1656 _bin_pickers_vectorscope(image, vectorscope, width, height, zoom, d, sample);
1657 samples = g_slist_next(samples);
1658 }
1659
1661 _bin_pickers_vectorscope(image, vectorscope, width, height, zoom, d,
1663 }
1664 else
1665 {
1666 // Bin the whole image
1667 _bin_pixels_vectorscope_in_roi(image, vectorscope, 0, width, 0, height, width, zoom, d);
1668 }
1669}
1670
1671
1672static void _process_vectorscope(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1673 const int height, const float zoom, dt_lib_histogram_t *d)
1674{
1676 if(IS_NULL_PTR(profile) || IS_NULL_PTR(d)) return;
1677
1678 struct dt_pixel_cache_entry_t *entry = NULL;
1679 void *data = NULL;
1680 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1681 if(IS_NULL_PTR(piece)) return;
1682 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1683 if(!dt_dev_pixelpipe_cache_peek_gui(darktable.develop->preview_pipe, piece, &data, &entry, &d->scope_wait,
1685 {
1686 return;
1687 }
1688
1690
1691 uint32_t *const restrict vectorscope = dt_pixelpipe_cache_alloc_align_cache(
1692 HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint32_t),
1693 0);
1694 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1695 4 * HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint8_t),
1696 0);
1697 if(IS_NULL_PTR(vectorscope) || IS_NULL_PTR(image)) goto error;
1698
1699 _bin_vectorscope(data, vectorscope, backbuf->width, backbuf->height, zoom, d);
1700
1701 const uint32_t max_hist = _find_max_histogram(vectorscope, HISTOGRAM_BINS * HISTOGRAM_BINS);
1702 _create_vectorscope_image(vectorscope, image, max_hist, zoom);
1703
1704 // 2. Draw
1705 if(max_hist > 0)
1706 {
1707 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, HISTOGRAM_BINS);
1708 cairo_surface_t *background = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_ARGB32, HISTOGRAM_BINS, HISTOGRAM_BINS, stride);
1709 const int side = MIN(width, height);
1710 cairo_translate(cr, (double)(width - side) / 2., (double)(height - side) / 2.);
1711 cairo_scale(cr, (double)side / HISTOGRAM_BINS, (double)side / HISTOGRAM_BINS);
1712
1713 const double radius = (float)(HISTOGRAM_BINS - 1) / 2 - DT_PIXEL_APPLY_DPI(1.);
1714 const double x_center = (float)(HISTOGRAM_BINS - 1) / 2;
1715
1716 // Background circle - Color will not be exposed to user theme because this is tricky
1717 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1718 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1719 cairo_fill(cr);
1720
1721 // Center circle
1722 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1723 cairo_arc(cr, x_center, x_center, 2., 0., 2. * M_PI);
1724 cairo_fill(cr);
1725
1726 // Concentric circles
1727 for(int k = 0; k < 4; k++)
1728 {
1729 cairo_arc(cr, x_center, x_center, (double)k * HISTOGRAM_BINS / 8., 0., 2. * M_PI);
1730 cairo_stroke(cr);
1731 }
1732
1733 // RGB space primaries and secondaries
1734 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 },
1735 { 0.f, 1.f, 1.f, 0.f }, { 0.f, 0.f, 1.f, 0.f }, { 1.f, 0.f, 1.f, 0.f } };
1736
1737 cairo_save(cr);
1738 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1739 cairo_clip(cr);
1740
1741 for(size_t k = 0; k < 6; k++)
1742 {
1743 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1744 dt_aligned_pixel_t xyY = { 0.f };
1745 dt_aligned_pixel_t Luv = { 0.f };
1746 dt_ioppr_rgb_matrix_to_xyz(colors[k], XYZ_D50, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
1747 profile->lutsize, profile->nonlinearlut);
1750
1751 const double x = _Luv_to_vectorscope_coord_zoom(Luv[1], zoom);
1752 // Remember v = 0 is at the bottom of the square while Cairo puts y = 0 on top
1753 const double y = HISTOGRAM_BINS - 1 - _Luv_to_vectorscope_coord_zoom(Luv[2], zoom);
1754
1755 // First, draw hue angles
1756 dt_aligned_pixel_t Lch = { 0.f };
1758
1759 const double delta_x = radius * cosf(Lch[2]);
1760 const double delta_y = radius * sinf(Lch[2]);
1761 const double destination_x = x_center + delta_x;
1762 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1763 cairo_move_to(cr, x_center, x_center);
1764 cairo_line_to(cr, destination_x, destination_y);
1765 cairo_set_source_rgba(cr, colors[k][0], colors[k][1], colors[k][2], 0.5);
1766 cairo_stroke(cr);
1767
1768 // Then draw color squares and ensure center is filled with scope background color
1769 const double half_square = DT_PIXEL_APPLY_DPI(4);
1770 cairo_arc(cr, x, y, half_square, 0, 2. * M_PI);
1771 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1772 cairo_fill_preserve(cr);
1773 cairo_set_source_rgb(cr, colors[k][0], colors[k][1], colors[k][2]);
1774 cairo_stroke(cr);
1775 }
1776 cairo_restore(cr);
1777
1778 // Hues ring
1779 cairo_save(cr);
1780 cairo_arc(cr, x_center, x_center, radius - DT_PIXEL_APPLY_DPI(1.), 0., 2. * M_PI);
1781 cairo_set_source_rgb(cr, 0.33, 0.33, 0.33);
1782 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1783 cairo_stroke(cr);
1784 cairo_restore(cr);
1785
1786 for(size_t h = 0; h < 180; h++)
1787 {
1788 dt_aligned_pixel_t Lch = { 50.f, 110.f, h / 180.f * 2.f * M_PI_F, 1.f };
1789 dt_aligned_pixel_t Luv = { 0.f };
1790 dt_aligned_pixel_t xyY = { 0.f };
1791 dt_aligned_pixel_t XYZ = { 0.f };
1792 dt_aligned_pixel_t RGB = { 0.f };
1797 const float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
1798 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1799 const double delta_x = (radius - DT_PIXEL_APPLY_DPI(1.)) * cosf(Lch[2]);
1800 const double delta_y = (radius - DT_PIXEL_APPLY_DPI(1.)) * sinf(Lch[2]);
1801 const double destination_x = x_center + delta_x;
1802 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1803 cairo_set_source_rgba(cr, RGB[0], RGB[1], RGB[2], 0.7);
1804 cairo_arc(cr, destination_x, destination_y, DT_PIXEL_APPLY_DPI(1.), 0, 2. * M_PI);
1805 cairo_fill(cr);
1806 }
1807
1808 // Actual vectorscope
1809 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1810 cairo_clip(cr);
1811 cairo_set_source_surface(cr, background, 0., 0.);
1812 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1813 cairo_paint(cr);
1814 cairo_surface_destroy(background);
1815
1816 // Draw the skin tones area
1817 // Values obtained with :
1818 // get_skin_tones_range();
1819 const float max_c = 49.34f;
1820 const float min_c = 9.00f;
1821 const float max_h = 0.99f;
1822 const float min_h = 0.26f;
1823
1824 const float n_w_x = min_c * cosf(max_h);
1825 const float n_w_y = min_c * sinf(max_h);
1826 const float n_e_x = max_c * cosf(max_h);
1827 const float n_e_y = max_c * sinf(max_h);
1828 const float s_e_x = max_c * cosf(min_h);
1829 const float s_e_y = max_c * sinf(min_h);
1830 const float s_w_x = min_c * cosf(min_h);
1831 const float s_w_y = min_c * sinf(min_h);
1832 cairo_move_to(cr, _Luv_to_vectorscope_coord_zoom(n_w_x, zoom),
1833 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(n_w_y, zoom));
1834 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(n_e_x, zoom),
1835 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(n_e_y, zoom));
1836 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(s_e_x, zoom),
1837 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(s_e_y, zoom));
1838 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(s_w_x, zoom),
1839 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(s_w_y, zoom));
1840 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(n_w_x, zoom),
1841 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(n_w_y, zoom));
1842 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1843 cairo_stroke(cr);
1844 }
1845
1846error:;
1848 dt_pixelpipe_cache_free_align(vectorscope);
1850}
1851
1852gboolean _needs_recompute(dt_lib_histogram_t *d, const int width, const int height)
1853{
1854 // Check if cache is up-to-date
1856 gboolean hash_match = (d->cache.hash == dt_dev_backbuf_get_hash(d->backbuf));
1857 gboolean size_match = (d->cache.width == width && d->cache.height == height);
1858 gboolean zoom_match = (d->cache.zoom == d->zoom);
1859 gboolean view_match = (d->cache.view == view);
1860 gboolean has_surface = (!IS_NULL_PTR(d->cst));
1861 return !(hash_match && size_match && zoom_match && view_match && has_surface);
1862}
1863
1864
1865static gboolean _draw_callback(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1866{
1867 // Note: the draw callback is called from our own callback (mapped to "preview pipe finished recomputing" signal)
1868 // but is also called by Gtk when the main window is resized, exposed, etc.
1869 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
1870 if(IS_NULL_PTR(d->cst)) return 1;
1871 cairo_set_source_surface(crf, d->cst, 0, 0);
1872 cairo_paint(crf);
1873 return 0;
1874}
1875
1876
1878{
1879 GtkAllocation allocation;
1880 gtk_widget_get_allocation(d->scope_draw, &allocation);
1881 *width = allocation.width;
1882 *height = allocation.height;
1883}
1884
1908static void _process_restricted_text(dt_lib_histogram_t *d, cairo_t *cr, const int height)
1909{
1910 if(_is_restricted(d))
1911 {
1912 cairo_save(cr);
1914
1915 PangoLayout *layout = pango_cairo_create_layout(cr);
1916 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1917 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(12.) * PANGO_SCALE);
1918 pango_layout_set_font_description(layout, desc);
1919 pango_layout_set_text(layout, _("Restricted"), -1);
1920
1921 PangoRectangle ink;
1922 PangoRectangle logical;
1923 pango_layout_get_pixel_extents(layout, &ink, &logical);
1924
1925 const double padding = DT_PIXEL_APPLY_DPI(2.);
1926 const double text_padding = DT_PIXEL_APPLY_DPI(1.5);
1927 const double label_x = padding;
1928 const double label_y = MAX(padding, height - logical.height - 2. * text_padding - padding);
1929
1931 cairo_move_to(cr, label_x + text_padding - ink.x, label_y + text_padding);
1932 pango_cairo_show_layout(cr, layout);
1933
1934 pango_font_description_free(desc);
1935 g_object_unref(layout);
1936 cairo_restore(cr);
1937 }
1938}
1939
1941{
1942 if(IS_NULL_PTR(d->cst)) return 1;
1943
1944 dt_times_t start;
1945 dt_get_times(&start);
1946
1947 int width, height;
1949
1950 // Save cache integrity
1951 d->cache.hash = dt_dev_backbuf_get_hash(d->backbuf);
1952 d->cache.width = width;
1953 d->cache.height = height;
1954 d->cache.zoom = d->zoom;
1955 d->cache.view = d->scope;
1956
1957 cairo_t *cr = cairo_create(d->cst);
1958
1959 // Paint background
1960 gtk_render_background(gtk_widget_get_style_context(d->scope_draw), cr, 0, 0, width, height);
1961 cairo_set_line_width(cr, 1.); // we want exactly 1 px no matter the resolution
1962
1963 // Paint content
1964 switch(d->scope)
1965 {
1967 {
1968 _process_histogram(d->backbuf, d->op, cr, width, height, d);
1969 break;
1970 }
1972 {
1973 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, FALSE, d);
1974 break;
1975 }
1977 {
1978 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, FALSE, d);
1979 break;
1980 }
1982 {
1983 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, TRUE, d);
1984 break;
1985 }
1987 {
1988 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, TRUE, d);
1989 break;
1990 }
1992 {
1993 _process_vectorscope(d->backbuf, d->op, cr, width, height, d->zoom, d);
1994 break;
1995 }
1996 default:
1997 break;
1998 }
1999
2001
2002 cairo_destroy(cr);
2003 dt_show_times_f(&start, "[histogram]", "redraw");
2004 return 0;
2005}
2006
2008{
2009 if(d->cst && cairo_surface_get_reference_count(d->cst) > 0) cairo_surface_destroy(d->cst);
2010 if(d->cst && cairo_surface_get_reference_count(d->cst) == 0) d->cst = NULL;
2011}
2012
2014{
2015 int width, height;
2017
2019 {
2021 // If width and height have changed, we need to recreate the surface.
2022 // Recreate it anyway.
2023 d->cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
2025 // Don't send gtk_queue_redraw event from here, catch the return value and do it in the calling function
2026 //fprintf(stdout, "recreate histograms\n");
2027 return 1;
2028 }
2029
2030 return 0;
2031}
2032
2033static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op)
2034{
2035 dt_develop_t *const dev = darktable.develop;
2036 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2037 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(op) || IS_NULL_PTR(backbuf)) return NULL;
2038
2039 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2040 if(IS_NULL_PTR(module)) return NULL;
2041
2042 const dt_dev_pixelpipe_iop_t *piece = dt_dev_pixelpipe_get_module_piece(pipe, module);
2043 if(IS_NULL_PTR(piece)) return NULL;
2044
2045 if(!strcmp(op, "gamma"))
2046 piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
2047
2048 if(IS_NULL_PTR(piece) || piece->global_hash != dt_dev_backbuf_get_hash(backbuf)) return NULL;
2049 return piece;
2050}
2051
2074static gboolean _resolve_backbuf_sampling_source(const char *const op, const dt_backbuf_t *const backbuf,
2075 dt_iop_roi_t *const roi, dt_iop_buffer_dsc_t *const dsc,
2076 double *const iop_order, int *const direction)
2077{
2078 dt_develop_t *const dev = darktable.develop;
2079 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2080 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;
2081 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read live module state
2082 * without blocking and let the cache peek manager handle synchronization. */
2083 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2084 if(IS_NULL_PTR(module)) return FALSE;
2085
2086 const dt_dev_pixelpipe_iop_t *const piece = dt_dev_pixelpipe_get_module_piece(pipe, module);
2087 if(IS_NULL_PTR(piece)) return FALSE;
2088
2089 uint64_t hash = piece->global_hash;
2090 *roi = piece->roi_out;
2091 *dsc = piece->dsc_out;
2092 *iop_order = module->iop_order;
2093 *direction = DT_DEV_TRANSFORM_DIR_FORW_EXCL;
2094
2095 if(!strcmp(op, "gamma"))
2096 {
2097 const dt_dev_pixelpipe_iop_t *const previous_piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
2098 if(IS_NULL_PTR(previous_piece)) return FALSE;
2099
2100 hash = previous_piece->global_hash;
2101 *roi = previous_piece->roi_out;
2102 *dsc = previous_piece->dsc_out;
2103 *direction = DT_DEV_TRANSFORM_DIR_FORW_INCL;
2104 }
2105
2106 const gboolean valid = hash == dt_dev_backbuf_get_hash(backbuf);
2107 return valid;
2108}
2109
2110static void _pixelpipe_pick_from_image(const dt_backbuf_t *const backbuf,
2112 const char *op)
2113{
2114 struct dt_pixel_cache_entry_t *entry = NULL;
2115 void *data = NULL;
2116 const dt_dev_pixelpipe_iop_t *const source_piece = _get_backbuf_source_piece(backbuf, op);
2117 if(IS_NULL_PTR(source_piece)) return;
2118 dt_dev_pixelpipe_cache_wait_set_owner(&d->picker_wait, "histogram-picker", d->module);
2120 &d->picker_wait, _histogram_restart_cache_wait, d->module))
2121 {
2122 return;
2123 }
2124
2126
2127 const float *const pixel = data;
2128 dt_iop_buffer_dsc_t dsc = {
2129 .channels = 4,
2130 .datatype = TYPE_FLOAT,
2131 .bpp = 4 * sizeof(float),
2132 .cst = IOP_CS_RGB
2133 };
2134 dt_iop_roi_t roi = {
2135 .x = 0,
2136 .y = 0,
2137 .width = (int)backbuf->width,
2138 .height = (int)backbuf->height,
2139 .scale = 1.0
2140 };
2141 double iop_order = 0.0;
2142 int direction = DT_DEV_TRANSFORM_DIR_FORW_EXCL;
2143 const gboolean have_live_stage = _resolve_backbuf_sampling_source(op, backbuf, &roi, &dsc, &iop_order, &direction);
2144 int box[4];
2145
2146 if(have_live_stage)
2147 {
2148 dt_boundingbox_t fbox = { 0.0f };
2149
2150 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2151 {
2152 _sample_raw_box_to_image_norm(sample, fbox);
2154 }
2155 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2156 {
2157 _sample_raw_point_to_image_norm(sample, fbox);
2159 fbox[2] = fbox[0];
2160 fbox[3] = fbox[1];
2161 }
2162
2164 direction, fbox, 2);
2165
2166 /* The backtransform returns stage coordinates in the sampled piece space. Shift them by the
2167 stage ROI before clamping so the picker box matches the published cache window exactly. */
2168 fbox[0] -= roi.x;
2169 fbox[1] -= roi.y;
2170 fbox[2] -= roi.x;
2171 fbox[3] -= roi.y;
2172
2173 box[0] = fminf(fbox[0], fbox[2]);
2174 box[1] = fminf(fbox[1], fbox[3]);
2175 box[2] = fmaxf(fbox[0], fbox[2]);
2176 box[3] = fmaxf(fbox[1], fbox[3]);
2177
2178 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2179 {
2180 box[2] += 1;
2181 box[3] += 1;
2182 }
2183
2184 box[0] = MIN(roi.width - 1, MAX(0, box[0]));
2185 box[1] = MIN(roi.height - 1, MAX(0, box[1]));
2186 box[2] = MIN(roi.width - 1, MAX(0, box[2]));
2187 box[3] = MIN(roi.height - 1, MAX(0, box[3]));
2188 }
2189 else
2190 {
2192 return;
2193 }
2194
2195 dt_aligned_pixel_t mean = { 0.0f };
2196 dt_aligned_pixel_t min = { INFINITY, INFINITY, INFINITY, INFINITY };
2197 dt_aligned_pixel_t max = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
2198
2199 dt_color_picker_helper(&dsc, pixel, &roi, box, mean, min, max, dsc.cst, IOP_CS_RGB, NULL);
2200
2202 {
2203 sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k] = mean[k];
2206 }
2207
2208 dt_lib_histogram_t scope = *d;
2209 scope.op = op;
2210
2212 {
2213 _scope_pixel_to_display_rgb(sample->scope[k], sample->display[k], &scope);
2214
2215 dt_aligned_pixel_t XYZ = { 0.f };
2216 _scope_pixel_to_xyz(sample->scope[k], XYZ, &scope);
2217 dt_XYZ_to_Lab(XYZ, sample->lab[k]);
2218 }
2219
2221}
2222
2224{
2225 GSList *samples = darktable.develop->color_picker.samples;
2226 while(samples)
2227 {
2228 dt_colorpicker_sample_t *sample = samples->data;
2229 if(!sample->locked)
2230 {
2231 const char *const op = sample->backbuf_op[0] ? sample->backbuf_op : d->op;
2233 if(backbuf) _pixelpipe_pick_from_image(backbuf, sample, d, op);
2234 }
2235 samples = g_slist_next(samples);
2236 }
2237
2240}
2241
2242
2244{
2246
2248 {
2250 }
2251
2253
2254 for(GSList *samples = darktable.develop->color_picker.samples;
2255 samples;
2256 samples = g_slist_next(samples))
2257 {
2258 _update_sample_label(self, samples->data);
2259 }
2260
2262
2263 // allow live sample button to work for iop samples
2264 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button),
2266}
2267
2269{
2270 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return FALSE;
2271
2273 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
2274
2275 if(!_is_backbuf_ready(d)) return FALSE;
2276
2277 /* In restricted mode the scope bins only picker rectangles/points. Moving the
2278 * global picker does not change the preview cache hash, so invalidate the
2279 * Cairo scope cache explicitly before recomputing from the same backbuffer. */
2280 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2281 _reset_cache(d);
2282
2283 _update_everything(self);
2284 return TRUE;
2285}
2286
2287
2288static void _lib_histogram_history_resync_callback(gpointer instance, dt_lib_module_t *self)
2289{
2290 (void)instance;
2292}
2293
2294static void _lib_histogram_cacheline_ready_callback(gpointer instance, const guint64 hash,
2295 const guint64 producer_node_key, dt_lib_module_t *self)
2296{
2297 (void)instance;
2300 if(IS_NULL_PTR(d) || !_remove_pending_hash(d, hash)) return;
2301
2303}
2304
2305// this is only called in darkroom view when history was resynchronized
2320
2321void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
2322{
2323 dt_lib_histogram_t *d = self->data;
2324 _reset_cache(d);
2325 dt_dev_pixelpipe_cache_wait_cleanup(&d->scope_wait, "histogram-view-leave-scope");
2326 dt_dev_pixelpipe_cache_wait_cleanup(&d->picker_wait, "histogram-view-leave-picker");
2327 dt_dev_pixelpipe_cache_wait_cleanup(&d->module_wait, "histogram-view-leave-module");
2328
2331 if(d->refresh_idle_source != 0)
2332 {
2333 g_source_remove(d->refresh_idle_source);
2334 d->refresh_idle_source = 0;
2335 }
2336
2341}
2342
2343
2344static void _set_stage(dt_lib_module_t *self, int value)
2345{
2348 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2349
2350 // Vectorscope is meaningless for raw Bayer data
2351 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2352 {
2354 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2355 }
2356
2359}
2360
2362{
2364 d->scope = scope;
2365 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2367}
2368
2369
2370static void _resize_callback(GtkWidget *widget, GdkRectangle *allocation, dt_lib_histogram_t *d)
2371{
2372 _reset_cache(d);
2374 // Don't start a redraw from here, Gtk does it automatically on resize event
2375}
2376
2377static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_lib_histogram_t *d)
2378{
2379 if(d->scope != DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE) return FALSE;
2380
2381 int delta_y = 0;
2382 if(!dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y)) return TRUE;
2383
2384 const float new_value = 4.f * delta_y + d->zoom;
2385
2386 if(new_value < 512.f && new_value > 32.f)
2387 {
2388 d->zoom = new_value;
2389 dt_conf_set_float("plugin/darkroom/histogram/zoom", new_value);
2390 if(_is_backbuf_ready(d))
2391 {
2394 }
2395 }
2396 return TRUE;
2397}
2398
2399static int _scope_resize_handle_get_size(gpointer user_data)
2400{
2401 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2402 return gtk_widget_get_allocated_height(d->scope_draw);
2403}
2404
2412static int _scope_resize_handle_resize(int requested_size, gboolean finished, gpointer user_data)
2413{
2414 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2415 int window_height;
2416 gtk_window_get_size(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL, &window_height);
2417
2419 const int max_height = MAX(min_height, MIN(DT_PIXEL_APPLY_DPI(DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT),
2420 window_height * 3 / 4));
2421 d->scope_height = CLAMP(requested_size, min_height, max_height);
2422
2423 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
2424 gtk_widget_queue_resize(d->scope_draw);
2425 if(finished)
2427
2428 return d->scope_height;
2429}
2430
2432{
2433 d->op = dt_conf_get_string_const("plugin/darkroom/histogram/op");
2434 if(!strcmp(d->op, "demosaic"))
2435 {
2436 d->op = "initialscale";
2437 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2438 }
2439 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
2440 d->zoom = fminf(fmaxf(dt_conf_get_float("plugin/darkroom/histogram/zoom"), 32.f), 252.f);
2441
2442 d->scope = (dt_lib_histogram_scope_type_t)CLAMP(
2443 dt_conf_get_int("plugin/darkroom/histogram/display"), 0, DT_LIB_HISTOGRAM_SCOPE_N - 1);
2444
2445 // Vectorscope is not valid on raw Bayer data; fall back to histogram
2446 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2448}
2449
2450
2451static gboolean _sample_draw_callback(GtkWidget *widget, cairo_t *cr, dt_colorpicker_sample_t *sample)
2452{
2453 const guint width = gtk_widget_get_allocated_width(widget);
2454 const guint height = gtk_widget_get_allocated_height(widget);
2455
2456 set_color(cr, sample->swatch);
2457 cairo_rectangle(cr, 0, 0, width, height);
2458 cairo_fill (cr);
2459
2460 // if the sample is locked we want to add a lock
2461 if(sample->locked)
2462 {
2463 const int border = DT_PIXEL_APPLY_DPI(2);
2464 const int icon_width = width - 2 * border;
2465 const int icon_height = height - 2 * border;
2466 if(icon_width > 0 && icon_height > 0)
2467 {
2468 GdkRGBA fg_color;
2469 gtk_style_context_get_color(gtk_widget_get_style_context(widget), gtk_widget_get_state_flags(widget), &fg_color);
2470
2471 gdk_cairo_set_source_rgba(cr, &fg_color);
2472 dtgtk_cairo_paint_lock(cr, border, border, icon_width, icon_height, 0, NULL);
2473 }
2474 }
2475
2476 return FALSE;
2477}
2478
2480{
2481 dt_lib_histogram_t *d = self->data;
2482 const dt_lib_colorpicker_statistic_t statistic
2484
2485 sample->swatch.red = sample->display[statistic][0];
2486 sample->swatch.green = sample->display[statistic][1];
2487 sample->swatch.blue = sample->display[statistic][2];
2489 sample->label_rgb[ch] = (int)roundf(sample->scope[statistic][ch] * 255.f);
2490
2491 // Setting the output label
2492 char text[128] = { 0 };
2493 dt_aligned_pixel_t alt = { 0 };
2494
2495 switch(d->model)
2496 {
2498 snprintf(text, sizeof(text), "%6d %6d %6d", sample->label_rgb[0], sample->label_rgb[1], sample->label_rgb[2]);
2499 break;
2500
2502 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f",
2503 CLAMP(sample->lab[statistic][0], .0f, 100.0f), sample->lab[statistic][1], sample->lab[statistic][2]);
2504 break;
2505
2507 dt_Lab_2_LCH(sample->lab[statistic], alt);
2508 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", CLAMP(alt[0], .0f, 100.0f), alt[1], alt[2] * 360.f);
2509 break;
2510
2512 dt_RGB_2_HSL(sample->scope[statistic], alt);
2513 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2514 break;
2515
2517 dt_RGB_2_HSV(sample->scope[statistic], alt);
2518 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2519 break;
2520
2521 default:
2523 snprintf(text, sizeof(text), "\342\227\216");
2524 break;
2525 }
2526
2527 if(g_strcmp0(gtk_label_get_text(GTK_LABEL(sample->output_label)), text))
2528 gtk_label_set_text(GTK_LABEL(sample->output_label), text);
2529
2530 gtk_widget_queue_draw(sample->color_patch);
2531}
2532
2534{
2535 _update_everything(self);
2536}
2537
2538static void _picker_button_toggled(GtkToggleButton *button, dt_lib_histogram_t *d)
2539{
2540 const gboolean picker_active = gtk_toggle_button_get_active(button);
2541 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), picker_active);
2542
2543 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
2544 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
2545 if(restrict_active)
2546 {
2547 /* The picker state is part of restricted-scope rendering but not of the
2548 * preview backbuffer hash. Invalidate on both activation and deactivation
2549 * so the same cached pixels are rebinned for the newly selected scope. */
2550 _reset_cache(d);
2552 }
2553}
2554
2555/* set sample area proxy impl */
2556
2558{
2560 dt_lib_histogram_t *d = self->data;
2561 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2562 _reset_cache(d);
2563 _update_everything(self);
2564}
2565
2566static void _set_sample_point(dt_lib_module_t *self, const float pos[2])
2567{
2569 dt_lib_histogram_t *d = self->data;
2570 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2571 _reset_cache(d);
2572 _update_everything(self);
2573}
2574
2575
2576static gboolean _sample_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
2577 GtkTooltip *tooltip, const dt_colorpicker_sample_t *sample)
2578{
2579 gchar **sample_parts = g_malloc0_n(14, sizeof(char*));
2580
2581 sample_parts[3] = g_strdup_printf("%22s(0x%02X%02X%02X)\n<big><b>%14s</b></big>", " ",
2582 CLAMP(sample->label_rgb[0], 0, 255), CLAMP(sample->label_rgb[1], 0, 255),
2583 CLAMP(sample->label_rgb[2], 0, 255), _("RGB"));
2584 sample_parts[7] = g_strdup_printf("\n<big><b>%14s</b></big>", _("Lab"));
2585
2586 for(int i = 0; i < DT_LIB_COLORPICKER_STATISTIC_N; i++)
2587 {
2588 sample_parts[i] = g_strdup_printf("<span background='#%02X%02X%02X'>%32s</span>",
2589 (int)roundf(CLAMP(sample->display[i][0], 0.f, 1.f) * 255.f),
2590 (int)roundf(CLAMP(sample->display[i][1], 0.f, 1.f) * 255.f),
2591 (int)roundf(CLAMP(sample->display[i][2], 0.f, 1.f) * 255.f), " ");
2592
2593 sample_parts[i + 4] = g_strdup_printf("<span foreground='#FF7F7F'>%6d</span> "
2594 "<span foreground='#7FFF7F'>%6d</span> "
2595 "<span foreground='#7F7FFF'>%6d</span> %s",
2596 (int)roundf(sample->scope[i][0] * 255.f),
2597 (int)roundf(sample->scope[i][1] * 255.f),
2598 (int)roundf(sample->scope[i][2] * 255.f),
2600
2601 sample_parts[i + 8] = g_strdup_printf("%6.02f %6.02f %6.02f %s",
2602 sample->lab[i][0], sample->lab[i][1], sample->lab[i][2],
2604 }
2605
2606 dt_aligned_pixel_t color;
2607 dt_Lab_2_LCH(sample->lab[DT_LIB_COLORPICKER_STATISTIC_MEAN], color);
2608 sample_parts[11] = g_strdup_printf("\n<big><b>%14s</b></big>", _("color"));
2609 sample_parts[12] = g_strdup_printf("%6s", Lch_to_color_name(color));
2610
2611 gchar *tooltip_text = g_strjoinv("\n", sample_parts);
2612 g_strfreev(sample_parts);
2613
2614 gtk_tooltip_set_markup(tooltip, tooltip_text);
2615
2616 dt_free(tooltip_text);
2617
2618 return TRUE;
2619}
2620
2622{
2623 dt_lib_histogram_t *d = self->data;
2624 d->statistic = dt_bauhaus_combobox_get(widget);
2625 darktable.develop->color_picker.statistic = (int)d->statistic;
2626 dt_conf_set_string("ui_last/colorpicker_mode", dt_lib_colorpicker_statistic_names[d->statistic]);
2627 _update_everything(self);
2628}
2629
2631{
2632 dt_lib_histogram_t *d = self->data;
2633 d->model = dt_bauhaus_combobox_get(widget);
2634 dt_conf_set_string("ui_last/colorpicker_model", dt_lib_colorpicker_model_names[d->model]);
2635 _update_everything(self);
2636}
2637
2638static void _label_size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
2639{
2640 gint label_width;
2641 gtk_label_set_attributes(GTK_LABEL(widget), NULL);
2642
2643 PangoStretch stretch = PANGO_STRETCH_NORMAL;
2644
2645 while(gtk_widget_get_preferred_width(widget, NULL, &label_width),
2646 label_width > allocation->width && stretch != PANGO_STRETCH_ULTRA_CONDENSED)
2647 {
2648 stretch--;
2649
2650 PangoAttrList *attrlist = pango_attr_list_new();
2651 PangoAttribute *attr = pango_attr_stretch_new(stretch);
2652 pango_attr_list_insert(attrlist, attr);
2653 gtk_label_set_attributes(GTK_LABEL(widget), attrlist);
2654 pango_attr_list_unref(attrlist);
2655 }
2656}
2657
2658static gboolean _sample_enter_callback(GtkWidget *widget, GdkEvent *event, dt_colorpicker_sample_t *sample)
2659{
2661 {
2664 }
2665
2666 return FALSE;
2667}
2668
2669static gboolean _sample_leave_callback(GtkWidget *widget, GdkEvent *event, gpointer data)
2670{
2671 if(event->crossing.detail == GDK_NOTIFY_INFERIOR) return FALSE;
2672
2674 {
2677 }
2678
2679 return FALSE;
2680}
2681
2683{
2684 gtk_widget_destroy(sample->container);
2686 = g_slist_remove(darktable.develop->color_picker.samples, (gpointer)sample);
2687 dt_free(sample);
2688}
2689
2690static void _remove_sample_cb(GtkButton *widget, dt_colorpicker_sample_t *sample)
2691{
2693 dt_lib_histogram_t *d = self ? self->data : NULL;
2694 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram") && !IS_NULL_PTR(d))
2695 _reset_cache(d);
2696
2697 _remove_sample(sample);
2699 if(!IS_NULL_PTR(self))
2700 _update_everything(self);
2701}
2702
2703static gboolean _live_sample_button(GtkWidget *widget, GdkEventButton *event, dt_colorpicker_sample_t *sample)
2704{
2705 if(event->button == 1)
2706 {
2707 sample->locked = !sample->locked;
2708 gtk_widget_queue_draw(widget);
2709 }
2710 else if(event->button == 3)
2711 {
2712 // copy to active picker
2715
2716 // no active picker, too much iffy GTK work to activate a default
2717 if(IS_NULL_PTR(picker)) return FALSE;
2718
2719 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2720 {
2721 float point[2] = { 0.0f };
2723 _set_sample_point(self, point);
2724 }
2725 else if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2726 {
2727 dt_boundingbox_t box = { 0.0f };
2728 _sample_raw_box_to_image_norm(sample, box);
2729 _set_sample_box_area(self, box);
2730 }
2731 else
2732 return FALSE;
2733
2735 }
2736 return FALSE;
2737}
2738
2739static void _add_sample(GtkButton *widget, dt_lib_module_t *self)
2740{
2741 dt_lib_histogram_t *d = self->data;
2742
2744 return;
2745
2747 if(IS_NULL_PTR(sample)) return;
2748
2750 sample->locked = FALSE;
2751 g_strlcpy(sample->backbuf_op, d->op ? d->op : "", sizeof(sample->backbuf_op));
2752
2753 sample->container = gtk_event_box_new();
2754 gtk_widget_add_events(sample->container, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
2755 g_signal_connect(G_OBJECT(sample->container), "enter-notify-event", G_CALLBACK(_sample_enter_callback), sample);
2756 g_signal_connect(G_OBJECT(sample->container), "leave-notify-event", G_CALLBACK(_sample_leave_callback), sample);
2757
2758 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2759 gtk_container_add(GTK_CONTAINER(sample->container), container);
2760
2761 sample->color_patch = gtk_drawing_area_new();
2762 gtk_widget_add_events(sample->color_patch, GDK_BUTTON_PRESS_MASK);
2763 gtk_widget_set_tooltip_text(sample->color_patch, _("hover to highlight sample on canvas,\n"
2764 "click to lock sample,\n"
2765 "right-click to load sample area into active color picker"));
2766 g_signal_connect(G_OBJECT(sample->color_patch), "button-press-event", G_CALLBACK(_live_sample_button), sample);
2767 g_signal_connect(G_OBJECT(sample->color_patch), "draw", G_CALLBACK(_sample_draw_callback), sample);
2768
2769 GtkWidget *color_patch_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2770 gtk_widget_set_name(color_patch_wrapper, "live-sample");
2771 gtk_box_pack_start(GTK_BOX(color_patch_wrapper), sample->color_patch, TRUE, TRUE, 0);
2772 gtk_box_pack_start(GTK_BOX(container), color_patch_wrapper, TRUE, TRUE, 0);
2773
2774 sample->output_label = gtk_label_new("");
2775 dt_gui_add_class(sample->output_label, "dt_monospace");
2776 gtk_label_set_ellipsize(GTK_LABEL(sample->output_label), PANGO_ELLIPSIZE_START);
2777 gtk_label_set_selectable(GTK_LABEL(sample->output_label), TRUE);
2778 gtk_widget_set_has_tooltip(sample->output_label, TRUE);
2779 g_signal_connect(G_OBJECT(sample->output_label), "query-tooltip", G_CALLBACK(_sample_tooltip_callback), sample);
2780 g_signal_connect(G_OBJECT(sample->output_label), "size-allocate", G_CALLBACK(_label_size_allocate_callback), sample);
2781 gtk_box_pack_start(GTK_BOX(container), sample->output_label, TRUE, TRUE, 0);
2782
2784 g_signal_connect(G_OBJECT(delete_button), "clicked", G_CALLBACK(_remove_sample_cb), sample);
2785 gtk_box_pack_start(GTK_BOX(container), delete_button, FALSE, FALSE, 0);
2786
2787 gtk_box_pack_start(GTK_BOX(d->samples_container), sample->container, FALSE, FALSE, 0);
2788 gtk_widget_show_all(sample->container);
2789
2791 = g_slist_append(darktable.develop->color_picker.samples, sample);
2792
2793 // remove emphasis on primary sample from mouseover on this button
2795
2796 // Updating the display
2797 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2798 _reset_cache(d);
2799 _update_everything(self);
2800}
2801
2802static void _display_samples_changed(GtkToggleButton *button, dt_lib_module_t *self)
2803{
2804 dt_conf_set_bool("ui_last/colorpicker_display_samples", gtk_toggle_button_get_active(button));
2805 darktable.develop->color_picker.display_samples = gtk_toggle_button_get_active(button);
2806 _update_everything(self);
2808}
2809
2810static void _restrict_histogram_changed(GtkToggleButton *button, dt_lib_module_t *self)
2811{
2812 dt_lib_histogram_t *d = self->data;
2813 const gboolean restrict_active = gtk_toggle_button_get_active(button);
2814 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
2815 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->picker_button));
2816 dt_conf_set_bool("ui_last/colorpicker_restrict_histogram", restrict_active);
2818
2819 if(picker_active)
2820 {
2821 /* Restrict toggling changes the scope bins between full-image and picker
2822 * geometry while the preview backbuffer hash stays identical. Make the
2823 * cached Cairo surface dirty so _update_everything() renders the new mode. */
2824 _reset_cache(d);
2825 }
2826 _update_everything(self);
2827}
2828
2830{
2831 dt_lib_histogram_t *d = self->data;
2833
2835
2836 // Resetting the picked colors
2837 for(int i = 0; i < 3; i++)
2838 {
2839 for(int s = 0; s < DT_LIB_COLORPICKER_STATISTIC_N; s++)
2840 {
2841 primary_sample->display[s][i] = 0.f;
2842 primary_sample->scope[s][i] = 0.f;
2843 primary_sample->lab[s][i] = 0.f;
2844 }
2845 primary_sample->label_rgb[i] = 0;
2846 }
2847 primary_sample->swatch.red = primary_sample->swatch.green
2848 = primary_sample->swatch.blue = 0.0;
2849
2851
2852 // Removing any live samples
2855
2856 // Resetting GUI elements
2857 dt_bauhaus_combobox_set(d->statistic_selector, 0);
2858 dt_bauhaus_combobox_set(d->color_mode_selector, 0);
2859 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box)))
2860 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box), FALSE);
2861
2862 _reset_cache(d);
2863 _set_params(d);
2866
2868}
2869
2870static void _pref_stage_toggled(GtkCheckMenuItem *item, gpointer user_data)
2871{
2872 if(!gtk_check_menu_item_get_active(item)) return;
2873 dt_lib_module_t *self = (dt_lib_module_t *)g_object_get_data(G_OBJECT(item), "self");
2874 _set_stage(self, GPOINTER_TO_INT(user_data));
2875}
2876
2877static void _pref_display_toggled(GtkCheckMenuItem *item, gpointer user_data)
2878{
2879 if(!gtk_check_menu_item_get_active(item)) return;
2880 dt_lib_module_t *self = (dt_lib_module_t *)g_object_get_data(G_OBJECT(item), "self");
2881 _set_scope(self, (dt_lib_histogram_scope_type_t)GPOINTER_TO_INT(user_data));
2882}
2883
2884void set_preferences(void *menu, dt_lib_module_t *self)
2885{
2887
2888 // "Show data from" submenu
2889 GtkWidget *mi_stage = gtk_menu_item_new_with_label(_("Show data from"));
2890 GtkWidget *submenu_stage = gtk_menu_new();
2891 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi_stage), submenu_stage);
2892
2893 const char *stage_labels[] = { N_("Raw image"), N_("Output color profile"), N_("Final display"), NULL };
2894 const int current_stage = _backbuf_op_to_int(d);
2895 GSList *stage_group = NULL;
2896 for(int i = 0; stage_labels[i]; i++)
2897 {
2898 GtkWidget *item = gtk_radio_menu_item_new_with_label(stage_group, _(stage_labels[i]));
2899 stage_group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
2900 if(i == current_stage) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2901 g_object_set_data(G_OBJECT(item), "self", self);
2902 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(_pref_stage_toggled), GINT_TO_POINTER(i));
2903 gtk_menu_shell_append(GTK_MENU_SHELL(submenu_stage), item);
2904 }
2905 gtk_widget_show_all(mi_stage);
2906 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi_stage);
2907
2908 // "Display" submenu
2909 GtkWidget *mi_display = gtk_menu_item_new_with_label(_("Display"));
2910 GtkWidget *submenu_display = gtk_menu_new();
2911 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi_display), submenu_display);
2912
2913 const char *display_labels[] = {
2914 N_("Histogram"), N_("Waveform (horizontal)"), N_("Waveform (vertical)"),
2915 N_("Parade (horizontal)"), N_("Parade (vertical)"), N_("Vectorscope"), NULL
2916 };
2917 const gboolean vectorscope_ok = strcmp(d->op, "initialscale");
2918 GSList *display_group = NULL;
2919 for(int i = 0; display_labels[i]; i++)
2920 {
2921 GtkWidget *item = gtk_radio_menu_item_new_with_label(display_group, _(display_labels[i]));
2922 display_group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
2923 if((dt_lib_histogram_scope_type_t)i == d->scope)
2924 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2926 gtk_widget_set_sensitive(item, vectorscope_ok);
2927 g_object_set_data(G_OBJECT(item), "self", self);
2928 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(_pref_display_toggled), GINT_TO_POINTER(i));
2929 gtk_menu_shell_append(GTK_MENU_SHELL(submenu_display), item);
2930 }
2931 gtk_widget_show_all(mi_display);
2932 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi_display);
2933}
2934
2935void gui_init(dt_lib_module_t *self)
2936{
2937 /* initialize ui widgets */
2939 if(d) memset(d, 0, sizeof(dt_lib_histogram_t));
2940 self->data = (void *)d;
2941 d->module = self;
2942 d->cst = NULL;
2943 d->pending_hashes = g_array_new(FALSE, FALSE, sizeof(uint64_t));
2944 d->refresh_idle_source = 0;
2945
2946 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2947 d->scope_draw = gtk_drawing_area_new();
2948 gtk_widget_add_events(GTK_WIDGET(d->scope_draw), darktable.gui->scroll_mask);
2952 d->scope_height = CLAMP(d->scope_height,
2955 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
2956 g_signal_connect(G_OBJECT(d->scope_draw), "draw", G_CALLBACK(_draw_callback), d);
2957 g_signal_connect(G_OBJECT(d->scope_draw), "scroll-event", G_CALLBACK(_area_scrolled_callback), d);
2958 g_signal_connect(G_OBJECT(d->scope_draw), "size-allocate", G_CALLBACK(_resize_callback), d);
2959
2960 // The grip floats on the scope's bottom edge (overlay) so it takes no layout space and leaves no
2961 // margin-like gap; invisible until hovered.
2962 GtkWidget *scope_overlay = gtk_overlay_new();
2963 gtk_container_add(GTK_CONTAINER(scope_overlay), d->scope_draw);
2964 gtk_box_pack_start(GTK_BOX(self->widget), scope_overlay, FALSE, FALSE, 0);
2965
2978 d->scope_resize_handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_VERTICAL, FALSE,
2979 _("Drag to resize the scope vertically"),
2982 gtk_overlay_add_overlay(GTK_OVERLAY(scope_overlay), d->scope_resize_handle);
2983
2985 (&d->cs,
2986 "plugins/darkroom/colorpicker/show",
2987 _("Color picker"),
2988 GTK_BOX(self->widget), GTK_PACK_END);
2989
2990 // The develop module owns the picker state because both the preview pipe and the GUI need to
2991 // observe it. Histogram only binds its widgets to that shared state.
2994 darktable.develop->color_picker.display_samples = dt_conf_get_bool("ui_last/colorpicker_display_samples");
2995 darktable.develop->color_picker.restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
2997
2998 const char *str = dt_conf_get_string_const("ui_last/colorpicker_model");
2999 const char **names = dt_lib_colorpicker_model_names;
3000 for(dt_lib_colorpicker_model_t i=0; *names; names++, i++)
3001 if(g_strcmp0(str, *names) == 0)
3002 d->model = i;
3003
3004 str = dt_conf_get_string_const("ui_last/colorpicker_mode");
3006 for(dt_lib_colorpicker_statistic_t i=0; *names; names++, i++)
3007 if(g_strcmp0(str, *names) == 0)
3008 d->statistic = i;
3009
3010 // The color patch
3011 GtkWidget *color_patch_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3012 gtk_widget_set_name(GTK_WIDGET(color_patch_wrapper), "color-picker-area");
3013
3014 // The picker button, mode and statistic combo boxes
3015 GtkWidget *picker_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3016 gtk_box_pack_start(GTK_BOX(d->cs.container), picker_row, TRUE, TRUE, 0);
3017
3018 d->statistic_selector = dt_bauhaus_combobox_new_full(
3019 darktable.bauhaus, NULL, NULL, _("select which statistic to show"), d->statistic,
3021 dt_bauhaus_combobox_set_entries_ellipsis(d->statistic_selector, PANGO_ELLIPSIZE_NONE);
3022 dt_bauhaus_widget_set_label(d->statistic_selector, NULL);
3023 gtk_widget_set_valign(d->statistic_selector, GTK_ALIGN_CENTER);
3024 gtk_box_pack_start(GTK_BOX(picker_row), d->statistic_selector, TRUE, TRUE, 0);
3025
3026 d->color_mode_selector = dt_bauhaus_combobox_new_full(
3027 darktable.bauhaus, NULL, NULL, _("select which color mode to use"), d->model,
3029 dt_bauhaus_combobox_set_entries_ellipsis(d->color_mode_selector, PANGO_ELLIPSIZE_NONE);
3030 dt_bauhaus_widget_set_label(d->color_mode_selector, NULL);
3031 gtk_widget_set_valign(d->color_mode_selector, GTK_ALIGN_CENTER);
3032 gtk_box_pack_start(GTK_BOX(picker_row), d->color_mode_selector, TRUE, TRUE, 0);
3033
3034 d->picker_button = dt_color_picker_new(NULL, DT_COLOR_PICKER_POINT_AREA, picker_row);
3035 gtk_widget_set_tooltip_text(d->picker_button, _("turn on color picker\nctrl+click or right-click to select an area"));
3036 gtk_widget_set_name(GTK_WIDGET(d->picker_button), "color-picker-button");
3037 g_signal_connect(G_OBJECT(d->picker_button), "toggled", G_CALLBACK(_picker_button_toggled), d);
3038
3039 // The small sample, label and add button
3040 GtkWidget *sample_row_events = gtk_event_box_new();
3041 gtk_widget_add_events(sample_row_events, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
3042 g_signal_connect(G_OBJECT(sample_row_events), "enter-notify-event", G_CALLBACK(_sample_enter_callback),
3044 g_signal_connect(G_OBJECT(sample_row_events), "leave-notify-event", G_CALLBACK(_sample_leave_callback),
3046 gtk_box_pack_start(GTK_BOX(d->cs.container), sample_row_events, TRUE, TRUE, 0);
3047
3048 GtkWidget *sample_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3049 gtk_container_add(GTK_CONTAINER(sample_row_events), sample_row);
3050
3051 darktable.develop->color_picker.primary_sample->color_patch = gtk_drawing_area_new();
3052 g_signal_connect(G_OBJECT(darktable.develop->color_picker.primary_sample->color_patch), "draw",
3054
3055 color_patch_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3056 gtk_widget_set_name(color_patch_wrapper, "live-sample");
3057 gtk_box_pack_start(GTK_BOX(color_patch_wrapper), darktable.develop->color_picker.primary_sample->color_patch, TRUE, TRUE, 0);
3058 gtk_box_pack_start(GTK_BOX(sample_row), color_patch_wrapper, TRUE, TRUE, 0);
3059
3060 GtkWidget *label = darktable.develop->color_picker.primary_sample->output_label = gtk_label_new("");
3061 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
3062 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
3063 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
3064 dt_gui_add_class(label, "dt_monospace");
3065 gtk_widget_set_has_tooltip(label, TRUE);
3066 g_signal_connect(G_OBJECT(label), "query-tooltip", G_CALLBACK(_sample_tooltip_callback),
3068 g_signal_connect(G_OBJECT(label), "size-allocate", G_CALLBACK(_label_size_allocate_callback),
3070 gtk_box_pack_start(GTK_BOX(sample_row), label, TRUE, TRUE, 0);
3071
3072 d->add_sample_button = dtgtk_button_new(dtgtk_cairo_paint_square_plus, 0, NULL);
3073 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), FALSE);
3074 g_signal_connect(G_OBJECT(d->add_sample_button), "clicked", G_CALLBACK(_add_sample), self);
3075 gtk_box_pack_end(GTK_BOX(sample_row), d->add_sample_button, FALSE, FALSE, 0);
3076
3077 // Adding the live samples section
3078 label = dt_ui_section_label_new(_("Live samples"));
3079 gtk_box_pack_start(GTK_BOX(d->cs.container), label, TRUE, TRUE, 0);
3080
3081 d->samples_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3082 gtk_box_pack_start(GTK_BOX(d->cs.container),
3083 dt_ui_scroll_wrap(d->samples_container, 1, "plugins/darkroom/colorpicker/windowheight",
3085
3086 d->display_samples_check_box = gtk_check_button_new_with_label(_("Display samples on image"));
3087 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->display_samples_check_box))),
3088 PANGO_ELLIPSIZE_MIDDLE);
3089 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box),
3090 dt_conf_get_bool("ui_last/colorpicker_display_samples"));
3091 g_signal_connect(G_OBJECT(d->display_samples_check_box), "toggled",
3092 G_CALLBACK(_display_samples_changed), self);
3093 gtk_box_pack_start(GTK_BOX(d->cs.container), d->display_samples_check_box, TRUE, TRUE, 0);
3094
3095 d->restrict_button = gtk_check_button_new_with_label(_("Restrict scope to selection"));
3096 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->restrict_button))), PANGO_ELLIPSIZE_MIDDLE);
3097 gboolean restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
3098 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->restrict_button), restrict_histogram);
3099 g_signal_connect(G_OBJECT(d->restrict_button), "toggled", G_CALLBACK(_restrict_histogram_changed), self);
3100 gtk_box_pack_start(GTK_BOX(d->cs.container), d->restrict_button, TRUE, TRUE, 0);
3101
3102 _reset_cache(d);
3103 _set_params(d);
3104}
3105
3107{
3108 if(IS_NULL_PTR(self->data)) return;
3109 dt_lib_histogram_t *d = self->data;
3110 dt_dev_pixelpipe_cache_wait_cleanup(&d->scope_wait, "histogram-gui-cleanup-scope");
3111 dt_dev_pixelpipe_cache_wait_cleanup(&d->picker_wait, "histogram-gui-cleanup-picker");
3112 dt_dev_pixelpipe_cache_wait_cleanup(&d->module_wait, "histogram-gui-cleanup-module");
3115 if(d->refresh_idle_source != 0)
3116 {
3117 g_source_remove(d->refresh_idle_source);
3118 d->refresh_idle_source = 0;
3119 }
3120 if(d->pending_hashes) g_array_free(d->pending_hashes, TRUE);
3123
3128
3130 self->data = NULL;
3131}
3132
3133// clang-format off
3134// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3135// vim: shiftwidth=2 expandtab tabstop=2 cindent
3136// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3137// clang-format on
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:1852
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2350
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:2067
GtkWidget * dt_bauhaus_resize_handle_new(GtkOrientation orientation, gboolean invert, const char *tooltip, dt_bauhaus_resize_handle_get_size_f get_size, dt_bauhaus_resize_handle_resize_f resize, gpointer user_data)
Create a themed handle widget driving one-dimensional resize gestures.
Definition bauhaus.c:1180
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2304
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1656
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:446
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:134
@ 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
const char * Lch_to_color_name(dt_aligned_pixel_t color)
dt_lib_colorpicker_statistic_t
Definition colorpicker.h:42
@ DT_LIB_COLORPICKER_STATISTIC_MAX
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_N
Definition colorpicker.h:46
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:44
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:43
@ DT_LIB_COLORPICKER_SIZE_POINT
Definition colorpicker.h:37
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:38
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
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
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
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)
char * name
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)
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
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:922
uint32_t view(const dt_view_t *self)
Definition darkroom.c:228
darktable_t darktable
Definition darktable.c:183
void * dt_alloc_align(size_t size)
Definition darktable.c:484
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...)
Definition darktable.c:1652
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define dt_free_align(ptr)
Definition darktable.h:503
@ DT_DEBUG_VERBOSE
Definition darktable.h:765
@ DT_DEBUG_DEV
Definition darktable.h:739
#define for_each_channel(_var,...)
Definition darktable.h:684
float dt_boundingbox_t[4]
Definition darktable.h:731
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
Definition darktable.h:445
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:478
static void dt_get_times(dt_times_t *t)
Definition darktable.h:983
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define for_four_channels(_var,...)
Definition darktable.h:686
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define __OMP_FOR_SIMD__(...)
Definition darktable.h:272
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:271
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
#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(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, void **data, dt_pixel_cache_entry_t **cache_entry, dt_dev_pixelpipe_cache_wait_t *wait, dt_dev_pixelpipe_cache_ready_callback_t restart, gpointer restart_data)
#define dt_dev_pixelpipe_update_history_preview(dev)
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1197
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1184
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:1957
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:1650
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:104
static void dt_draw_line(cairo_t *cr, float left, float top, float right, float bottom)
Definition draw.h:137
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:143
static void dt_draw_histogram_8(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const gboolean linear)
Definition draw.h:454
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)
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
const dt_collection_filter_flag_t colors[6]
Definition filter.c:295
@ TYPE_FLOAT
Definition format.h:46
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
Definition gtk.c:300
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.
Definition gtk.c:3334
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2945
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:322
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:469
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:266
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
const char * tooltip
Definition image.h:251
dt_iop_module_t * dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
Definition imageop.c:3160
static gboolean dt_iop_colorspace_is_rgb(const dt_iop_colorspace_type_t cst)
Definition imageop.h:243
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:450
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:456
static void gui_init(dt_lib_import_t *d)
Definition import.c:1048
void dt_ioppr_transform_image_colorspace(struct dt_iop_module_t *self, 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)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
static const float x
static dt_aligned_pixel_t rgb_out
const float v
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1456
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1478
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 _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 _refresh_module_histogram(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const float *pixel, dt_iop_module_t *module)
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 _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 _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 width)
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 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)
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define M_PI
Definition math.h:45
float dt_aligned_pixel_t[4]
@ DT_REQUEST_ON
Definition pixelpipe.h:48
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:49
void dt_dev_pixelpipe_cache_ref_count_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
void dt_dev_pixelpipe_cache_unref_hash(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash)
Find the entry matching hash, and decrease its ref_count if found.
void dt_dev_pixelpipe_cache_rdlock_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:209
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:185
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
struct _GtkWidget GtkWidget
Definition splash.h:29
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_lib_t * lib
Definition darktable.h:799
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:818
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
int32_t unmuted
Definition darktable.h:788
struct dt_develop_t * develop
Definition darktable.h:798
GdkRGBA graph_bg
Definition bauhaus.h:281
GdkRGBA graph_scope_restricted
Definition bauhaus.h:282
GdkRGBA graph_grid
Definition bauhaus.h:281
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
GdkRGBA graph_colors[3]
Definition bauhaus.h:283
lib_colorpicker_sample_statistics display
Definition colorpicker.h:69
dt_lib_colorpicker_size_t size
Definition colorpicker.h:63
lib_colorpicker_sample_statistics scope
Definition colorpicker.h:72
char backbuf_op[32]
Global histogram stage backing this live sample.
Definition colorpicker.h:92
dt_boundingbox_t box
Definition colorpicker.h:62
lib_colorpicker_sample_statistics lab
Definition colorpicker.h:74
const struct dt_histogram_roi_t * roi
Definition pixelpipe.h:56
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:162
struct dt_develop_t::@19 color_picker
Authoritative darkroom color-picker state.
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:397
dt_backbuf_t display_histogram
Definition develop.h:343
GList * iop
Definition develop.h:285
dt_backbuf_t output_histogram
Definition develop.h:342
gboolean(* refresh_global_picker)(struct dt_lib_module_t *self)
Definition develop.h:405
gboolean restrict_histogram
Definition develop.h:402
int statistic
Definition develop.h:403
GSList * samples
Definition develop.h:398
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:389
dt_backbuf_t raw_histogram
Definition develop.h:341
struct dt_colorpicker_sample_t * selected_sample
Definition develop.h:399
struct dt_lib_module_t * histogram_module
Definition develop.h:404
gboolean display_samples
Definition develop.h:400
gint scroll_mask
Definition gtk.h:230
dt_ui_t * ui
Definition gtk.h:165
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
GtkWidget * widget
Definition imageop.h:374
dt_dev_histogram_stats_t histogram_stats
Definition imageop.h:315
uint32_t histogram_max[4]
Definition imageop.h:317
int histogram_middle_grey
Definition imageop.h:324
uint32_t * histogram
Definition imageop.h:313
dt_iop_colorspace_type_t histogram_cst
Definition imageop.h:322
dt_colormatrix_t matrix_out_transposed
Definition iop_profile.h:66
dt_colormatrix_t matrix_in_transposed
Definition iop_profile.h:65
Region of interest passed through the pixelpipe.
Definition imageop.h:72
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:80
GtkWidget * widget
Definition lib.h:84
uint64_t hash
void * data
uint64_t producer_node_key
GQueue * log
Definition supervisor.c:117
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
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER