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, gpointer user_data)
529{
530 (void)instance;
531 (void)user_data;
532
533 dt_develop_t *const dev = darktable.develop;
534 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->preview_pipe)) return;
536 {
537 _refresh_global_histogram_backbuf_for_hash(dev, "initialscale", hash);
539 }
540
542 {
543 _refresh_global_histogram_backbuf_for_hash(dev, "colorout", hash);
545 }
546
548 {
551 }
552
553 for(GList *l = _preview_refresh_state.module_histograms; l;)
554 {
555 GList *next = g_list_next(l);
556 dt_histogram_pending_module_refresh_t *pending = l->data;
557 if(pending->hash == hash)
558 {
559 _refresh_preview_module_histogram_for_hash(dev, pending->module, pending->hash);
561 g_free(pending);
562 }
563 l = next;
564 }
565}
566
567const char *name(struct dt_lib_module_t *self)
568{
569 return _("scopes");
570}
571
572const char **views(dt_lib_module_t *self)
573{
574 static const char *v[] = {"darkroom", NULL};
575 return v;
576}
577
579{
581}
582
584{
585 return 1;
586}
587
589{
590 return 1000;
591}
592
593static void _update_picker_output(dt_lib_module_t *self);
595static void _update_everything(dt_lib_module_t *self);
596
597
599{
600 switch(value)
601 {
602 case 0:
603 {
604 d->op = "initialscale";
605 break;
606 }
607 case 1:
608 {
609 d->op = "colorout";
610 break;
611 }
612 case 2:
613 {
614 d->op = "gamma";
615 }
616 }
617}
618
619
621{
622 if(!strcmp(d->op, "initialscale") || !strcmp(d->op, "demosaic")) return 0;
623 if(!strcmp(d->op, "colorout")) return 1;
624 if(!strcmp(d->op, "gamma")) return 2;
625 return 2;
626}
627
628
629#ifdef _OPENMP
630#pragma omp declare simd \
631 aligned(rgb_in, xyz_out:64) \
632 uniform(rgb_in, xyz_out)
633#endif
635{
636 if(_backbuf_op_to_int(d) > 0)
637 {
638 // We are in display RGB
640 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
641 profile->lutsize, profile->nonlinearlut);
642 }
643 else
644 {
645 // We are in sensor RGB
647 dt_ioppr_rgb_matrix_to_xyz(rgb_in, xyz_out, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
648 profile->lutsize, profile->nonlinearlut);
649 }
650}
651
652
653#ifdef _OPENMP
654#pragma omp declare simd \
655 aligned(rgb_in, rgb_out:64) \
656 uniform(rgb_in, rgb_out)
657#endif
659{
660 if(_backbuf_op_to_int(d) > 0)
661 {
662 // We are in display RGB
664 rgb_out[c] = rgb_in[c];
665 }
666 else
667 {
668 // We are in the sensor/input RGB profile. Convert directly between the
669 // cached stage profile and the display profile so the swatch patch uses
670 // the same authoritative ICC path as the rest of the GUI.
673 dt_ioppr_transform_image_colorspace_rgb(rgb_in, rgb_out, 1, 1, profile_in, profile_out,
674 "[histogram] sample swatch");
675 }
676}
677
679{
680 d->cache.view = DT_LIB_HISTOGRAM_SCOPE_N;
681 d->cache.width = -1;
682 d->cache.height = -1;
683 d->cache.hash = (uint64_t)-1;
684 d->cache.zoom = -1.;
685}
686
688{
689 if(IS_NULL_PTR(d) || !d->pending_hashes) return;
690 g_array_set_size(d->pending_hashes, 0);
691}
692
693static gboolean _has_pending_hash(const dt_lib_histogram_t *d, const uint64_t hash)
694{
695 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
696
697 for(guint i = 0; i < d->pending_hashes->len; i++)
698 if(g_array_index(d->pending_hashes, uint64_t, i) == hash) return TRUE;
699
700 return FALSE;
701}
702
704{
705 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID || _has_pending_hash(d, hash)) return;
706 g_array_append_val(d->pending_hashes, hash);
707}
708
710{
711 if(IS_NULL_PTR(d) || !d->pending_hashes || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
712
713 for(guint i = 0; i < d->pending_hashes->len; i++)
714 {
715 if(g_array_index(d->pending_hashes, uint64_t, i) == hash)
716 {
717 g_array_remove_index(d->pending_hashes, i);
718 return TRUE;
719 }
720 }
721
722 return FALSE;
723}
724
725static uint64_t _get_live_histogram_hash(const char *op)
726{
727 dt_develop_t *const dev = darktable.develop;
728 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
730 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read the live module
731 * hashes without the mutex and rely on the cache peek manager to handle
732 * synchronization and retries for missing cachelines. */
733 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
735
738
739 if(!strcmp(op, "gamma"))
740 piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
741
742 const uint64_t hash = piece ? piece->global_hash : DT_PIXELPIPE_CACHE_HASH_INVALID;
743 return hash;
744}
745
746static gboolean _histogram_refresh_idle(gpointer user_data)
747{
748 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
749 dt_lib_histogram_t *d = self ? self->data : NULL;
750 if(IS_NULL_PTR(d)) return G_SOURCE_REMOVE;
751
752 d->refresh_idle_source = 0;
753 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
754 _update_everything(self);
755 return G_SOURCE_REMOVE;
756}
757
759{
760 dt_lib_histogram_t *d = self ? self->data : NULL;
761 if(IS_NULL_PTR(d) || d->refresh_idle_source != 0) return;
762 d->refresh_idle_source = g_idle_add(_histogram_refresh_idle, self);
763}
764
766{
768 if(IS_NULL_PTR(d)) return;
769
772
773 for(GSList *samples = darktable.develop->color_picker.samples; samples; samples = g_slist_next(samples))
774 {
775 dt_colorpicker_sample_t *sample = samples->data;
776 if(sample->locked) continue;
778 }
779
782
783 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
784 _update_everything(self);
785}
786
787
788static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op);
789
804{
805 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->backbuf)) return FALSE;
806
807 return !IS_NULL_PTR(_get_backbuf_source_piece(d->backbuf, d->op));
808}
809
811{
812 gtk_widget_queue_draw(d->scope_draw);
813}
814
815uint32_t _find_max_histogram(const uint32_t *const restrict bins, const size_t binning_size)
816{
817 uint32_t max_hist = 0;
818 __OMP_PARALLEL_FOR_SIMD__(aligned(bins: 64) reduction(max: max_hist) )
819 for(size_t k = 0; k < binning_size; k++) if(bins[k] > max_hist) max_hist = bins[k];
820
821 return max_hist;
822}
823
824static inline void _sample_raw_box_to_image_norm(const dt_colorpicker_sample_t *const sample, float box[4])
825{
826 memcpy(box, sample->box, sizeof(float) * 4);
828}
829
830static inline void _sample_raw_point_to_image_norm(const dt_colorpicker_sample_t *const sample, float point[2])
831{
832 point[0] = sample->point[0];
833 point[1] = sample->point[1];
835}
836
837
838static inline void _bin_pixels_histogram_in_roi(const float *const restrict image, uint32_t *const restrict bins,
839 const size_t min_x, const size_t max_x,
840 const size_t min_y, const size_t max_y,
841 const size_t width)
842{
843 //fprintf(stdout, "computing histogram from x = [%lu;%lu], y = [%lu;%lu]\n", min_x, max_x, min_y, max_y);
844 // Process
845#ifdef _OPENMP
846#ifndef _WIN32
847__OMP_PARALLEL_FOR__(reduction(+: bins[0: HISTOGRAM_BINS * 4]) collapse(3))
848#else
849__OMP_PARALLEL_FOR__(shared(bins) collapse(3))
850#endif
851#endif
852 for(size_t i = min_y; i < max_y; i++)
853 for(size_t j = min_x; j < max_x; j++)
854 for(size_t c = 0; c < 3; c++)
855 {
856 const float value = image[(i * width + j) * 4 + c];
857 const size_t index = (size_t)CLAMP(roundf(value * (HISTOGRAM_BINS - 1)), 0, HISTOGRAM_BINS - 1);
858 bins[index * 4 + c]++;
859 }
860}
861
862
863static inline void _bin_pickers_histogram(const float *const restrict image,
864 const size_t width, const size_t height,
865 uint32_t *bins,
867{
868 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
869 {
870 float image_box[4] = { 0.0f };
871 _sample_raw_box_to_image_norm(sample, image_box);
872 const size_t box[4] = {
873 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
874 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
875 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
876 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
877 };
878 _bin_pixels_histogram_in_roi(image, bins, box[0], box[2], box[1], box[3], width);
879 }
880 else
881 {
882 float image_point[2] = { 0.0f };
883 _sample_raw_point_to_image_norm(sample, image_point);
884 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
885 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
886 _bin_pixels_histogram_in_roi(image, bins, x, x + 1, y, y + 1, width);
887 }
888}
889
891{
892 if(IS_NULL_PTR(d)) return FALSE;
893 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
894 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
895 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
896 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->picker_button));
897 return restrict_active && picker_active;
898}
899
900static void _process_histogram(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
901 const int height, dt_lib_histogram_t *d)
902{
903 // Histogram backbuffers already own their keepalive ref in the pipeline state. Drawing only borrows them.
904 struct dt_pixel_cache_entry_t *entry = NULL;
905 void *data = NULL;
906 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
907 if(IS_NULL_PTR(piece)) return;
908 if(!IS_NULL_PTR(d))
909 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
911 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
913 !IS_NULL_PTR(d) ? d->module : NULL))
914 {
915 return;
916 }
917
919
920 uint32_t *bins = calloc(4 * HISTOGRAM_BINS, sizeof(uint32_t));
921 if(IS_NULL_PTR(bins))
922 {
924 return;
925 }
926
927 const gboolean restrict_mode = _is_restricted(d);
928 if(restrict_mode)
929 {
930 // Bin only areas within color pickers
931 GSList *samples = darktable.develop->color_picker.samples;
932 while(samples)
933 {
934 dt_colorpicker_sample_t *sample = samples->data;
935 _bin_pickers_histogram(data, backbuf->width, backbuf->height,
936 bins, sample);
937 samples = g_slist_next(samples);
938 }
939
941 _bin_pickers_histogram(data, backbuf->width, backbuf->height,
943 }
944 else
945 {
946 _bin_pixels_histogram_in_roi(data, bins, 0, backbuf->width, 0, backbuf->height, width);
947 }
948
950
951 uint32_t overall_histogram_max = _find_max_histogram(bins, 4 * HISTOGRAM_BINS);
952
953 // Draw thingy
954 if(overall_histogram_max > 0)
955 {
956 // Paint background
957 cairo_rectangle(cr, 0, 0, width, height);
959 cairo_fill(cr);
960
962 dt_draw_grid(cr, 4, 0, 0, width, height);
963
964 cairo_save(cr);
965 cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR);
966 cairo_translate(cr, 0, height);
967 cairo_scale(cr, width / 255.0, - (double)height / (double)(1. + log(overall_histogram_max)));
968 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
969
970 for(int k = 0; k < 3; k++)
971 {
973 dt_draw_histogram_8(cr, bins, 4, k, FALSE);
974 }
975
976 cairo_pop_group_to_source(cr);
977 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
978 cairo_paint_with_alpha(cr, 0.5);
979 cairo_restore(cr);
980 }
981
982 dt_free(bins);
983}
984
985
986static inline void _bin_pixels_waveform_in_roi(const float *const restrict image, uint32_t *const restrict bins,
987 const size_t min_x, const size_t max_x,
988 const size_t min_y, const size_t max_y,
989 const size_t source_width, const size_t source_height,
990 const size_t tone_bins, const size_t raster_extent,
991 const gboolean vertical)
992{
993 if(vertical)
994 {
995 /* In vertical waveform/parade, pixel value is drawn along the widget width and source image
996 * position along the widget height. We therefore loop on final raster rows and collect the
997 * source rows that map there, so both axes have the same detail as the drawn surface. */
998 __OMP_PARALLEL_FOR__(shared(bins))
999 for(size_t raster_y = 0; raster_y < raster_extent; raster_y++)
1000 {
1001 const double source_y0d = (double)raster_y * (double)source_height / (double)raster_extent;
1002 const double source_y1d = (double)(raster_y + 1) * (double)source_height / (double)raster_extent;
1003 const size_t source_y0 = MAX(min_y, (size_t)floor(source_y0d));
1004 const size_t source_y1 = MIN(max_y, (size_t)ceil(source_y1d));
1005 if(source_y0 >= source_y1) continue;
1006
1007 for(size_t i = source_y0; i < source_y1; i++)
1008 {
1009 const double overlap = MIN((double)(i + 1), source_y1d) - MAX((double)i, source_y0d);
1010 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1011 __OMP_PARALLEL_FOR__(shared(bins) collapse(2))
1012 for(size_t j = min_x; j < max_x; j++)
1013 for(size_t c = 0; c < 3; c++)
1014 {
1015 const float value = image[(i * source_width + j) * 4 + c];
1016 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1017 const size_t tone0 = (size_t)floorf(tone_position);
1018 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1019 const float tone_mix = tone_position - (float)tone0;
1020 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1021 const uint32_t weight0 = weight - weight1;
1022 bins[(raster_y * tone_bins + tone0) * 4 + c] += weight0;
1023 bins[(raster_y * tone_bins + tone1) * 4 + c] += weight1;
1024 }
1025 }
1026 }
1027 }
1028 else
1029 {
1030 /* In horizontal waveform/parade, pixel value is drawn along the widget height and source image
1031 * position along the widget width. Each final raster column owns disjoint bins, avoiding races. */
1032 __OMP_PARALLEL_FOR__(shared(bins))
1033 for(size_t raster_x = 0; raster_x < raster_extent; raster_x++)
1034 {
1035 const double source_x0d = (double)raster_x * (double)source_width / (double)raster_extent;
1036 const double source_x1d = (double)(raster_x + 1) * (double)source_width / (double)raster_extent;
1037 const size_t source_x0 = MAX(min_x, (size_t)floor(source_x0d));
1038 const size_t source_x1 = MIN(max_x, (size_t)ceil(source_x1d));
1039 if(source_x0 >= source_x1) continue;
1040
1041 for(size_t j = source_x0; j < source_x1; j++)
1042 {
1043 const double overlap = MIN((double)(j + 1), source_x1d) - MAX((double)j, source_x0d);
1044 const uint32_t weight = MAX(1u, (uint32_t)round(overlap * 256.));
1045 __OMP_PARALLEL_FOR__(shared(bins) collapse(2))
1046 for(size_t i = min_y; i < max_y; i++)
1047 for(size_t c = 0; c < 3; c++)
1048 {
1049 const float value = image[(i * source_width + j) * 4 + c];
1050 const float tone_position = CLAMPF(value, 0.f, 1.f) * (float)(tone_bins - 1);
1051 const size_t tone0 = (size_t)floorf(tone_position);
1052 const size_t tone1 = MIN(tone0 + 1, tone_bins - 1);
1053 const float tone_mix = tone_position - (float)tone0;
1054 const uint32_t weight1 = (uint32_t)roundf((float)weight * tone_mix);
1055 const uint32_t weight0 = weight - weight1;
1056 bins[((tone_bins - 1 - tone0) * raster_extent + raster_x) * 4 + c] += weight0;
1057 bins[((tone_bins - 1 - tone1) * raster_extent + raster_x) * 4 + c] += weight1;
1058 }
1059 }
1060 }
1061 }
1062}
1063
1064static inline void _bin_pickers_waveforms(const float *const restrict image, uint32_t *const restrict bins,
1065 const size_t width, const size_t height,
1066 const size_t tone_bins, const size_t raster_extent,
1067 const gboolean vertical, dt_colorpicker_sample_t *sample)
1068{
1069 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1070 {
1071 float image_box[4] = { 0.0f };
1072 _sample_raw_box_to_image_norm(sample, image_box);
1073 const size_t box[4] = {
1074 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1075 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1076 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1077 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1078 };
1079 _bin_pixels_waveform_in_roi(image, bins, box[0], box[2], box[1], box[3],
1080 width, height, tone_bins, raster_extent, vertical);
1081 }
1082 else
1083 {
1084 float image_point[2] = { 0.0f };
1085 _sample_raw_point_to_image_norm(sample, image_point);
1086 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1087 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1088 _bin_pixels_waveform_in_roi(image, bins, x, x + 1, y, y + 1,
1089 width, height, tone_bins, raster_extent, vertical);
1090 }
1091}
1092
1093
1094static inline void _bin_pixels_waveform(const float *const restrict image, uint32_t *const restrict bins,
1095 const size_t width, const size_t height, const size_t binning_size,
1096 const size_t tone_bins, const size_t raster_extent,
1097 const gboolean vertical, const gboolean restricted)
1098{
1099 // Init
1100 __OMP_FOR_SIMD__(aligned(bins: 64) )
1101 for(size_t k = 0; k < binning_size; k++) bins[k] = 0;
1102
1103 if(restricted)
1104 {
1105 // Bin only areas within color pickers
1106 GSList *samples = darktable.develop->color_picker.samples;
1107 while(samples)
1108 {
1109 dt_colorpicker_sample_t *sample = samples->data;
1110 _bin_pickers_waveforms(image, bins, width, height, tone_bins, raster_extent, vertical, sample);
1111 samples = g_slist_next(samples);
1112 }
1113
1115 _bin_pickers_waveforms(image, bins, width, height, tone_bins, raster_extent, vertical,
1117 }
1118 else
1119 {
1120 // Bin the whole image
1121 _bin_pixels_waveform_in_roi(image, bins, 0, width, 0, height,
1122 width, height, tone_bins, raster_extent, vertical);
1123 }
1124}
1125
1126static void _create_waveform_image(const uint32_t *const restrict bins, uint8_t *const restrict image,
1127 const uint32_t max_hist,
1128 const size_t width, const size_t height)
1129{
1130 __OMP_FOR_SIMD__(aligned(image, bins: 64) )
1131 for(size_t k = 0; k < height * width * 4; k += 4)
1132 {
1133 image[k + 3] = 255; // alpha
1134
1135 // We apply a slight "gamma" boost for legibility
1136 image[k + 2] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 0] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1137 image[k + 1] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 1] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1138 image[k + 0] = (uint8_t)CLAMP(roundf(powf((float)bins[k + 2] / (float)max_hist, GAMMA) * 255.f), 0, 255);
1139 }
1140}
1141
1142static void _paint_waveform(cairo_t *cr, uint8_t *const restrict image, const int width, const int height,
1143 const size_t img_width, const size_t img_height, const size_t tone_bins,
1144 const gboolean vertical)
1145{
1146 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1147 cairo_surface_t *background = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_ARGB32, img_width, img_height, stride);
1148 const double scale_w = (vertical) ? (double)width / (double)tone_bins
1149 : (double)width / (double)img_width;
1150 const double scale_h = (vertical) ? (double)height / (double)img_height
1151 : (double)height / (double)tone_bins;
1152 cairo_scale(cr, scale_w, scale_h);
1153 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
1154 cairo_set_source_surface(cr, background, 0., 0.);
1155 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1156 cairo_paint(cr);
1157 cairo_surface_destroy(background);
1158}
1159
1160
1161static void _process_waveform(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1162 const int height, const gboolean vertical, const gboolean parade,
1164{
1165 struct dt_pixel_cache_entry_t *entry = NULL;
1166 void *data = NULL;
1167 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1168 if(IS_NULL_PTR(piece)) return;
1169 if(!IS_NULL_PTR(d))
1170 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1172 !IS_NULL_PTR(d) ? &d->scope_wait : NULL,
1174 !IS_NULL_PTR(d) ? d->module : NULL))
1175 {
1176 return;
1177 }
1178
1180
1181 /* The value axis must match the final drawing axis: widget height for horizontal scopes and
1182 * widget width for vertical scopes. Keeping it fixed forces Cairo to stretch sparse
1183 * value rows/columns, which shows as hatching when the scope is resized. */
1184 const size_t tone_bins = MAX(2, (vertical) ? width : height);
1185 /* The source-position axis must also match the final raster size. Otherwise Cairo stretches the
1186 * preview-pipe dimensions to the scope allocation, which can leave visible regular stripes. */
1187 const size_t raster_extent = MAX(2, (vertical)
1188 ? (parade ? (height + 2) / 3 : height)
1189 : (parade ? (width + 2) / 3 : width));
1190 const size_t binning_size = 4 * tone_bins * raster_extent;
1191 const size_t img_width = (parade) ? width : ((vertical) ? tone_bins : raster_extent);
1192 const size_t img_height = (parade) ? height : ((vertical) ? raster_extent : tone_bins);
1193 const size_t image_size = 4 * img_width * img_height;
1194 const size_t source_axis = (vertical) ? backbuf->height : backbuf->width;
1195 float source_min = INFINITY;
1196 float source_max = -INFINITY;
1197 size_t source_clipped = 0;
1198 size_t source_nan = 0;
1199 const size_t source_step = MAX((size_t)1, (backbuf->width * backbuf->height) / 4096);
1200 for(size_t pixel = 0; pixel < backbuf->width * backbuf->height; pixel += source_step)
1201 for(size_t c = 0; c < 3; c++)
1202 {
1203 const float value = ((const float *)data)[pixel * 4 + c];
1204 if(isnan(value))
1205 source_nan++;
1206 else
1207 {
1208 source_min = fminf(source_min, value);
1209 source_max = fmaxf(source_max, value);
1210 source_clipped += (value < 0.f || value > 1.f);
1211 }
1212 }
1213 uint32_t *smooth_bins = NULL;
1214
1217 "[histogram/scope] waveform setup op=%s parade=%d vertical=%d widget=%dx%d backbuf=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT " "
1218 "tone_bins=%" G_GSIZE_FORMAT " raster_extent=%" G_GSIZE_FORMAT " source_axis=%" G_GSIZE_FORMAT
1219 " source_per_raster=%.4f binning_size=%" G_GSIZE_FORMAT " "
1220 "source_value=%0.6f..%0.6f clipped=%" G_GSIZE_FORMAT " nan=%" G_GSIZE_FORMAT " sample_step=%" G_GSIZE_FORMAT "\n",
1221 op, parade, vertical, width, height, backbuf->width, backbuf->height,
1222 tone_bins, raster_extent, source_axis, (double)source_axis / (double)raster_extent,
1223 binning_size, source_min, source_max, source_clipped, source_nan, source_step);
1224 uint32_t *const restrict bins = dt_pixelpipe_cache_alloc_align_cache(
1225 binning_size * sizeof(uint32_t),
1226 0);
1227 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1228 image_size * sizeof(uint8_t),
1229 0);
1230 if(IS_NULL_PTR(image) || IS_NULL_PTR(bins))
1231 {
1234 "[histogram/scope] waveform allocation failed bins=%p image=%p binning_size=%" G_GSIZE_FORMAT
1235 " image_size=%" G_GSIZE_FORMAT "\n",
1236 (void *)bins, (void *)image, binning_size, image_size);
1237 goto error;
1238 }
1239
1240 // 1. Pixel binning along columns/rows, aka compute a column/row-wise histogram
1241 _bin_pixels_waveform(data, bins, backbuf->width, backbuf->height, binning_size,
1242 tone_bins, raster_extent, vertical, _is_restricted(d));
1243
1244 const uint32_t *render_bins = bins;
1245 int smoothing_passes = 0;
1246#if DT_LIB_HISTOGRAM_SCOPE_ENABLE_SMOOTHING \
1247 && (DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES > 0 \
1248 || DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES > 0)
1249 smooth_bins = dt_pixelpipe_cache_alloc_align_cache(binning_size * sizeof(uint32_t), 0);
1250 const uint32_t smoothing_force = CLAMP(DT_LIB_HISTOGRAM_SCOPE_SMOOTH_FORCE, 0, 256);
1251 if(!IS_NULL_PTR(smooth_bins) && smoothing_force > 0)
1252 {
1253 /* The waveform/parade is a spatially-indexed histogram. Even after fractional resampling,
1254 * isolated source-column content can create one-pixel density modulation that reads as stries.
1255 * The passes below are explicit at call site because the source/destination ownership matters:
1256 * bins and smooth_bins alternate roles, and render_bins always records the last valid raster. */
1257 const uint32_t original_force = 256u - smoothing_force;
1258
1259 for(int pass = 0; pass < DT_LIB_HISTOGRAM_SCOPE_SMOOTH_SPATIAL_PASSES; pass++)
1260 {
1261 const uint32_t *const source_bins = render_bins;
1262 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1263
1264 if(vertical)
1265 {
1266 for(size_t axis = 0; axis < raster_extent; axis++)
1267 {
1268 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1269 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1270 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1271 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1272 for(size_t tone = 0; tone < tone_bins; tone++)
1273 for(size_t c = 0; c < 4; c++)
1274 {
1275 const size_t index = (axis * tone_bins + tone) * 4 + c;
1276 const uint64_t smoothed = source_bins[(axis_m2 * tone_bins + tone) * 4 + c]
1277 + 4u * source_bins[(axis_m1 * tone_bins + tone) * 4 + c]
1278 + 6u * source_bins[index]
1279 + 4u * source_bins[(axis_p1 * tone_bins + tone) * 4 + c]
1280 + source_bins[(axis_p2 * tone_bins + tone) * 4 + c];
1281 const uint64_t blended = original_force * source_bins[index]
1282 + smoothing_force * ((smoothed + 8u) / 16u);
1283 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1284 }
1285 }
1286 }
1287 else
1288 {
1289 for(size_t tone = 0; tone < tone_bins; tone++)
1290 for(size_t axis = 0; axis < raster_extent; axis++)
1291 {
1292 const size_t axis_m2 = (axis > 1) ? axis - 2 : 0;
1293 const size_t axis_m1 = (axis > 0) ? axis - 1 : 0;
1294 const size_t axis_p1 = MIN(axis + 1, raster_extent - 1);
1295 const size_t axis_p2 = MIN(axis + 2, raster_extent - 1);
1296 for(size_t c = 0; c < 4; c++)
1297 {
1298 const size_t index = (tone * raster_extent + axis) * 4 + c;
1299 const uint64_t smoothed = source_bins[(tone * raster_extent + axis_m2) * 4 + c]
1300 + 4u * source_bins[(tone * raster_extent + axis_m1) * 4 + c]
1301 + 6u * source_bins[index]
1302 + 4u * source_bins[(tone * raster_extent + axis_p1) * 4 + c]
1303 + source_bins[(tone * raster_extent + axis_p2) * 4 + c];
1304 const uint64_t blended = original_force * source_bins[index]
1305 + smoothing_force * ((smoothed + 8u) / 16u);
1306 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1307 }
1308 }
1309 }
1310
1311 render_bins = target_bins;
1312 smoothing_passes++;
1313 }
1314
1315 for(int pass = 0; pass < DT_LIB_HISTOGRAM_SCOPE_SMOOTH_TONE_PASSES; pass++)
1316 {
1317 const uint32_t *const source_bins = render_bins;
1318 uint32_t *const target_bins = (source_bins == bins) ? smooth_bins : bins;
1319
1320 if(vertical)
1321 {
1322 for(size_t axis = 0; axis < raster_extent; axis++)
1323 for(size_t tone = 0; tone < tone_bins; tone++)
1324 {
1325 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1326 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1327 for(size_t c = 0; c < 4; c++)
1328 {
1329 const size_t index = (axis * tone_bins + tone) * 4 + c;
1330 const uint64_t smoothed = source_bins[(axis * tone_bins + tone_m1) * 4 + c]
1331 + 2u * source_bins[index]
1332 + source_bins[(axis * tone_bins + tone_p1) * 4 + c];
1333 const uint64_t blended = original_force * source_bins[index]
1334 + smoothing_force * ((smoothed + 2u) / 4u);
1335 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1336 }
1337 }
1338 }
1339 else
1340 {
1341 for(size_t tone = 0; tone < tone_bins; tone++)
1342 {
1343 const size_t tone_m1 = (tone > 0) ? tone - 1 : 0;
1344 const size_t tone_p1 = MIN(tone + 1, tone_bins - 1);
1345 for(size_t axis = 0; axis < raster_extent; axis++)
1346 for(size_t c = 0; c < 4; c++)
1347 {
1348 const size_t index = (tone * raster_extent + axis) * 4 + c;
1349 const uint64_t smoothed = source_bins[(tone_m1 * raster_extent + axis) * 4 + c]
1350 + 2u * source_bins[index]
1351 + source_bins[(tone_p1 * raster_extent + axis) * 4 + c];
1352 const uint64_t blended = original_force * source_bins[index]
1353 + smoothing_force * ((smoothed + 2u) / 4u);
1354 target_bins[index] = (uint32_t)((blended + 128u) / 256u);
1355 }
1356 }
1357 }
1358
1359 render_bins = target_bins;
1360 smoothing_passes++;
1361 }
1362 }
1363#endif
1364
1365 // 2. Paint image.
1366 // In a 1D histogram, pixel frequencies are shown as height (y axis) for each RGB quantum (x axis).
1367 // Here, we do a sort of 2D histogram : pixel frequencies are shown as opacity ("z" axis),
1368 // for each image column (x axis), for each RGB quantum (y axis)
1369 const uint32_t overall_max_hist = _find_max_histogram(render_bins, binning_size);
1370 size_t empty_axis = 0;
1371 uint64_t min_axis = UINT64_MAX;
1372 uint64_t max_axis = 0;
1373 uint32_t min_axis_peak = UINT32_MAX;
1374 uint32_t max_axis_peak = 0;
1375 for(size_t axis = 0; axis < raster_extent; axis++)
1376 {
1377 uint64_t axis_total = 0;
1378 uint32_t axis_peak = 0;
1379 for(size_t tone = 0; tone < tone_bins; tone++)
1380 {
1381 const size_t pixel = (vertical) ? axis * tone_bins + tone : tone * raster_extent + axis;
1382 for(size_t c = 0; c < 3; c++)
1383 {
1384 const uint32_t value = render_bins[pixel * 4 + c];
1385 axis_total += value;
1386 axis_peak = MAX(axis_peak, value);
1387 }
1388 }
1389
1390 if(axis_total == 0) empty_axis++;
1391 min_axis = MIN(min_axis, axis_total);
1392 max_axis = MAX(max_axis, axis_total);
1393 min_axis_peak = MIN(min_axis_peak, axis_peak);
1394 max_axis_peak = MAX(max_axis_peak, axis_peak);
1395 }
1396 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img_width);
1398 "[histogram/scope] waveform raster op=%s parade=%d vertical=%d image=%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT
1399 " stride=%" G_GSIZE_FORMAT " overall_max=%u empty_axis=%" G_GSIZE_FORMAT " axis_total=%" PRIu64 "..%" PRIu64
1400 " axis_peak=%u..%u smooth=%d scale=%0.4fx%0.4f\n",
1401 op, parade, vertical, img_width, img_height, stride, overall_max_hist,
1402 empty_axis, min_axis, max_axis, min_axis_peak, max_axis_peak, smoothing_passes,
1403 (double)width / (double)img_width, (double)height / (double)img_height);
1404
1405 // 3. Send everything to GUI buffer.
1406 if(overall_max_hist > 0)
1407 {
1408 if(parade)
1409 {
1410 /* Build the parade directly in the final widget raster. This keeps channel thirds on integer
1411 * device pixels and removes all Cairo source-offset/scaling interactions from the diagnostic
1412 * path: if stries remain, they are in the histogram bins, not in channel painting. */
1413 for(size_t k = 0; k < image_size; k++) image[k] = 0;
1414
1415 for(size_t c = 0; c < 3; c++)
1416 {
1417 const size_t x0 = vertical ? 0 : c * width / 3;
1418 const size_t x1 = vertical ? width : (c + 1) * width / 3;
1419 const size_t y0 = vertical ? c * height / 3 : 0;
1420 const size_t y1 = vertical ? (c + 1) * height / 3 : height;
1421 const size_t channel_width = x1 - x0;
1422 const size_t channel_height = y1 - y0;
1423 if(channel_width == 0 || channel_height == 0) continue;
1424
1425 for(size_t y = y0; y < y1; y++)
1426 for(size_t x = x0; x < x1; x++)
1427 {
1428 const size_t axis = vertical
1429 ? (y - y0) * raster_extent / channel_height
1430 : (x - x0) * raster_extent / channel_width;
1431 const size_t tone = vertical ? x : y;
1432 const size_t bin_index = vertical ? (axis * tone_bins + tone) * 4 + c
1433 : (tone * raster_extent + axis) * 4 + c;
1434 const uint8_t value = (uint8_t)CLAMP(roundf(powf((float)render_bins[bin_index] / (float)overall_max_hist, GAMMA) * 255.f), 0, 255);
1435 const size_t image_index = (y * img_width + x) * 4;
1436 image[image_index + 2 - c] = value;
1437 image[image_index + 3] = 255;
1438 }
1439 }
1440 }
1441 else
1442 _create_waveform_image(render_bins, image, overall_max_hist, img_width, img_height);
1443
1444 cairo_save(cr);
1445
1446 // Paint background - Color not exposed to user theme because this is tricky
1447 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1448 cairo_paint(cr);
1449
1463 cairo_set_source_rgb(cr, 0.21, 0.21, 0.21);
1464 const gboolean raw_stage = (!strcmp(op, "initialscale") || !strcmp(op, "demosaic"));
1465 if(raw_stage)
1466 {
1467 /* The regular 4x4 grid has a tonal guide at 0.75, which is not an integer raw bit stop.
1468 * In raw mode, non-parade scopes keep only the global spatial guides here; the tone guides
1469 * are drawn below from powers of two. Parade intentionally has no spatial guides. */
1470 for(int k = 1; !parade && k < 4; k++)
1471 {
1472 if(vertical)
1473 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1474 else
1475 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1476 cairo_stroke(cr);
1477 }
1478 }
1479 else if(parade)
1480 {
1481 /* Parade already splits the spatial axis into RGB strips. Keep only the regular tone-axis
1482 * guides here so the grid does not add misleading source-position lines inside each strip. */
1483 for(int k = 1; k < 4; k++)
1484 {
1485 if(vertical)
1486 dt_draw_line(cr, (double)k * (double)width / 4.0, 0, (double)k * (double)width / 4.0, height);
1487 else
1488 dt_draw_line(cr, 0, (double)k * (double)height / 4.0, width, (double)k * (double)height / 4.0);
1489 cairo_stroke(cr);
1490 }
1491 }
1492 else
1493 dt_draw_grid(cr, 4, 0, 0, width, height);
1494
1495 if(raw_stage)
1496 {
1497 /* Raw data is linear, so integer bit stops are powers of two in normalized tone space.
1498 * Drawing those stops on the tone axis makes exposure clipping and low-bit detail readable
1499 * without depending on the current scope height. */
1500 for(int stop = 0; stop <= 8; stop++)
1501 {
1502 const double value = 1.0 / (double)(1u << stop);
1503 if(value < (double)DT_LIB_HISTOGRAM_SCOPE_MIN_VALUE) break;
1504
1505 if(vertical)
1506 {
1507 const double x = value * (double)width;
1508 dt_draw_line(cr, x, 0, x, height);
1509 }
1510 else
1511 {
1512 const double y = (1.0 - value) * (double)height;
1513 dt_draw_line(cr, 0, y, width, y);
1514 }
1515 cairo_stroke(cr);
1516 }
1517 }
1518
1519 _paint_waveform(cr, image, width, height, img_width, img_height, tone_bins, vertical);
1520
1521 cairo_restore(cr);
1522 }
1523
1524error:;
1525 dt_pixelpipe_cache_free_align(smooth_bins);
1529}
1530
1531static float _Luv_to_vectorscope_coord_zoom(const float value, const float zoom)
1532{
1533 // Convert u, v coordinates of Luv vectors into x, y coordinates
1534 // into the space of the vectorscope square buffer
1535 return (value + zoom) * (HISTOGRAM_BINS - 1) / (2.f * zoom);
1536}
1537
1538static float _vectorscope_coord_zoom_to_Luv(const float value, const float zoom)
1539{
1540 // Inverse of the above
1541 return value * (2.f * zoom) / (HISTOGRAM_BINS - 1) - zoom;
1542}
1543
1544static void _bin_pixels_vectorscope_in_roi(const float *const restrict image, uint32_t *const restrict vectorscope,
1545 const size_t min_x, const size_t max_x, const size_t min_y,
1546 const size_t max_y, const size_t width, const float zoom,
1548{
1549#ifdef _OPENMP
1550#ifndef _WIN32
1551#pragma omp parallel for default(firstprivate) \
1552 reduction(+: vectorscope[0: HISTOGRAM_BINS * HISTOGRAM_BINS])
1553#else
1554#pragma omp parallel for default(firstprivate) \
1555 shared(vectorscope)
1556#endif
1557#endif
1558 for(size_t i = min_y; i < max_y; i++)
1559 for(size_t j = min_x; j < max_x; j++)
1560 {
1561 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1562 dt_aligned_pixel_t xyY = { 0.f };
1563 dt_aligned_pixel_t Luv = { 0.f };
1564 _scope_pixel_to_xyz(image + (i * width + j) * 4, XYZ_D50, d);
1567
1568 // Luv is sampled between 0 and 100.0f, u and v between +/- 220.f
1569 const size_t U_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[1], zoom)), 0, HISTOGRAM_BINS - 1);
1570 const size_t V_index = (size_t)CLAMP(roundf(_Luv_to_vectorscope_coord_zoom(Luv[2], zoom)), 0, HISTOGRAM_BINS - 1);
1571
1572 // We put V = 0 at the bottom of the image.
1573 vectorscope[(HISTOGRAM_BINS - 1 - V_index) * HISTOGRAM_BINS + U_index]++;
1574 }
1575}
1576
1577static inline void _bin_pickers_vectorscope(const float *const restrict image,
1578 uint32_t *const restrict vectorscope, const size_t width,
1579 const size_t height, const float zoom, dt_lib_histogram_t *d,
1580 const dt_colorpicker_sample_t *const sample)
1581{
1582 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1583 {
1584 float image_box[4] = { 0.0f };
1585 _sample_raw_box_to_image_norm(sample, image_box);
1586 const size_t box[4] = {
1587 CLAMP((size_t)roundf(image_box[0] * width), 0, width),
1588 CLAMP((size_t)roundf(image_box[1] * height), 0, height),
1589 CLAMP((size_t)roundf(image_box[2] * width), 0, width),
1590 CLAMP((size_t)roundf(image_box[3] * height), 0, height)
1591 };
1592 _bin_pixels_vectorscope_in_roi(image, vectorscope, box[0], box[2], box[1], box[3], width, zoom, d);
1593 }
1594 else
1595 {
1596 float image_point[2] = { 0.0f };
1597 _sample_raw_point_to_image_norm(sample, image_point);
1598 const size_t x = CLAMP((size_t)roundf(image_point[0] * width), 0, width - 1);
1599 const size_t y = CLAMP((size_t)roundf(image_point[1] * height), 0, height - 1);
1600 _bin_pixels_vectorscope_in_roi(image, vectorscope, x, x + 1, y, y + 1, width, zoom, d);
1601 }
1602}
1603
1604
1605static void _create_vectorscope_image(const uint32_t *const restrict vectorscope, uint8_t *const restrict image,
1606 const uint32_t max_hist, const float zoom)
1607{
1609 __OMP_PARALLEL_FOR__(collapse(2))
1610 for(size_t i = 0; i < HISTOGRAM_BINS; i++)
1611 for(size_t j = 0; j < HISTOGRAM_BINS; j++)
1612 {
1613 const size_t index = (HISTOGRAM_BINS - 1 - i) * HISTOGRAM_BINS + j;
1614 const float value = sqrtf((float)vectorscope[index] / (float)max_hist);
1615 dt_aligned_pixel_t RGB = { 0.f };
1616 // RGB gamuts tend to have a max chroma around L = 67
1618 dt_aligned_pixel_t xyY = { 0.f };
1619 dt_aligned_pixel_t XYZ = { 0.f };
1621 for(int c = 0; c < 2; c++) xyY[c] = fmaxf(xyY[c], 0.f);
1623 for(int c = 0; c < 3; c++) XYZ[c] = fmaxf(XYZ[c], 0.f);
1625
1626 // We will normalize RGB to get closer to display peak emission
1627 for(int c = 0; c < 3; c++) RGB[c] = fmaxf(RGB[c], 0.f);
1628 const float max_RGB = fmax(RGB[0], fmaxf(RGB[1], RGB[2]));
1629 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1630
1631 image[index * 4 + 3] = (uint8_t)roundf(value * 255.f); // alpha
1632 // Premultiply alpha
1633 image[index * 4 + 2] = (uint8_t)roundf(powf(RGB[0] * value, 1.f / 2.2f) * 255.f);
1634 image[index * 4 + 1] = (uint8_t)roundf(powf(RGB[1] * value, 1.f / 2.2f) * 255.f);
1635 image[index * 4 + 0] = (uint8_t)roundf(powf(RGB[2] * value, 1.f / 2.2f) * 255.f);
1636 }
1637}
1638
1639static void _bin_vectorscope(const float *const restrict image, uint32_t *const vectorscope,
1640 const size_t width, const size_t height,
1641 const float zoom, dt_lib_histogram_t *d)
1642{
1643 __OMP_FOR_SIMD__(aligned(vectorscope: 64) )
1644 for(size_t k = 0; k < HISTOGRAM_BINS * HISTOGRAM_BINS; k++) vectorscope[k] = 0;
1645
1646 if(_is_restricted(d))
1647 {
1648 // Bin only areas within color pickers
1649 GSList *samples = darktable.develop->color_picker.samples;
1650 while(samples)
1651 {
1652 dt_colorpicker_sample_t *sample = samples->data;
1653 _bin_pickers_vectorscope(image, vectorscope, width, height, zoom, d, sample);
1654 samples = g_slist_next(samples);
1655 }
1656
1658 _bin_pickers_vectorscope(image, vectorscope, width, height, zoom, d,
1660 }
1661 else
1662 {
1663 // Bin the whole image
1664 _bin_pixels_vectorscope_in_roi(image, vectorscope, 0, width, 0, height, width, zoom, d);
1665 }
1666}
1667
1668
1669static void _process_vectorscope(dt_backbuf_t *backbuf, const char *op, cairo_t *cr, const int width,
1670 const int height, const float zoom, dt_lib_histogram_t *d)
1671{
1673 if(IS_NULL_PTR(profile) || IS_NULL_PTR(d)) return;
1674
1675 struct dt_pixel_cache_entry_t *entry = NULL;
1676 void *data = NULL;
1677 const dt_dev_pixelpipe_iop_t *const piece = _get_backbuf_source_piece(backbuf, op);
1678 if(IS_NULL_PTR(piece)) return;
1679 dt_dev_pixelpipe_cache_wait_set_owner(&d->scope_wait, "histogram-scope", d->module);
1680 if(!dt_dev_pixelpipe_cache_peek_gui(darktable.develop->preview_pipe, piece, &data, &entry, &d->scope_wait,
1682 {
1683 return;
1684 }
1685
1687
1688 uint32_t *const restrict vectorscope = dt_pixelpipe_cache_alloc_align_cache(
1689 HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint32_t),
1690 0);
1691 uint8_t *const restrict image = dt_pixelpipe_cache_alloc_align_cache(
1692 4 * HISTOGRAM_BINS * HISTOGRAM_BINS * sizeof(uint8_t),
1693 0);
1694 if(IS_NULL_PTR(vectorscope) || IS_NULL_PTR(image)) goto error;
1695
1696 _bin_vectorscope(data, vectorscope, backbuf->width, backbuf->height, zoom, d);
1697
1698 const uint32_t max_hist = _find_max_histogram(vectorscope, HISTOGRAM_BINS * HISTOGRAM_BINS);
1699 _create_vectorscope_image(vectorscope, image, max_hist, zoom);
1700
1701 // 2. Draw
1702 if(max_hist > 0)
1703 {
1704 const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, HISTOGRAM_BINS);
1705 cairo_surface_t *background = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_ARGB32, HISTOGRAM_BINS, HISTOGRAM_BINS, stride);
1706 const int side = MIN(width, height);
1707 cairo_translate(cr, (double)(width - side) / 2., (double)(height - side) / 2.);
1708 cairo_scale(cr, (double)side / HISTOGRAM_BINS, (double)side / HISTOGRAM_BINS);
1709
1710 const double radius = (float)(HISTOGRAM_BINS - 1) / 2 - DT_PIXEL_APPLY_DPI(1.);
1711 const double x_center = (float)(HISTOGRAM_BINS - 1) / 2;
1712
1713 // Background circle - Color will not be exposed to user theme because this is tricky
1714 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1715 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1716 cairo_fill(cr);
1717
1718 // Center circle
1719 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1720 cairo_arc(cr, x_center, x_center, 2., 0., 2. * M_PI);
1721 cairo_fill(cr);
1722
1723 // Concentric circles
1724 for(int k = 0; k < 4; k++)
1725 {
1726 cairo_arc(cr, x_center, x_center, (double)k * HISTOGRAM_BINS / 8., 0., 2. * M_PI);
1727 cairo_stroke(cr);
1728 }
1729
1730 // RGB space primaries and secondaries
1731 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 },
1732 { 0.f, 1.f, 1.f, 0.f }, { 0.f, 0.f, 1.f, 0.f }, { 1.f, 0.f, 1.f, 0.f } };
1733
1734 cairo_save(cr);
1735 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1736 cairo_clip(cr);
1737
1738 for(size_t k = 0; k < 6; k++)
1739 {
1740 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1741 dt_aligned_pixel_t xyY = { 0.f };
1742 dt_aligned_pixel_t Luv = { 0.f };
1743 dt_ioppr_rgb_matrix_to_xyz(colors[k], XYZ_D50, profile->matrix_in_transposed, profile->lut_in, profile->unbounded_coeffs_in,
1744 profile->lutsize, profile->nonlinearlut);
1747
1748 const double x = _Luv_to_vectorscope_coord_zoom(Luv[1], zoom);
1749 // Remember v = 0 is at the bottom of the square while Cairo puts y = 0 on top
1750 const double y = HISTOGRAM_BINS - 1 - _Luv_to_vectorscope_coord_zoom(Luv[2], zoom);
1751
1752 // First, draw hue angles
1753 dt_aligned_pixel_t Lch = { 0.f };
1755
1756 const double delta_x = radius * cosf(Lch[2]);
1757 const double delta_y = radius * sinf(Lch[2]);
1758 const double destination_x = x_center + delta_x;
1759 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1760 cairo_move_to(cr, x_center, x_center);
1761 cairo_line_to(cr, destination_x, destination_y);
1762 cairo_set_source_rgba(cr, colors[k][0], colors[k][1], colors[k][2], 0.5);
1763 cairo_stroke(cr);
1764
1765 // Then draw color squares and ensure center is filled with scope background color
1766 const double half_square = DT_PIXEL_APPLY_DPI(4);
1767 cairo_arc(cr, x, y, half_square, 0, 2. * M_PI);
1768 cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
1769 cairo_fill_preserve(cr);
1770 cairo_set_source_rgb(cr, colors[k][0], colors[k][1], colors[k][2]);
1771 cairo_stroke(cr);
1772 }
1773 cairo_restore(cr);
1774
1775 // Hues ring
1776 cairo_save(cr);
1777 cairo_arc(cr, x_center, x_center, radius - DT_PIXEL_APPLY_DPI(1.), 0., 2. * M_PI);
1778 cairo_set_source_rgb(cr, 0.33, 0.33, 0.33);
1779 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1780 cairo_stroke(cr);
1781 cairo_restore(cr);
1782
1783 for(size_t h = 0; h < 180; h++)
1784 {
1785 dt_aligned_pixel_t Lch = { 50.f, 110.f, h / 180.f * 2.f * M_PI_F, 1.f };
1786 dt_aligned_pixel_t Luv = { 0.f };
1787 dt_aligned_pixel_t xyY = { 0.f };
1788 dt_aligned_pixel_t XYZ = { 0.f };
1789 dt_aligned_pixel_t RGB = { 0.f };
1794 const float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
1795 for(int c = 0; c < 3; c++) RGB[c] /= max_RGB;
1796 const double delta_x = (radius - DT_PIXEL_APPLY_DPI(1.)) * cosf(Lch[2]);
1797 const double delta_y = (radius - DT_PIXEL_APPLY_DPI(1.)) * sinf(Lch[2]);
1798 const double destination_x = x_center + delta_x;
1799 const double destination_y = (HISTOGRAM_BINS - 1) - (x_center + delta_y);
1800 cairo_set_source_rgba(cr, RGB[0], RGB[1], RGB[2], 0.7);
1801 cairo_arc(cr, destination_x, destination_y, DT_PIXEL_APPLY_DPI(1.), 0, 2. * M_PI);
1802 cairo_fill(cr);
1803 }
1804
1805 // Actual vectorscope
1806 cairo_arc(cr, x_center, x_center, radius, 0., 2. * M_PI);
1807 cairo_clip(cr);
1808 cairo_set_source_surface(cr, background, 0., 0.);
1809 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1810 cairo_paint(cr);
1811 cairo_surface_destroy(background);
1812
1813 // Draw the skin tones area
1814 // Values obtained with :
1815 // get_skin_tones_range();
1816 const float max_c = 49.34f;
1817 const float min_c = 9.00f;
1818 const float max_h = 0.99f;
1819 const float min_h = 0.26f;
1820
1821 const float n_w_x = min_c * cosf(max_h);
1822 const float n_w_y = min_c * sinf(max_h);
1823 const float n_e_x = max_c * cosf(max_h);
1824 const float n_e_y = max_c * sinf(max_h);
1825 const float s_e_x = max_c * cosf(min_h);
1826 const float s_e_y = max_c * sinf(min_h);
1827 const float s_w_x = min_c * cosf(min_h);
1828 const float s_w_y = min_c * sinf(min_h);
1829 cairo_move_to(cr, _Luv_to_vectorscope_coord_zoom(n_w_x, zoom),
1830 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(n_w_y, zoom));
1831 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(n_e_x, zoom),
1832 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(n_e_y, zoom));
1833 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(s_e_x, zoom),
1834 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(s_e_y, zoom));
1835 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(s_w_x, zoom),
1836 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(s_w_y, zoom));
1837 cairo_line_to(cr, _Luv_to_vectorscope_coord_zoom(n_w_x, zoom),
1838 (HISTOGRAM_BINS - 1) - _Luv_to_vectorscope_coord_zoom(n_w_y, zoom));
1839 cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
1840 cairo_stroke(cr);
1841 }
1842
1843error:;
1845 dt_pixelpipe_cache_free_align(vectorscope);
1847}
1848
1849gboolean _needs_recompute(dt_lib_histogram_t *d, const int width, const int height)
1850{
1851 // Check if cache is up-to-date
1853 gboolean hash_match = (d->cache.hash == dt_dev_backbuf_get_hash(d->backbuf));
1854 gboolean size_match = (d->cache.width == width && d->cache.height == height);
1855 gboolean zoom_match = (d->cache.zoom == d->zoom);
1856 gboolean view_match = (d->cache.view == view);
1857 gboolean has_surface = (!IS_NULL_PTR(d->cst));
1858 return !(hash_match && size_match && zoom_match && view_match && has_surface);
1859}
1860
1861
1862static gboolean _draw_callback(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1863{
1864 // Note: the draw callback is called from our own callback (mapped to "preview pipe finished recomputing" signal)
1865 // but is also called by Gtk when the main window is resized, exposed, etc.
1866 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
1867 if(IS_NULL_PTR(d->cst)) return 1;
1868 cairo_set_source_surface(crf, d->cst, 0, 0);
1869 cairo_paint(crf);
1870 return 0;
1871}
1872
1873
1875{
1876 GtkAllocation allocation;
1877 gtk_widget_get_allocation(d->scope_draw, &allocation);
1878 *width = allocation.width;
1879 *height = allocation.height;
1880}
1881
1905static void _process_restricted_text(dt_lib_histogram_t *d, cairo_t *cr, const int height)
1906{
1907 if(_is_restricted(d))
1908 {
1909 cairo_save(cr);
1911
1912 PangoLayout *layout = pango_cairo_create_layout(cr);
1913 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1914 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(12.) * PANGO_SCALE);
1915 pango_layout_set_font_description(layout, desc);
1916 pango_layout_set_text(layout, _("Restricted"), -1);
1917
1918 PangoRectangle ink;
1919 PangoRectangle logical;
1920 pango_layout_get_pixel_extents(layout, &ink, &logical);
1921
1922 const double padding = DT_PIXEL_APPLY_DPI(2.);
1923 const double text_padding = DT_PIXEL_APPLY_DPI(1.5);
1924 const double label_x = padding;
1925 const double label_y = MAX(padding, height - logical.height - 2. * text_padding - padding);
1926
1928 cairo_move_to(cr, label_x + text_padding - ink.x, label_y + text_padding);
1929 pango_cairo_show_layout(cr, layout);
1930
1931 pango_font_description_free(desc);
1932 g_object_unref(layout);
1933 cairo_restore(cr);
1934 }
1935}
1936
1938{
1939 if(IS_NULL_PTR(d->cst)) return 1;
1940
1941 dt_times_t start;
1942 dt_get_times(&start);
1943
1944 int width, height;
1946
1947 // Save cache integrity
1948 d->cache.hash = dt_dev_backbuf_get_hash(d->backbuf);
1949 d->cache.width = width;
1950 d->cache.height = height;
1951 d->cache.zoom = d->zoom;
1952 d->cache.view = d->scope;
1953
1954 cairo_t *cr = cairo_create(d->cst);
1955
1956 // Paint background
1957 gtk_render_background(gtk_widget_get_style_context(d->scope_draw), cr, 0, 0, width, height);
1958 cairo_set_line_width(cr, 1.); // we want exactly 1 px no matter the resolution
1959
1960 // Paint content
1961 switch(d->scope)
1962 {
1964 {
1965 _process_histogram(d->backbuf, d->op, cr, width, height, d);
1966 break;
1967 }
1969 {
1970 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, FALSE, d);
1971 break;
1972 }
1974 {
1975 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, FALSE, d);
1976 break;
1977 }
1979 {
1980 _process_waveform(d->backbuf, d->op, cr, width, height, FALSE, TRUE, d);
1981 break;
1982 }
1984 {
1985 _process_waveform(d->backbuf, d->op, cr, width, height, TRUE, TRUE, d);
1986 break;
1987 }
1989 {
1990 _process_vectorscope(d->backbuf, d->op, cr, width, height, d->zoom, d);
1991 break;
1992 }
1993 default:
1994 break;
1995 }
1996
1998
1999 cairo_destroy(cr);
2000 dt_show_times_f(&start, "[histogram]", "redraw");
2001 return 0;
2002}
2003
2005{
2006 if(d->cst && cairo_surface_get_reference_count(d->cst) > 0) cairo_surface_destroy(d->cst);
2007 if(d->cst && cairo_surface_get_reference_count(d->cst) == 0) d->cst = NULL;
2008}
2009
2011{
2012 int width, height;
2014
2016 {
2018 // If width and height have changed, we need to recreate the surface.
2019 // Recreate it anyway.
2020 d->cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
2022 // Don't send gtk_queue_redraw event from here, catch the return value and do it in the calling function
2023 //fprintf(stdout, "recreate histograms\n");
2024 return 1;
2025 }
2026
2027 return 0;
2028}
2029
2030static const dt_dev_pixelpipe_iop_t *_get_backbuf_source_piece(const dt_backbuf_t *backbuf, const char *op)
2031{
2032 dt_develop_t *const dev = darktable.develop;
2033 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2034 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(op) || IS_NULL_PTR(backbuf)) return NULL;
2035
2036 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2037 if(IS_NULL_PTR(module)) return NULL;
2038
2039 const dt_dev_pixelpipe_iop_t *piece = dt_dev_pixelpipe_get_module_piece(pipe, module);
2040 if(IS_NULL_PTR(piece)) return NULL;
2041
2042 if(!strcmp(op, "gamma"))
2043 piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
2044
2045 if(IS_NULL_PTR(piece) || piece->global_hash != dt_dev_backbuf_get_hash(backbuf)) return NULL;
2046 return piece;
2047}
2048
2071static gboolean _resolve_backbuf_sampling_source(const char *const op, const dt_backbuf_t *const backbuf,
2072 dt_iop_roi_t *const roi, dt_iop_buffer_dsc_t *const dsc,
2073 double *const iop_order, int *const direction)
2074{
2075 dt_develop_t *const dev = darktable.develop;
2076 dt_dev_pixelpipe_t *const pipe = dev ? dev->preview_pipe : NULL;
2077 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;
2078 /* Avoid taking `pipe->busy_mutex` on the GUI thread: read live module state
2079 * without blocking and let the cache peek manager handle synchronization. */
2080 dt_iop_module_t *const module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
2081 if(IS_NULL_PTR(module)) return FALSE;
2082
2083 const dt_dev_pixelpipe_iop_t *const piece = dt_dev_pixelpipe_get_module_piece(pipe, module);
2084 if(IS_NULL_PTR(piece)) return FALSE;
2085
2086 uint64_t hash = piece->global_hash;
2087 *roi = piece->roi_out;
2088 *dsc = piece->dsc_out;
2089 *iop_order = module->iop_order;
2090 *direction = DT_DEV_TRANSFORM_DIR_FORW_EXCL;
2091
2092 if(!strcmp(op, "gamma"))
2093 {
2094 const dt_dev_pixelpipe_iop_t *const previous_piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
2095 if(IS_NULL_PTR(previous_piece)) return FALSE;
2096
2097 hash = previous_piece->global_hash;
2098 *roi = previous_piece->roi_out;
2099 *dsc = previous_piece->dsc_out;
2100 *direction = DT_DEV_TRANSFORM_DIR_FORW_INCL;
2101 }
2102
2103 const gboolean valid = hash == dt_dev_backbuf_get_hash(backbuf);
2104 return valid;
2105}
2106
2107static void _pixelpipe_pick_from_image(const dt_backbuf_t *const backbuf,
2109 const char *op)
2110{
2111 struct dt_pixel_cache_entry_t *entry = NULL;
2112 void *data = NULL;
2113 const dt_dev_pixelpipe_iop_t *const source_piece = _get_backbuf_source_piece(backbuf, op);
2114 if(IS_NULL_PTR(source_piece)) return;
2115 dt_dev_pixelpipe_cache_wait_set_owner(&d->picker_wait, "histogram-picker", d->module);
2117 &d->picker_wait, _histogram_restart_cache_wait, d->module))
2118 {
2119 return;
2120 }
2121
2123
2124 const float *const pixel = data;
2125 dt_iop_buffer_dsc_t dsc = {
2126 .channels = 4,
2127 .datatype = TYPE_FLOAT,
2128 .bpp = 4 * sizeof(float),
2129 .cst = IOP_CS_RGB
2130 };
2131 dt_iop_roi_t roi = {
2132 .x = 0,
2133 .y = 0,
2134 .width = (int)backbuf->width,
2135 .height = (int)backbuf->height,
2136 .scale = 1.0
2137 };
2138 double iop_order = 0.0;
2139 int direction = DT_DEV_TRANSFORM_DIR_FORW_EXCL;
2140 const gboolean have_live_stage = _resolve_backbuf_sampling_source(op, backbuf, &roi, &dsc, &iop_order, &direction);
2141 int box[4];
2142
2143 if(have_live_stage)
2144 {
2145 dt_boundingbox_t fbox = { 0.0f };
2146
2147 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2148 {
2149 _sample_raw_box_to_image_norm(sample, fbox);
2151 }
2152 else if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2153 {
2154 _sample_raw_point_to_image_norm(sample, fbox);
2156 fbox[2] = fbox[0];
2157 fbox[3] = fbox[1];
2158 }
2159
2161 direction, fbox, 2);
2162
2163 /* The backtransform returns stage coordinates in the sampled piece space. Shift them by the
2164 stage ROI before clamping so the picker box matches the published cache window exactly. */
2165 fbox[0] -= roi.x;
2166 fbox[1] -= roi.y;
2167 fbox[2] -= roi.x;
2168 fbox[3] -= roi.y;
2169
2170 box[0] = fminf(fbox[0], fbox[2]);
2171 box[1] = fminf(fbox[1], fbox[3]);
2172 box[2] = fmaxf(fbox[0], fbox[2]);
2173 box[3] = fmaxf(fbox[1], fbox[3]);
2174
2175 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2176 {
2177 box[2] += 1;
2178 box[3] += 1;
2179 }
2180
2181 box[0] = MIN(roi.width - 1, MAX(0, box[0]));
2182 box[1] = MIN(roi.height - 1, MAX(0, box[1]));
2183 box[2] = MIN(roi.width - 1, MAX(0, box[2]));
2184 box[3] = MIN(roi.height - 1, MAX(0, box[3]));
2185 }
2186 else
2187 {
2189 return;
2190 }
2191
2192 dt_aligned_pixel_t mean = { 0.0f };
2193 dt_aligned_pixel_t min = { INFINITY, INFINITY, INFINITY, INFINITY };
2194 dt_aligned_pixel_t max = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
2195
2196 dt_color_picker_helper(&dsc, pixel, &roi, box, mean, min, max, dsc.cst, IOP_CS_RGB, NULL);
2197
2199 {
2200 sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k] = mean[k];
2203 }
2204
2205 dt_lib_histogram_t scope = *d;
2206 scope.op = op;
2207
2209 {
2210 _scope_pixel_to_display_rgb(sample->scope[k], sample->display[k], &scope);
2211
2212 dt_aligned_pixel_t XYZ = { 0.f };
2213 _scope_pixel_to_xyz(sample->scope[k], XYZ, &scope);
2214 dt_XYZ_to_Lab(XYZ, sample->lab[k]);
2215 }
2216
2218}
2219
2221{
2222 GSList *samples = darktable.develop->color_picker.samples;
2223 while(samples)
2224 {
2225 dt_colorpicker_sample_t *sample = samples->data;
2226 if(!sample->locked)
2227 {
2228 const char *const op = sample->backbuf_op[0] ? sample->backbuf_op : d->op;
2230 if(backbuf) _pixelpipe_pick_from_image(backbuf, sample, d, op);
2231 }
2232 samples = g_slist_next(samples);
2233 }
2234
2237}
2238
2239
2241{
2243
2245 {
2247 }
2248
2250
2251 for(GSList *samples = darktable.develop->color_picker.samples;
2252 samples;
2253 samples = g_slist_next(samples))
2254 {
2255 _update_sample_label(self, samples->data);
2256 }
2257
2259
2260 // allow live sample button to work for iop samples
2261 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button),
2263}
2264
2266{
2267 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return FALSE;
2268
2270 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
2271
2272 if(!_is_backbuf_ready(d)) return FALSE;
2273
2274 /* In restricted mode the scope bins only picker rectangles/points. Moving the
2275 * global picker does not change the preview cache hash, so invalidate the
2276 * Cairo scope cache explicitly before recomputing from the same backbuffer. */
2277 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2278 _reset_cache(d);
2279
2280 _update_everything(self);
2281 return TRUE;
2282}
2283
2284
2285static void _lib_histogram_history_resync_callback(gpointer instance, dt_lib_module_t *self)
2286{
2287 (void)instance;
2289}
2290
2291static void _lib_histogram_cacheline_ready_callback(gpointer instance, const guint64 hash, dt_lib_module_t *self)
2292{
2293 (void)instance;
2295 if(IS_NULL_PTR(d) || !_remove_pending_hash(d, hash)) return;
2296
2298}
2299
2300// this is only called in darkroom view when history was resynchronized
2315
2316void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
2317{
2318 dt_lib_histogram_t *d = self->data;
2319 _reset_cache(d);
2320 dt_dev_pixelpipe_cache_wait_cleanup(&d->scope_wait, "histogram-view-leave-scope");
2321 dt_dev_pixelpipe_cache_wait_cleanup(&d->picker_wait, "histogram-view-leave-picker");
2322 dt_dev_pixelpipe_cache_wait_cleanup(&d->module_wait, "histogram-view-leave-module");
2323
2326 if(d->refresh_idle_source != 0)
2327 {
2328 g_source_remove(d->refresh_idle_source);
2329 d->refresh_idle_source = 0;
2330 }
2331
2336}
2337
2338
2339static void _set_stage(dt_lib_module_t *self, int value)
2340{
2343 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2344
2345 // Vectorscope is meaningless for raw Bayer data
2346 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2347 {
2349 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2350 }
2351
2354}
2355
2357{
2359 d->scope = scope;
2360 dt_conf_set_int("plugin/darkroom/histogram/display", d->scope);
2362}
2363
2364
2365static void _resize_callback(GtkWidget *widget, GdkRectangle *allocation, dt_lib_histogram_t *d)
2366{
2367 _reset_cache(d);
2369 // Don't start a redraw from here, Gtk does it automatically on resize event
2370}
2371
2372static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_lib_histogram_t *d)
2373{
2374 if(d->scope != DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE) return FALSE;
2375
2376 int delta_y = 0;
2377 if(!dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y)) return TRUE;
2378
2379 const float new_value = 4.f * delta_y + d->zoom;
2380
2381 if(new_value < 512.f && new_value > 32.f)
2382 {
2383 d->zoom = new_value;
2384 dt_conf_set_float("plugin/darkroom/histogram/zoom", new_value);
2385 if(_is_backbuf_ready(d))
2386 {
2389 }
2390 }
2391 return TRUE;
2392}
2393
2394static int _scope_resize_handle_get_size(gpointer user_data)
2395{
2396 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2397 return gtk_widget_get_allocated_height(d->scope_draw);
2398}
2399
2407static int _scope_resize_handle_resize(int requested_size, gboolean finished, gpointer user_data)
2408{
2409 dt_lib_histogram_t *d = (dt_lib_histogram_t *)user_data;
2410 int window_height;
2411 gtk_window_get_size(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL, &window_height);
2412
2414 const int max_height = MAX(min_height, MIN(DT_PIXEL_APPLY_DPI(DT_LIB_HISTOGRAM_SCOPE_MAX_HEIGHT),
2415 window_height * 3 / 4));
2416 d->scope_height = CLAMP(requested_size, min_height, max_height);
2417
2418 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
2419 gtk_widget_queue_resize(d->scope_draw);
2420 if(finished)
2422
2423 return d->scope_height;
2424}
2425
2427{
2428 d->op = dt_conf_get_string_const("plugin/darkroom/histogram/op");
2429 if(!strcmp(d->op, "demosaic"))
2430 {
2431 d->op = "initialscale";
2432 dt_conf_set_string("plugin/darkroom/histogram/op", d->op);
2433 }
2434 d->backbuf = _get_histogram_backbuf(darktable.develop, d->op);
2435 d->zoom = fminf(fmaxf(dt_conf_get_float("plugin/darkroom/histogram/zoom"), 32.f), 252.f);
2436
2437 d->scope = (dt_lib_histogram_scope_type_t)CLAMP(
2438 dt_conf_get_int("plugin/darkroom/histogram/display"), 0, DT_LIB_HISTOGRAM_SCOPE_N - 1);
2439
2440 // Vectorscope is not valid on raw Bayer data; fall back to histogram
2441 if(!strcmp(d->op, "initialscale") && d->scope == DT_LIB_HISTOGRAM_SCOPE_VECTORSCOPE)
2443}
2444
2445
2446static gboolean _sample_draw_callback(GtkWidget *widget, cairo_t *cr, dt_colorpicker_sample_t *sample)
2447{
2448 const guint width = gtk_widget_get_allocated_width(widget);
2449 const guint height = gtk_widget_get_allocated_height(widget);
2450
2451 set_color(cr, sample->swatch);
2452 cairo_rectangle(cr, 0, 0, width, height);
2453 cairo_fill (cr);
2454
2455 // if the sample is locked we want to add a lock
2456 if(sample->locked)
2457 {
2458 const int border = DT_PIXEL_APPLY_DPI(2);
2459 const int icon_width = width - 2 * border;
2460 const int icon_height = height - 2 * border;
2461 if(icon_width > 0 && icon_height > 0)
2462 {
2463 GdkRGBA fg_color;
2464 gtk_style_context_get_color(gtk_widget_get_style_context(widget), gtk_widget_get_state_flags(widget), &fg_color);
2465
2466 gdk_cairo_set_source_rgba(cr, &fg_color);
2467 dtgtk_cairo_paint_lock(cr, border, border, icon_width, icon_height, 0, NULL);
2468 }
2469 }
2470
2471 return FALSE;
2472}
2473
2475{
2476 dt_lib_histogram_t *d = self->data;
2477 const dt_lib_colorpicker_statistic_t statistic
2479
2480 sample->swatch.red = sample->display[statistic][0];
2481 sample->swatch.green = sample->display[statistic][1];
2482 sample->swatch.blue = sample->display[statistic][2];
2484 sample->label_rgb[ch] = (int)roundf(sample->scope[statistic][ch] * 255.f);
2485
2486 // Setting the output label
2487 char text[128] = { 0 };
2488 dt_aligned_pixel_t alt = { 0 };
2489
2490 switch(d->model)
2491 {
2493 snprintf(text, sizeof(text), "%6d %6d %6d", sample->label_rgb[0], sample->label_rgb[1], sample->label_rgb[2]);
2494 break;
2495
2497 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f",
2498 CLAMP(sample->lab[statistic][0], .0f, 100.0f), sample->lab[statistic][1], sample->lab[statistic][2]);
2499 break;
2500
2502 dt_Lab_2_LCH(sample->lab[statistic], alt);
2503 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", CLAMP(alt[0], .0f, 100.0f), alt[1], alt[2] * 360.f);
2504 break;
2505
2507 dt_RGB_2_HSL(sample->scope[statistic], alt);
2508 snprintf(text, sizeof(text), "%6.02f %6.02f %6.02f", alt[0] * 360.f, alt[1] * 100.f, alt[2] * 100.f);
2509 break;
2510
2512 dt_RGB_2_HSV(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
2516 default:
2518 snprintf(text, sizeof(text), "\342\227\216");
2519 break;
2520 }
2521
2522 if(g_strcmp0(gtk_label_get_text(GTK_LABEL(sample->output_label)), text))
2523 gtk_label_set_text(GTK_LABEL(sample->output_label), text);
2524
2525 gtk_widget_queue_draw(sample->color_patch);
2526}
2527
2529{
2530 _update_everything(self);
2531}
2532
2533static void _picker_button_toggled(GtkToggleButton *button, dt_lib_histogram_t *d)
2534{
2535 const gboolean picker_active = gtk_toggle_button_get_active(button);
2536 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), picker_active);
2537
2538 const gboolean restrict_active = !IS_NULL_PTR(d->restrict_button)
2539 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->restrict_button));
2540 if(restrict_active)
2541 {
2542 /* The picker state is part of restricted-scope rendering but not of the
2543 * preview backbuffer hash. Invalidate on both activation and deactivation
2544 * so the same cached pixels are rebinned for the newly selected scope. */
2545 _reset_cache(d);
2547 }
2548}
2549
2550/* set sample area proxy impl */
2551
2553{
2555 dt_lib_histogram_t *d = self->data;
2556 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2557 _reset_cache(d);
2558 _update_everything(self);
2559}
2560
2561static void _set_sample_point(dt_lib_module_t *self, const float pos[2])
2562{
2564 dt_lib_histogram_t *d = self->data;
2565 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2566 _reset_cache(d);
2567 _update_everything(self);
2568}
2569
2570
2571static gboolean _sample_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
2572 GtkTooltip *tooltip, const dt_colorpicker_sample_t *sample)
2573{
2574 gchar **sample_parts = g_malloc0_n(14, sizeof(char*));
2575
2576 sample_parts[3] = g_strdup_printf("%22s(0x%02X%02X%02X)\n<big><b>%14s</b></big>", " ",
2577 CLAMP(sample->label_rgb[0], 0, 255), CLAMP(sample->label_rgb[1], 0, 255),
2578 CLAMP(sample->label_rgb[2], 0, 255), _("RGB"));
2579 sample_parts[7] = g_strdup_printf("\n<big><b>%14s</b></big>", _("Lab"));
2580
2581 for(int i = 0; i < DT_LIB_COLORPICKER_STATISTIC_N; i++)
2582 {
2583 sample_parts[i] = g_strdup_printf("<span background='#%02X%02X%02X'>%32s</span>",
2584 (int)roundf(CLAMP(sample->display[i][0], 0.f, 1.f) * 255.f),
2585 (int)roundf(CLAMP(sample->display[i][1], 0.f, 1.f) * 255.f),
2586 (int)roundf(CLAMP(sample->display[i][2], 0.f, 1.f) * 255.f), " ");
2587
2588 sample_parts[i + 4] = g_strdup_printf("<span foreground='#FF7F7F'>%6d</span> "
2589 "<span foreground='#7FFF7F'>%6d</span> "
2590 "<span foreground='#7F7FFF'>%6d</span> %s",
2591 (int)roundf(sample->scope[i][0] * 255.f),
2592 (int)roundf(sample->scope[i][1] * 255.f),
2593 (int)roundf(sample->scope[i][2] * 255.f),
2595
2596 sample_parts[i + 8] = g_strdup_printf("%6.02f %6.02f %6.02f %s",
2597 sample->lab[i][0], sample->lab[i][1], sample->lab[i][2],
2599 }
2600
2601 dt_aligned_pixel_t color;
2602 dt_Lab_2_LCH(sample->lab[DT_LIB_COLORPICKER_STATISTIC_MEAN], color);
2603 sample_parts[11] = g_strdup_printf("\n<big><b>%14s</b></big>", _("color"));
2604 sample_parts[12] = g_strdup_printf("%6s", Lch_to_color_name(color));
2605
2606 gchar *tooltip_text = g_strjoinv("\n", sample_parts);
2607 g_strfreev(sample_parts);
2608
2609 gtk_tooltip_set_markup(tooltip, tooltip_text);
2610
2611 dt_free(tooltip_text);
2612
2613 return TRUE;
2614}
2615
2617{
2618 dt_lib_histogram_t *d = self->data;
2619 d->statistic = dt_bauhaus_combobox_get(widget);
2620 darktable.develop->color_picker.statistic = (int)d->statistic;
2621 dt_conf_set_string("ui_last/colorpicker_mode", dt_lib_colorpicker_statistic_names[d->statistic]);
2622 _update_everything(self);
2623}
2624
2626{
2627 dt_lib_histogram_t *d = self->data;
2628 d->model = dt_bauhaus_combobox_get(widget);
2629 dt_conf_set_string("ui_last/colorpicker_model", dt_lib_colorpicker_model_names[d->model]);
2630 _update_everything(self);
2631}
2632
2633static void _label_size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
2634{
2635 gint label_width;
2636 gtk_label_set_attributes(GTK_LABEL(widget), NULL);
2637
2638 PangoStretch stretch = PANGO_STRETCH_NORMAL;
2639
2640 while(gtk_widget_get_preferred_width(widget, NULL, &label_width),
2641 label_width > allocation->width && stretch != PANGO_STRETCH_ULTRA_CONDENSED)
2642 {
2643 stretch--;
2644
2645 PangoAttrList *attrlist = pango_attr_list_new();
2646 PangoAttribute *attr = pango_attr_stretch_new(stretch);
2647 pango_attr_list_insert(attrlist, attr);
2648 gtk_label_set_attributes(GTK_LABEL(widget), attrlist);
2649 pango_attr_list_unref(attrlist);
2650 }
2651}
2652
2653static gboolean _sample_enter_callback(GtkWidget *widget, GdkEvent *event, dt_colorpicker_sample_t *sample)
2654{
2656 {
2659 }
2660
2661 return FALSE;
2662}
2663
2664static gboolean _sample_leave_callback(GtkWidget *widget, GdkEvent *event, gpointer data)
2665{
2666 if(event->crossing.detail == GDK_NOTIFY_INFERIOR) return FALSE;
2667
2669 {
2672 }
2673
2674 return FALSE;
2675}
2676
2678{
2679 gtk_widget_destroy(sample->container);
2681 = g_slist_remove(darktable.develop->color_picker.samples, (gpointer)sample);
2682 dt_free(sample);
2683}
2684
2685static void _remove_sample_cb(GtkButton *widget, dt_colorpicker_sample_t *sample)
2686{
2688 dt_lib_histogram_t *d = self ? self->data : NULL;
2689 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram") && !IS_NULL_PTR(d))
2690 _reset_cache(d);
2691
2692 _remove_sample(sample);
2694 if(!IS_NULL_PTR(self))
2695 _update_everything(self);
2696}
2697
2698static gboolean _live_sample_button(GtkWidget *widget, GdkEventButton *event, dt_colorpicker_sample_t *sample)
2699{
2700 if(event->button == 1)
2701 {
2702 sample->locked = !sample->locked;
2703 gtk_widget_queue_draw(widget);
2704 }
2705 else if(event->button == 3)
2706 {
2707 // copy to active picker
2710
2711 // no active picker, too much iffy GTK work to activate a default
2712 if(IS_NULL_PTR(picker)) return FALSE;
2713
2714 if(sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
2715 {
2716 float point[2] = { 0.0f };
2718 _set_sample_point(self, point);
2719 }
2720 else if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2721 {
2722 dt_boundingbox_t box = { 0.0f };
2723 _sample_raw_box_to_image_norm(sample, box);
2724 _set_sample_box_area(self, box);
2725 }
2726 else
2727 return FALSE;
2728
2730 }
2731 return FALSE;
2732}
2733
2734static void _add_sample(GtkButton *widget, dt_lib_module_t *self)
2735{
2736 dt_lib_histogram_t *d = self->data;
2737
2739 return;
2740
2742 if(IS_NULL_PTR(sample)) return;
2743
2745 sample->locked = FALSE;
2746 g_strlcpy(sample->backbuf_op, d->op ? d->op : "", sizeof(sample->backbuf_op));
2747
2748 sample->container = gtk_event_box_new();
2749 gtk_widget_add_events(sample->container, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
2750 g_signal_connect(G_OBJECT(sample->container), "enter-notify-event", G_CALLBACK(_sample_enter_callback), sample);
2751 g_signal_connect(G_OBJECT(sample->container), "leave-notify-event", G_CALLBACK(_sample_leave_callback), sample);
2752
2753 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2754 gtk_container_add(GTK_CONTAINER(sample->container), container);
2755
2756 sample->color_patch = gtk_drawing_area_new();
2757 gtk_widget_add_events(sample->color_patch, GDK_BUTTON_PRESS_MASK);
2758 gtk_widget_set_tooltip_text(sample->color_patch, _("hover to highlight sample on canvas,\n"
2759 "click to lock sample,\n"
2760 "right-click to load sample area into active color picker"));
2761 g_signal_connect(G_OBJECT(sample->color_patch), "button-press-event", G_CALLBACK(_live_sample_button), sample);
2762 g_signal_connect(G_OBJECT(sample->color_patch), "draw", G_CALLBACK(_sample_draw_callback), sample);
2763
2764 GtkWidget *color_patch_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2765 gtk_widget_set_name(color_patch_wrapper, "live-sample");
2766 gtk_box_pack_start(GTK_BOX(color_patch_wrapper), sample->color_patch, TRUE, TRUE, 0);
2767 gtk_box_pack_start(GTK_BOX(container), color_patch_wrapper, TRUE, TRUE, 0);
2768
2769 sample->output_label = gtk_label_new("");
2770 dt_gui_add_class(sample->output_label, "dt_monospace");
2771 gtk_label_set_ellipsize(GTK_LABEL(sample->output_label), PANGO_ELLIPSIZE_START);
2772 gtk_label_set_selectable(GTK_LABEL(sample->output_label), TRUE);
2773 gtk_widget_set_has_tooltip(sample->output_label, TRUE);
2774 g_signal_connect(G_OBJECT(sample->output_label), "query-tooltip", G_CALLBACK(_sample_tooltip_callback), sample);
2775 g_signal_connect(G_OBJECT(sample->output_label), "size-allocate", G_CALLBACK(_label_size_allocate_callback), sample);
2776 gtk_box_pack_start(GTK_BOX(container), sample->output_label, TRUE, TRUE, 0);
2777
2779 g_signal_connect(G_OBJECT(delete_button), "clicked", G_CALLBACK(_remove_sample_cb), sample);
2780 gtk_box_pack_start(GTK_BOX(container), delete_button, FALSE, FALSE, 0);
2781
2782 gtk_box_pack_start(GTK_BOX(d->samples_container), sample->container, FALSE, FALSE, 0);
2783 gtk_widget_show_all(sample->container);
2784
2786 = g_slist_append(darktable.develop->color_picker.samples, sample);
2787
2788 // remove emphasis on primary sample from mouseover on this button
2790
2791 // Updating the display
2792 if(dt_conf_get_bool("ui_last/colorpicker_restrict_histogram"))
2793 _reset_cache(d);
2794 _update_everything(self);
2795}
2796
2797static void _display_samples_changed(GtkToggleButton *button, dt_lib_module_t *self)
2798{
2799 dt_conf_set_bool("ui_last/colorpicker_display_samples", gtk_toggle_button_get_active(button));
2800 darktable.develop->color_picker.display_samples = gtk_toggle_button_get_active(button);
2801 _update_everything(self);
2803}
2804
2805static void _restrict_histogram_changed(GtkToggleButton *button, dt_lib_module_t *self)
2806{
2807 dt_lib_histogram_t *d = self->data;
2808 const gboolean restrict_active = gtk_toggle_button_get_active(button);
2809 const gboolean picker_active = !IS_NULL_PTR(d->picker_button)
2810 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->picker_button));
2811 dt_conf_set_bool("ui_last/colorpicker_restrict_histogram", restrict_active);
2813
2814 if(picker_active)
2815 {
2816 /* Restrict toggling changes the scope bins between full-image and picker
2817 * geometry while the preview backbuffer hash stays identical. Make the
2818 * cached Cairo surface dirty so _update_everything() renders the new mode. */
2819 _reset_cache(d);
2820 }
2821 _update_everything(self);
2822}
2823
2825{
2826 dt_lib_histogram_t *d = self->data;
2828
2830
2831 // Resetting the picked colors
2832 for(int i = 0; i < 3; i++)
2833 {
2834 for(int s = 0; s < DT_LIB_COLORPICKER_STATISTIC_N; s++)
2835 {
2836 primary_sample->display[s][i] = 0.f;
2837 primary_sample->scope[s][i] = 0.f;
2838 primary_sample->lab[s][i] = 0.f;
2839 }
2840 primary_sample->label_rgb[i] = 0;
2841 }
2842 primary_sample->swatch.red = primary_sample->swatch.green
2843 = primary_sample->swatch.blue = 0.0;
2844
2846
2847 // Removing any live samples
2850
2851 // Resetting GUI elements
2852 dt_bauhaus_combobox_set(d->statistic_selector, 0);
2853 dt_bauhaus_combobox_set(d->color_mode_selector, 0);
2854 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box)))
2855 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box), FALSE);
2856
2857 _reset_cache(d);
2858 _set_params(d);
2861
2863}
2864
2865static void _pref_stage_toggled(GtkCheckMenuItem *item, gpointer user_data)
2866{
2867 if(!gtk_check_menu_item_get_active(item)) return;
2868 dt_lib_module_t *self = (dt_lib_module_t *)g_object_get_data(G_OBJECT(item), "self");
2869 _set_stage(self, GPOINTER_TO_INT(user_data));
2870}
2871
2872static void _pref_display_toggled(GtkCheckMenuItem *item, gpointer user_data)
2873{
2874 if(!gtk_check_menu_item_get_active(item)) return;
2875 dt_lib_module_t *self = (dt_lib_module_t *)g_object_get_data(G_OBJECT(item), "self");
2876 _set_scope(self, (dt_lib_histogram_scope_type_t)GPOINTER_TO_INT(user_data));
2877}
2878
2879void set_preferences(void *menu, dt_lib_module_t *self)
2880{
2882
2883 // "Show data from" submenu
2884 GtkWidget *mi_stage = gtk_menu_item_new_with_label(_("Show data from"));
2885 GtkWidget *submenu_stage = gtk_menu_new();
2886 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi_stage), submenu_stage);
2887
2888 const char *stage_labels[] = { N_("Raw image"), N_("Output color profile"), N_("Final display"), NULL };
2889 const int current_stage = _backbuf_op_to_int(d);
2890 GSList *stage_group = NULL;
2891 for(int i = 0; stage_labels[i]; i++)
2892 {
2893 GtkWidget *item = gtk_radio_menu_item_new_with_label(stage_group, _(stage_labels[i]));
2894 stage_group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
2895 if(i == current_stage) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2896 g_object_set_data(G_OBJECT(item), "self", self);
2897 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(_pref_stage_toggled), GINT_TO_POINTER(i));
2898 gtk_menu_shell_append(GTK_MENU_SHELL(submenu_stage), item);
2899 }
2900 gtk_widget_show_all(mi_stage);
2901 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi_stage);
2902
2903 // "Display" submenu
2904 GtkWidget *mi_display = gtk_menu_item_new_with_label(_("Display"));
2905 GtkWidget *submenu_display = gtk_menu_new();
2906 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi_display), submenu_display);
2907
2908 const char *display_labels[] = {
2909 N_("Histogram"), N_("Waveform (horizontal)"), N_("Waveform (vertical)"),
2910 N_("Parade (horizontal)"), N_("Parade (vertical)"), N_("Vectorscope"), NULL
2911 };
2912 const gboolean vectorscope_ok = strcmp(d->op, "initialscale");
2913 GSList *display_group = NULL;
2914 for(int i = 0; display_labels[i]; i++)
2915 {
2916 GtkWidget *item = gtk_radio_menu_item_new_with_label(display_group, _(display_labels[i]));
2917 display_group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
2918 if((dt_lib_histogram_scope_type_t)i == d->scope)
2919 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2921 gtk_widget_set_sensitive(item, vectorscope_ok);
2922 g_object_set_data(G_OBJECT(item), "self", self);
2923 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(_pref_display_toggled), GINT_TO_POINTER(i));
2924 gtk_menu_shell_append(GTK_MENU_SHELL(submenu_display), item);
2925 }
2926 gtk_widget_show_all(mi_display);
2927 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi_display);
2928}
2929
2930void gui_init(dt_lib_module_t *self)
2931{
2932 /* initialize ui widgets */
2934 if(d) memset(d, 0, sizeof(dt_lib_histogram_t));
2935 self->data = (void *)d;
2936 d->module = self;
2937 d->cst = NULL;
2938 d->pending_hashes = g_array_new(FALSE, FALSE, sizeof(uint64_t));
2939 d->refresh_idle_source = 0;
2940
2941 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2942 d->scope_draw = gtk_drawing_area_new();
2943 gtk_widget_add_events(GTK_WIDGET(d->scope_draw), darktable.gui->scroll_mask);
2947 d->scope_height = CLAMP(d->scope_height,
2950 gtk_widget_set_size_request(d->scope_draw, -1, d->scope_height);
2951 g_signal_connect(G_OBJECT(d->scope_draw), "draw", G_CALLBACK(_draw_callback), d);
2952 g_signal_connect(G_OBJECT(d->scope_draw), "scroll-event", G_CALLBACK(_area_scrolled_callback), d);
2953 g_signal_connect(G_OBJECT(d->scope_draw), "size-allocate", G_CALLBACK(_resize_callback), d);
2954
2955 // The grip floats on the scope's bottom edge (overlay) so it takes no layout space and leaves no
2956 // margin-like gap; invisible until hovered.
2957 GtkWidget *scope_overlay = gtk_overlay_new();
2958 gtk_container_add(GTK_CONTAINER(scope_overlay), d->scope_draw);
2959 gtk_box_pack_start(GTK_BOX(self->widget), scope_overlay, FALSE, FALSE, 0);
2960
2973 d->scope_resize_handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_VERTICAL, FALSE,
2974 _("Drag to resize the scope vertically"),
2977 gtk_overlay_add_overlay(GTK_OVERLAY(scope_overlay), d->scope_resize_handle);
2978
2980 (&d->cs,
2981 "plugins/darkroom/colorpicker/show",
2982 _("Color picker"),
2983 GTK_BOX(self->widget), GTK_PACK_END);
2984
2985 // The develop module owns the picker state because both the preview pipe and the GUI need to
2986 // observe it. Histogram only binds its widgets to that shared state.
2989 darktable.develop->color_picker.display_samples = dt_conf_get_bool("ui_last/colorpicker_display_samples");
2990 darktable.develop->color_picker.restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
2992
2993 const char *str = dt_conf_get_string_const("ui_last/colorpicker_model");
2994 const char **names = dt_lib_colorpicker_model_names;
2995 for(dt_lib_colorpicker_model_t i=0; *names; names++, i++)
2996 if(g_strcmp0(str, *names) == 0)
2997 d->model = i;
2998
2999 str = dt_conf_get_string_const("ui_last/colorpicker_mode");
3001 for(dt_lib_colorpicker_statistic_t i=0; *names; names++, i++)
3002 if(g_strcmp0(str, *names) == 0)
3003 d->statistic = i;
3004
3005 // The color patch
3006 GtkWidget *color_patch_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3007 gtk_widget_set_name(GTK_WIDGET(color_patch_wrapper), "color-picker-area");
3008
3009 // The picker button, mode and statistic combo boxes
3010 GtkWidget *picker_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3011 gtk_box_pack_start(GTK_BOX(d->cs.container), picker_row, TRUE, TRUE, 0);
3012
3013 d->statistic_selector = dt_bauhaus_combobox_new_full(
3014 darktable.bauhaus, NULL, NULL, _("select which statistic to show"), d->statistic,
3016 dt_bauhaus_combobox_set_entries_ellipsis(d->statistic_selector, PANGO_ELLIPSIZE_NONE);
3017 dt_bauhaus_widget_set_label(d->statistic_selector, NULL);
3018 gtk_widget_set_valign(d->statistic_selector, GTK_ALIGN_CENTER);
3019 gtk_box_pack_start(GTK_BOX(picker_row), d->statistic_selector, TRUE, TRUE, 0);
3020
3021 d->color_mode_selector = dt_bauhaus_combobox_new_full(
3022 darktable.bauhaus, NULL, NULL, _("select which color mode to use"), d->model,
3024 dt_bauhaus_combobox_set_entries_ellipsis(d->color_mode_selector, PANGO_ELLIPSIZE_NONE);
3025 dt_bauhaus_widget_set_label(d->color_mode_selector, NULL);
3026 gtk_widget_set_valign(d->color_mode_selector, GTK_ALIGN_CENTER);
3027 gtk_box_pack_start(GTK_BOX(picker_row), d->color_mode_selector, TRUE, TRUE, 0);
3028
3029 d->picker_button = dt_color_picker_new(NULL, DT_COLOR_PICKER_POINT_AREA, picker_row);
3030 gtk_widget_set_tooltip_text(d->picker_button, _("turn on color picker\nctrl+click or right-click to select an area"));
3031 gtk_widget_set_name(GTK_WIDGET(d->picker_button), "color-picker-button");
3032 g_signal_connect(G_OBJECT(d->picker_button), "toggled", G_CALLBACK(_picker_button_toggled), d);
3033
3034 // The small sample, label and add button
3035 GtkWidget *sample_row_events = gtk_event_box_new();
3036 gtk_widget_add_events(sample_row_events, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
3037 g_signal_connect(G_OBJECT(sample_row_events), "enter-notify-event", G_CALLBACK(_sample_enter_callback),
3039 g_signal_connect(G_OBJECT(sample_row_events), "leave-notify-event", G_CALLBACK(_sample_leave_callback),
3041 gtk_box_pack_start(GTK_BOX(d->cs.container), sample_row_events, TRUE, TRUE, 0);
3042
3043 GtkWidget *sample_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3044 gtk_container_add(GTK_CONTAINER(sample_row_events), sample_row);
3045
3046 darktable.develop->color_picker.primary_sample->color_patch = gtk_drawing_area_new();
3047 g_signal_connect(G_OBJECT(darktable.develop->color_picker.primary_sample->color_patch), "draw",
3049
3050 color_patch_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3051 gtk_widget_set_name(color_patch_wrapper, "live-sample");
3052 gtk_box_pack_start(GTK_BOX(color_patch_wrapper), darktable.develop->color_picker.primary_sample->color_patch, TRUE, TRUE, 0);
3053 gtk_box_pack_start(GTK_BOX(sample_row), color_patch_wrapper, TRUE, TRUE, 0);
3054
3055 GtkWidget *label = darktable.develop->color_picker.primary_sample->output_label = gtk_label_new("");
3056 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
3057 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
3058 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
3059 dt_gui_add_class(label, "dt_monospace");
3060 gtk_widget_set_has_tooltip(label, TRUE);
3061 g_signal_connect(G_OBJECT(label), "query-tooltip", G_CALLBACK(_sample_tooltip_callback),
3063 g_signal_connect(G_OBJECT(label), "size-allocate", G_CALLBACK(_label_size_allocate_callback),
3065 gtk_box_pack_start(GTK_BOX(sample_row), label, TRUE, TRUE, 0);
3066
3067 d->add_sample_button = dtgtk_button_new(dtgtk_cairo_paint_square_plus, 0, NULL);
3068 gtk_widget_set_sensitive(GTK_WIDGET(d->add_sample_button), FALSE);
3069 g_signal_connect(G_OBJECT(d->add_sample_button), "clicked", G_CALLBACK(_add_sample), self);
3070 gtk_box_pack_end(GTK_BOX(sample_row), d->add_sample_button, FALSE, FALSE, 0);
3071
3072 // Adding the live samples section
3073 label = dt_ui_section_label_new(_("Live samples"));
3074 gtk_box_pack_start(GTK_BOX(d->cs.container), label, TRUE, TRUE, 0);
3075
3076 d->samples_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3077 gtk_box_pack_start(GTK_BOX(d->cs.container),
3078 dt_ui_scroll_wrap(d->samples_container, 1, "plugins/darkroom/colorpicker/windowheight",
3080
3081 d->display_samples_check_box = gtk_check_button_new_with_label(_("Display samples on image"));
3082 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->display_samples_check_box))),
3083 PANGO_ELLIPSIZE_MIDDLE);
3084 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->display_samples_check_box),
3085 dt_conf_get_bool("ui_last/colorpicker_display_samples"));
3086 g_signal_connect(G_OBJECT(d->display_samples_check_box), "toggled",
3087 G_CALLBACK(_display_samples_changed), self);
3088 gtk_box_pack_start(GTK_BOX(d->cs.container), d->display_samples_check_box, TRUE, TRUE, 0);
3089
3090 d->restrict_button = gtk_check_button_new_with_label(_("Restrict scope to selection"));
3091 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->restrict_button))), PANGO_ELLIPSIZE_MIDDLE);
3092 gboolean restrict_histogram = dt_conf_get_bool("ui_last/colorpicker_restrict_histogram");
3093 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->restrict_button), restrict_histogram);
3094 g_signal_connect(G_OBJECT(d->restrict_button), "toggled", G_CALLBACK(_restrict_histogram_changed), self);
3095 gtk_box_pack_start(GTK_BOX(d->cs.container), d->restrict_button, TRUE, TRUE, 0);
3096
3097 _reset_cache(d);
3098 _set_params(d);
3099}
3100
3102{
3103 if(IS_NULL_PTR(self->data)) return;
3104 dt_lib_histogram_t *d = self->data;
3105 dt_dev_pixelpipe_cache_wait_cleanup(&d->scope_wait, "histogram-gui-cleanup-scope");
3106 dt_dev_pixelpipe_cache_wait_cleanup(&d->picker_wait, "histogram-gui-cleanup-picker");
3107 dt_dev_pixelpipe_cache_wait_cleanup(&d->module_wait, "histogram-gui-cleanup-module");
3110 if(d->refresh_idle_source != 0)
3111 {
3112 g_source_remove(d->refresh_idle_source);
3113 d->refresh_idle_source = 0;
3114 }
3115 if(d->pending_hashes) g_array_free(d->pending_hashes, TRUE);
3118
3123
3125 self->data = NULL;
3126}
3127
3128// clang-format off
3129// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3130// vim: shiftwidth=2 expandtab tabstop=2 cindent
3131// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3132// 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:1849
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:2064
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:1179
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
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:861
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:906
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
void * dt_alloc_align(size_t size)
Definition darktable.c:446
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...)
Definition darktable.c:1594
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
@ DT_DEBUG_VERBOSE
Definition darktable.h:743
@ DT_DEBUG_DEV
Definition darktable.h:717
#define for_each_channel(_var,...)
Definition darktable.h:662
float dt_boundingbox_t[4]
Definition darktable.h:709
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
Definition darktable.h:433
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
static void dt_get_times(dt_times_t *t)
Definition darktable.h:921
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define for_four_channels(_var,...)
Definition darktable.h:664
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define __OMP_FOR_SIMD__(...)
Definition darktable.h:260
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#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:1144
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1131
void dt_dev_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:1886
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1586
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:104
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:219
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:3102
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:2713
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:316
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:451
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#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:3081
static gboolean dt_iop_colorspace_is_rgb(const dt_iop_colorspace_type_t cst)
Definition imageop.h:213
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
static void gui_init(dt_lib_import_t *d)
Definition import.c:1047
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 _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 _preview_cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
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)
static void _lib_histogram_cacheline_ready_callback(gpointer instance, const guint64 hash, dt_lib_module_t *self)
#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 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:368
@ 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:357
struct _GtkWidget GtkWidget
Definition splash.h:29
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_lib_t * lib
Definition darktable.h:771
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:790
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
int32_t unmuted
Definition darktable.h:760
struct dt_develop_t * develop
Definition darktable.h:770
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:391
dt_backbuf_t display_histogram
Definition develop.h:337
GList * iop
Definition develop.h:279
dt_backbuf_t output_histogram
Definition develop.h:336
gboolean(* refresh_global_picker)(struct dt_lib_module_t *self)
Definition develop.h:399
gboolean restrict_histogram
Definition develop.h:396
int statistic
Definition develop.h:397
GSList * samples
Definition develop.h:392
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:383
dt_backbuf_t raw_histogram
Definition develop.h:335
struct dt_colorpicker_sample_t * selected_sample
Definition develop.h:393
struct dt_lib_module_t * histogram_module
Definition develop.h:398
gboolean display_samples
Definition develop.h:394
gint scroll_mask
Definition gtk.h:224
dt_ui_t * ui
Definition gtk.h:164
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
GtkWidget * widget
Definition imageop.h:337
dt_dev_histogram_stats_t histogram_stats
Definition imageop.h:278
uint32_t histogram_max[4]
Definition imageop.h:280
int histogram_middle_grey
Definition imageop.h:287
uint32_t * histogram
Definition imageop.h:276
dt_iop_colorspace_type_t histogram_cst
Definition imageop.h:285
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
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