Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
studio_capture.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Guillaume STUTIN.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
25#include "develop/masks_gui.h"
26#include "system/atomic.h"
29#include "common/collection.h"
31#include "common/selection.h"
32#include "control/input.h"
33#include "control/control.h"
34#include "develop/develop.h"
35#include "develop/dev_history.h"
37#include "develop/imageop.h"
38#include "develop/masks/masks_history.h" // dt_masks_release_all_forms()
43#include "widgets/gdkkeys.h"
44#include "gui/application.h"
45#include "gui/guides.h"
46#include "libs/colorpicker.h"
47#include "libs/lib.h"
48#include "views/dev_backbuf.h"
49#include "views/dev_toolbox.h"
50#include "views/view.h"
51#include "views/view_api.h"
52
53#include <gdk/gdkkeysyms.h>
54#include <math.h>
55#include "control/signal.h"
56
57DT_MODULE(1)
58
59typedef struct dt_studio_capture_t
60{
62 cairo_surface_t *surface;
63 int32_t imgid;
64
65 // Last known center size, updated on expose.
66 int width;
67 int height;
68
69 // DT_THUMBTABLE_ZOOM_FIT or DT_THUMBTABLE_ZOOM_FULL (100% + panning)
71
72 // Top-left corner of the viewport inside the 100% surface, in surface pixels.
73 double pan_x;
74 double pan_y;
75
76 // The 100% surface arrives asynchronously: remember the relative image point
77 // (0..1) that was under the pointer when zooming so the first 100% expose can
78 // center the pan there.
82 double anchor_px;
83 double anchor_py;
84
85 // Pan dragging state
86 gboolean panning;
91
92 // Color picker box-drag state (image-normalized anchor corner)
94 float picker_box_anchor[2];
95
96 // Deferred refresh after an import: styles are applied right after the
97 // import signal fires, so the first fetched surface may predate them.
99
100 // Own develop instance. While the atelier is active it is published as
101 // darktable.develop and runs the pixelpipe on the displayed image so the
102 // scopes (which read darktable.develop->preview_pipe) have data to show.
103 // At DT_THUMBTABLE_ZOOM_FIT, expose() also prefers this pipe's own live
104 // backbuf (main_locked/main_wait below) over the plain surface fetcher when
105 // it is ready, since that is the only path that bakes in ISO 12646/
106 // overexposed/raw overexposed/softproof/gamut. The fetcher's surface stays
107 // the fallback (cold start, or 100% zoom, which this dev's ROI never
108 // reflects - see _studio_configure_dev_roi) and the only source the color
109 // picker's coordinate mapping (_studio_widget_to_image_norm) ever reads.
111 dt_develop_t *saved_develop; // global develop pointer to restore on leave
112 gboolean dev_loaded; // an image is loaded and its pipelines are set up
113 int dev_width; // last center allocation, for dt_dev_configure()
115
116 // dev->pipe's live backbuf, GUI-side view (see dev_backbuf.h). Must be
117 // released (dt_dev_release_locked_surface) before dev's pipeline nodes are
118 // torn down - see _studio_dev_teardown().
121
122 // The Scopes module's "current pick" readout (numeric label + swatch) is a
123 // GTK widget pair created once, at startup, hard-wired via signal user_data
124 // to whichever develop's primary_sample was live at that moment: always
125 // darkroom's. Our own dt_dev_init()-allocated primary_sample has no such
126 // widgets, so its values compute correctly but never reach the screen. We
127 // share darkroom's instance while active (own_primary_sample stashes ours)
128 // and always restore it before dt_dev_cleanup() would otherwise free
129 // darkroom's shared instance instead of our own.
132
133const char *name(const dt_view_t *self)
134{
135 return _("Studio Capture");
136}
137
138uint32_t view(const dt_view_t *self)
139{
141}
142
143void init(dt_view_t *self)
144{
145 dt_studio_capture_t *d = calloc(1, sizeof(dt_studio_capture_t));
146 d->imgid = UNKNOWN_IMAGE;
148 // calloc() zero-initializes main_locked.hash to 0, which looks like a valid hash, not the
149 // "never locked" sentinel dt_dev_lock_pipe_surface()/dt_dev_release_locked_surface() expect.
150 d->main_locked.hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
152
153 // gui_attached = 1: the scopes only read a develop whose preview pipe is a
154 // GUI-observable source (see the dev->gui_attached guards in libs/histogram.c).
155 d->dev = (dt_develop_t *)malloc(sizeof(dt_develop_t));
156 dt_dev_init(d->dev, 1);
157 d->own_primary_sample = d->dev->color_picker.primary_sample;
158
159 self->data = d;
160}
161
163{
165
166 // Own instances, wired to this view's own dev, so the toggles' state and
167 // their popovers' controls are actually correct here rather than mutating
168 // darkroom's separate dev. The effect still won't be visible: it's
169 // darkroom's own expose() that paints it on the pixelpipe backbuffer, a
170 // path this view's surface-fetcher-based center never touches. Display's
171 // popover only gets the generic controls (brightness, margins) here:
172 // darkroom's own rendering-size and mask-preview-checkerboard extras don't
173 // apply without an editing layer, so we don't append them.
174 static const dt_dev_toolbox_button_t studio_capture_toolbox_buttons[]
177 dt_dev_toolbox_create(d->dev, DT_VIEW_STUDIO_CAPTURE, studio_capture_toolbox_buttons,
178 G_N_ELEMENTS(studio_capture_toolbox_buttons));
179 gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(d->dev->display.floating_window)));
180
181 // Accelerators for the same buttons, bound to the accel group this view
182 // actually connects (see enter(): dt_accels_connect_active_group(...,
183 // "lighttable")) instead of darkroom's darkroom_accels.
184 dt_dev_toolbox_add_accels(d->dev, dt_gui_get_accels()->lighttable_accels, N_("Studio capture/Toolbox"),
185 studio_capture_toolbox_buttons, G_N_ELEMENTS(studio_capture_toolbox_buttons));
186}
187
189{
191 if(d->refresh_timeout) g_source_remove(d->refresh_timeout);
192 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
194 // Guard against leave() never having run (e.g. app shutdown while this view
195 // was active): dt_dev_cleanup() must free our own primary sample, never
196 // darkroom's shared one that enter() may have swapped in.
197 d->dev->color_picker.primary_sample = d->own_primary_sample;
198 dt_dev_cleanup(d->dev);
199 dt_free(d->dev);
200 dt_free(self->data);
201}
202
204{
205 // The viewer can always be entered; it shows whatever image the filmstrip
206 // points at, or an empty center until an image is selected or imported.
207 return 0;
208}
209
232
233static void _studio_dev_setup(dt_studio_capture_t *d, const int32_t imgid)
234{
235 dt_develop_t *dev = d->dev;
236
237 dev->exit = 0;
239 dev->gui_module = NULL;
240
241 if(IS_NULL_PTR(dev->iop)) dev->iop = dt_dev_load_modules(dev);
242
243 // From here the develop holds per-image resources that teardown must release.
244 d->dev_loaded = TRUE;
245
246 if(dt_dev_load_image(dev, imgid))
247 {
248 dt_control_log(_("Studio capture: could not load the image in the viewer."));
249 return;
250 }
251
255
256 // Size the pipelines to the current center allocation so the preview pipe
257 // (the scopes' source) produces a valid buffer.
258 if(d->dev_width > 0 && d->dev_height > 0)
259 _studio_configure_dev_roi(d, d->dev_width, d->dev_height);
260
262}
263
288{
289 if(!d->dev_loaded) return;
290 d->dev_loaded = FALSE;
291
292 // Must happen before the pipe nodes/backbufs below are torn down: main_locked otherwise keeps
293 // referencing a cache entry that is about to be invalidated/reused by the next image's pipe.
294 dt_dev_release_locked_surface(&d->main_locked);
295 dt_dev_pixelpipe_cache_wait_cleanup(&d->main_wait, "studio-capture-release-main");
296
297 dt_develop_t *dev = d->dev;
298
299 dev->exit = 1;
303
304 // Stop module background threads before freeing the nodes/history they read.
305 for(GList *m = dev->iop; m; m = g_list_next(m))
306 {
307 dt_iop_module_t *mod = (dt_iop_module_t *)m->data;
308 if(mod && mod->quiesce) mod->quiesce(mod);
309 }
310
311 // Taking each busy lock waits for the running pipe to release it.
317
324
326 if(dev->preview_pipe->last_devid != dev->pipe->last_devid)
328
332
333 while(dev->iop)
334 {
335 dt_iop_module_t *module = (dt_iop_module_t *)(dev->iop->data);
336 if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
337 dt_iop_cleanup_module(module);
338 dt_free(module);
339 dev->iop = g_list_delete_link(dev->iop, dev->iop);
340 }
341 while(dev->alliop)
342 {
343 dt_iop_module_t *module = (dt_iop_module_t *)dev->alliop->data;
344 if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
345 dt_iop_cleanup_module(module);
346 dt_free(module);
347 dev->alliop = g_list_delete_link(dev->alliop, dev->alliop);
348 }
349 dev->iop = dev->alliop = NULL;
350
351 if(dev->form_gui)
352 {
353 dev->gui_module = NULL;
355 // dt_masks_gui_cleanup() frees but does not clear the pointer; NULL it so a
356 // teardown not followed by a setup (reset to no image) cannot double-free.
357 dev->form_gui = NULL;
358 }
359
361
362 dev->image_storage.id = -1;
363
364 // Release the histogram backbuf cache references.
371}
372
376static void _studio_set_image(dt_studio_capture_t *d, const int32_t imgid)
377{
378 if(imgid == d->imgid) return;
379
380 // Drop the previous image's pipeline before loading the next.
382
383 d->imgid = imgid;
385 d->pan_x = d->pan_y = 0.0;
386 d->anchor_pending = FALSE;
387 d->panning = FALSE;
388 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
389
391 if(imgid > UNKNOWN_IMAGE)
392 {
394 // Keep the library selection in sync with whatever this view displays
395 // (a new import, a filmstrip activate...): darkroom's try_enter() falls
396 // back to the selection, not to our own active-image tracking, and the
397 // user expects "what's shown" and "what's selected" to be the same thing.
399 // darkroom's try_enter() checks mouse_over_id BEFORE falling back to the
400 // selection, and its own enter() sets both mouse_over_id and
401 // keyboard_over_id to the image it is about to load. Mirror that here so
402 // switching to darkroom from Studio Capture resolves to the displayed
403 // image even if a stale mouse_over_id is lingering from filmstrip hover.
406 // Only feed the scopes while the atelier owns the global develop pointer.
407 if(dt_dev_get_global() == d->dev) _studio_dev_setup(d, imgid);
408 }
409
411}
412
413static void _studio_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
414{
415 dt_view_t *self = (dt_view_t *)user_data;
416 if(imgid > UNKNOWN_IMAGE) _studio_set_image((dt_studio_capture_t *)self->data, imgid);
417}
418
422static gboolean _studio_deferred_refresh(gpointer user_data)
423{
425 d->refresh_timeout = 0;
426 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
428 return G_SOURCE_REMOVE;
429}
430
434static void _studio_image_imported_callback(gpointer instance, int32_t imgid, gpointer user_data)
435{
436 dt_view_t *self = (dt_view_t *)user_data;
438 if(imgid <= UNKNOWN_IMAGE) return;
439
440 _studio_set_image(d, imgid);
441
442 // The import signal fires before the survey styles are applied to the new
443 // image, so the surface fetched right away may show the unstyled state.
444 // Schedule one deferred re-fetch after the styles have been written.
445 if(d->refresh_timeout) g_source_remove(d->refresh_timeout);
446 d->refresh_timeout = g_timeout_add(1500, _studio_deferred_refresh, d);
447}
448
453static void _studio_history_changed_callback(gpointer instance, gpointer user_data)
454{
455 dt_view_t *self = (dt_view_t *)user_data;
457 if(d->imgid <= UNKNOWN_IMAGE) return;
458
459 // The style was written straight to DB (common/styles.c uses a throwaway dev, never
460 // d->dev), so d->dev's in-memory history/pipeline are now stale for the displayed
461 // image. Reload and reprocess them here so the scopes/color-picker samples — which
462 // read from d->dev's preview pipe, not from the fetcher's mipmap-based surface —
463 // catch up at the same time as the center image. Skip dt_dev_history_gui_update():
464 // it walks dev->iop expecting module GUIs, which this viewer never initializes.
465 if(d->dev_loaded && dt_dev_get_global() == d->dev)
466 {
467 dt_dev_reload_history_items(d->dev, d->imgid);
469 }
470
471 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
473}
474
475void enter(dt_view_t *self)
476{
478
479 // Publish our develop so the scopes/navigation modules and the pixelpipe read
480 // this viewer's pipeline while the atelier is active.
481 d->saved_develop = dt_dev_get_global();
482 dt_dev_set_global(d->dev);
483
484 // The Scopes module binds its instance and refresh callback onto whichever
485 // develop is active at application startup (always darkroom's, since that
486 // develop is created first). Copy that binding onto our own develop.
487 // The primary sample's readout widgets (numeric label + swatch) are
488 // likewise created once at startup and wired to darkroom's specific
489 // primary_sample struct: share that instance while we are active so the
490 // readout displays here too (own_primary_sample is restored in leave()).
491 const dt_view_t *const darkroom_view = dt_view_manager_get_global()->proxy.darkroom.view;
492 if(!IS_NULL_PTR(darkroom_view) && !IS_NULL_PTR(darkroom_view->data))
493 {
494 const dt_develop_t *const darkroom_dev = (const dt_develop_t *)darkroom_view->data;
495 d->dev->color_picker.histogram_module = darkroom_dev->color_picker.histogram_module;
496 d->dev->color_picker.refresh_global_picker = darkroom_dev->color_picker.refresh_global_picker;
497 if(!IS_NULL_PTR(darkroom_dev->color_picker.primary_sample))
498 d->dev->color_picker.primary_sample = darkroom_dev->color_picker.primary_sample;
499 // The Scopes panel's picker toggle button is the same physical widget
500 // regardless of the active view, wired at startup to darkroom_dev's
501 // dt_iop_color_picker_t. _refresh_active_picker() (gui/color_picker_proxy.c)
502 // bails out unconditionally when dev->color_picker.picker is NULL — without
503 // this, our own dev never resamples on pipe-finished/cacheline-ready events,
504 // so the primary sample keeps showing whatever value darkroom last computed
505 // instead of the image displayed here.
506 d->dev->color_picker.picker = darkroom_dev->color_picker.picker;
507 }
509
511
515
516 // Attach shortcuts
519
520 gtk_widget_show(dt_gui_center_widget());
521 dt_thumbtable_show(dt_gui_get_ui()->thumbtable_filmstrip);
522 dt_thumbtable_update_parent(dt_gui_get_ui()->thumbtable_filmstrip);
523
525 G_CALLBACK(_studio_filmstrip_activate_callback), self);
527 G_CALLBACK(_studio_image_imported_callback), self);
529 G_CALLBACK(_studio_history_changed_callback), self);
530
532
533 // Start from the current selection when there is one.
534 const int32_t imgid = dt_selection_get_first_id(dt_selection_get_global());
535 if(imgid > UNKNOWN_IMAGE) _studio_set_image(d, imgid);
536
537 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
539}
540
541void leave(dt_view_t *self)
542{
544
546 (gpointer)self);
548 (gpointer)self);
550 (gpointer)self);
551
552 if(d->refresh_timeout)
553 {
554 g_source_remove(d->refresh_timeout);
555 d->refresh_timeout = 0;
556 }
557
558 // Release the color picker while our develop is still the global one: both
559 // read/write darktable.develop directly, not a passed-in pointer.
561 if(d->dev->color_picker.picker)
562 dt_iop_color_picker_reset(d->dev->color_picker.picker->module, FALSE);
563 d->picker_dragging_box = FALSE;
564
565 // Stop sharing darkroom's primary sample instance (see enter()): our own
566 // develop must own a real, private instance again before it can be freed.
567 d->dev->color_picker.primary_sample = d->own_primary_sample;
568
569 // Tear down the pipeline while our develop is still the global one, then
570 // restore the previous global develop pointer.
572 dt_dev_set_global(d->saved_develop);
573 // Force a fresh setup on the next enter even if the same image is shown.
574 d->imgid = UNKNOWN_IMAGE;
575
576 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
577 d->panning = FALSE;
578
580
581 dt_thumbtable_hide(dt_gui_get_ui()->thumbtable_filmstrip);
583}
584
585void configure(dt_view_t *self, int width, int height)
586{
588
589 // Only the active view owns a valid center allocation.
591
592 d->dev_width = width;
593 d->dev_height = height;
594
595 // Re-size the running pipelines to the new center allocation.
596 if(d->dev_loaded) _studio_configure_dev_roi(d, width, height);
597}
598
604
608static void _studio_clamp_pan(dt_studio_capture_t *d, const double logical_width, const double logical_height)
609{
610 const double max_x = MAX(0.0, logical_width - d->width);
611 const double max_y = MAX(0.0, logical_height - d->height);
612 d->pan_x = CLAMP(d->pan_x, 0.0, max_x);
613 d->pan_y = CLAMP(d->pan_y, 0.0, max_y);
614}
615
626static gboolean _studio_surface_geometry(const dt_studio_capture_t *d, double *tr_x, double *tr_y,
627 double *logical_width, double *logical_height)
628{
629 if(IS_NULL_PTR(d->surface)) return FALSE;
630
631 const int surface_width = cairo_image_surface_get_width(d->surface);
632 const int surface_height = cairo_image_surface_get_height(d->surface);
633 double sx = 1.0, sy = 1.0;
634 cairo_surface_get_device_scale(d->surface, &sx, &sy);
635 *logical_width = surface_width / sx;
636 *logical_height = surface_height / sy;
637
638 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT)
639 {
640 *tr_x = (d->width < *logical_width) ? 0.0 : (d->width - *logical_width) * .5;
641 *tr_y = (d->height < *logical_height) ? 0.0 : (d->height - *logical_height) * .5;
642 }
643 else
644 {
645 *tr_x = (*logical_width <= d->width) ? (d->width - *logical_width) * .5 : -d->pan_x;
646 *tr_y = (*logical_height <= d->height) ? (d->height - *logical_height) * .5 : -d->pan_y;
647 }
648 return TRUE;
649}
650
659static gboolean _studio_widget_to_image_norm(const dt_studio_capture_t *d, const double x, const double y,
660 float point[2])
661{
662 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
663 if(!_studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height)) return FALSE;
664 if(logical_width <= 0.0 || logical_height <= 0.0) return FALSE;
665
666 point[0] = CLAMP((float)((x - tr_x) / logical_width), 0.0f, 1.0f);
667 point[1] = CLAMP((float)((y - tr_y) / logical_height), 0.0f, 1.0f);
668 return TRUE;
669}
670
674static gboolean _studio_image_norm_to_widget(const dt_studio_capture_t *d, const float point[2], double *x,
675 double *y)
676{
677 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
678 if(!_studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height)) return FALSE;
679
680 *x = tr_x + point[0] * logical_width;
681 *y = tr_y + point[1] * logical_height;
682 return TRUE;
683}
684
698 const gboolean is_primary_sample, const gboolean selected)
699{
700 const double line_scale = selected ? 2.0 : 1.0;
701
702 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
703 {
704 float image_box[4] = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
706 double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0;
707 if(!_studio_image_norm_to_widget(d, &image_box[0], &x0, &y0)
708 || !_studio_image_norm_to_widget(d, &image_box[2], &x1, &y1))
709 return;
710
711 cairo_rectangle(cr, x0, y0, x1 - x0, y1 - y0);
712 if(is_primary_sample)
713 {
714 const double hw = 5.0;
715 cairo_rectangle(cr, x0 - hw, y0 - hw, 2. * hw, 2. * hw);
716 cairo_rectangle(cr, x0 - hw, y1 - hw, 2. * hw, 2. * hw);
717 cairo_rectangle(cr, x1 - hw, y0 - hw, 2. * hw, 2. * hw);
718 cairo_rectangle(cr, x1 - hw, y1 - hw, 2. * hw, 2. * hw);
719 }
720
721 cairo_set_line_width(cr, 3.0 * line_scale);
722 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.4);
723 cairo_stroke_preserve(cr);
724
725 const gboolean draw_dashed = !is_primary_sample && !selected;
726 const double dashes[1] = { 4.0 };
727 cairo_set_line_width(cr, line_scale);
728 cairo_set_dash(cr, dashes, draw_dashed ? 1 : 0, 0.0);
729 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.8);
730 cairo_stroke(cr);
731 cairo_set_dash(cr, NULL, 0, 0.0);
732 // Darkroom does not fill a swatch on-canvas for box samples either: their
733 // color readout only shows in the Scopes panel's live sample list.
734 }
735 else
736 {
737 float image_point[2] = { sample->point[0], sample->point[1] };
738 dt_dev_coordinates_raw_norm_to_image_norm(d->dev, image_point, 1);
739 double px = 0.0, py = 0.0;
740 if(!_studio_image_norm_to_widget(d, image_point, &px, &py)) return;
741
742 // Darkroom's constant-size fallback (min_half_px_device): the zoom-scaled
743 // half_px it computes almost always bottoms out at this value in practice.
744 const double half_px = 4.0;
745 double crosshair = (is_primary_sample ? 4.0 : 5.0) * half_px;
746 if(selected) crosshair *= 2.0;
747
748 if(is_primary_sample) cairo_arc(cr, px, py, crosshair, 0., 2. * M_PI);
749 cairo_move_to(cr, px - crosshair, py);
750 cairo_line_to(cr, px + crosshair, py);
751 cairo_move_to(cr, px, py - crosshair);
752 cairo_line_to(cr, px, py + crosshair);
753
754 cairo_set_line_width(cr, 3.0 * line_scale);
755 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.4);
756 cairo_stroke_preserve(cr);
757
758 cairo_set_line_width(cr, line_scale);
759 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.8);
760 cairo_stroke(cr);
761
762 // Darkroom fills a rectangle here only when zoomed in enough that half_px
763 // reflects an actual preview-pixel size; at our constant device-pixel
764 // half_px that case does not apply, so the swatch is always a circle
765 // (radius doubled for the selected sample), matching darkroom's own
766 // fallback behavior at typical (non-extreme) zoom levels.
767 cairo_arc(cr, px, py, selected ? half_px * 2.0 : half_px, 0., 2. * M_PI);
768 cairo_set_source_rgba(cr, sample->swatch.red, sample->swatch.green, sample->swatch.blue,
769 sample->swatch.alpha);
770 cairo_fill(cr);
771 }
772}
773
781{
782 dt_develop_t *dev = d->dev;
783 dt_colorpicker_sample_t *const selected_sample = dev->color_picker.selected_sample;
784
785 cairo_save(cr);
786 cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
787
788 if(dev->color_picker.samples
790 || (selected_sample && selected_sample != dev->color_picker.primary_sample)))
791 {
792 const gboolean only_selected = selected_sample && !dev->color_picker.display_samples;
793 for(GSList *l = dev->color_picker.samples; l; l = g_slist_next(l))
794 {
795 dt_colorpicker_sample_t *sample = l->data;
796 if(only_selected && sample != selected_sample) continue;
797 _studio_draw_one_sample(d, cr, sample, FALSE, sample == selected_sample);
798 }
799 }
800
803 dev->color_picker.primary_sample == selected_sample);
804
805 cairo_restore(cr);
806}
807
808void expose(dt_view_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
809{
811 d->width = width;
812 d->height = height;
813
814 // The view's configure() callback only fires on window resizes, not on view
815 // entry. Size the running pipelines here so the preview pipe (the scopes'
816 // source) always gets a valid ROI, and follow later center resizes.
817 if(d->dev_loaded && (d->dev_width != width || d->dev_height != height))
818 {
819 d->dev_width = width;
820 d->dev_height = height;
822 }
823
824 dt_aligned_pixel_t bg_color = { 0.0f };
825 dt_dev_get_background_color(d->dev, bg_color);
826 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
827 cairo_paint(cr);
828
829 if(d->imgid <= UNKNOWN_IMAGE) return;
830
831 // Always fetch/refresh the mipmap surface first: _studio_widget_to_image_norm() (the color
832 // picker's click-to-image-point mapping, in button_pressed/mouse_moved) keys off its
833 // dimensions via _studio_surface_geometry(), so both must stay valid and current even on
834 // frames where the live backbuf below ends up being what actually gets painted. Cheap: this
835 // is a cached async fetch, not a re-decode, on frames where nothing relevant changed.
837 = dt_view_image_get_surface_async(&d->fetcher, d->imgid, MAX(2, width), MAX(2, height), &d->surface,
838 dt_gui_center_widget(), d->zoom);
839 if(res != DT_VIEW_SURFACE_OK || IS_NULL_PTR(d->surface))
840 {
843 return;
844 }
845
846 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
847 // First 100% frame after a zoom request: place the image point that was
848 // under the pointer back under the pointer. Needs the surface's logical
849 // size, so peek it once before the shared geometry helper applies pan/clamp.
850 if(d->zoom != DT_THUMBTABLE_ZOOM_FIT && d->anchor_pending)
851 {
852 const int surface_width = cairo_image_surface_get_width(d->surface);
853 const int surface_height = cairo_image_surface_get_height(d->surface);
854 double sx = 1.0, sy = 1.0;
855 cairo_surface_get_device_scale(d->surface, &sx, &sy);
856 d->pan_x = d->anchor_rel_x * (surface_width / sx) - d->anchor_px;
857 d->pan_y = d->anchor_rel_y * (surface_height / sy) - d->anchor_py;
858 d->anchor_pending = FALSE;
859 }
860 _studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height);
861 if(d->zoom != DT_THUMBTABLE_ZOOM_FIT) _studio_clamp_pan(d, logical_width, logical_height);
862
863 // Prefer the live main pipe's backbuf over the plain mipmap thumbnail: it is what actually
864 // bakes in ISO 12646/overexposed/raw overexposed/softproof/gamut, none of which the mipmap
865 // fetcher above is aware of. Only meaningful at FIT: d->dev->roi never reflects zoom/pan (see
866 // _studio_configure_dev_roi), so at 100% this pipe is always framed as "fit", not "100%" -
867 // trying to show it there would just display the wrong framing under the wrong label. Falls
868 // back to the mipmap surface (translated/sized by the geometry above) whenever the live
869 // backbuf is not ready yet (cold start, image just switched) or zoom is 100%.
870 gboolean drew_live = FALSE;
871 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT
872 && dt_dev_lock_pipe_surface(d->dev, d->dev->pipe, &d->main_locked, &d->main_wait, "studio-capture-main", TRUE)
873 && d->main_locked.surface)
874 {
875 // dt_dev_render_locked_surface() translates cr directly with no save/restore of its own:
876 // darkroom always calls it on a throwaway intermediate context it discards right after, but
877 // this view draws everything (including the guides below) on the same cr, so the translate
878 // must not leak past this call.
879 cairo_save(cr);
880 drew_live = dt_dev_render_locked_surface(cr, d->dev, &d->main_locked, width, height,
881 dt_dev_viewport_border_size(d->dev), bg_color);
882 cairo_restore(cr);
883 }
884
885 if(!drew_live)
886 {
887 cairo_save(cr);
888 cairo_translate(cr, tr_x, tr_y);
889 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT && d->dev->iso_12646.enabled)
890 dt_dev_draw_iso12646_border(cr, logical_width, logical_height, dt_dev_viewport_border_size(d->dev));
891 cairo_set_source_surface(cr, d->surface, 0, 0);
892 cairo_pattern_set_filter(cairo_get_source(cr), dt_widget_image_filter());
893 cairo_rectangle(cr, 0, 0, logical_width, logical_height);
894 cairo_fill(cr);
895 cairo_restore(cr);
896 }
897
898 // Guide lines: the toggle button (darktable.view_manager->guides_toggle, wired up in
899 // darkroom.c's gui_init) is already shared into this view's toolbox, but its state and
900 // dt_guides_draw() itself read only global conf, independent of any dev - only the
901 // clip/transform setup below is dev-driven. Only meaningful at FIT, same reasoning as the
902 // live backbuf above: d->dev->roi always describes "fit", never this view's own 100%/pan.
903 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT)
904 {
905 const float wd = dt_dev_roi_request_preview_width(d->dev);
906 const float ht = dt_dev_roi_request_preview_height(d->dev);
907 const float scaling = dt_dev_get_overlay_scale(d->dev);
908
909 cairo_save(cr);
910 // don't draw guides on image margins
911 dt_dev_clip_roi(d->dev, cr, width, height);
912 // place origin at top-left corner of image
913 dt_dev_rescale_roi(d->dev, cr, width, height);
914
915 dt_guides_draw(cr, 0, 0, wd, ht, scaling);
916 cairo_restore(cr);
917 }
918
921}
922
926static void _studio_toggle_zoom(dt_studio_capture_t *d, const double x, const double y)
927{
928 if(d->imgid <= UNKNOWN_IMAGE) return;
929
930 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT)
931 {
933
934 // Convert the pointer position to a relative image point using the
935 // currently displayed fit surface.
936 d->anchor_rel_x = 0.5;
937 d->anchor_rel_y = 0.5;
938 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
939 if(_studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height) && logical_width > 0.0
940 && logical_height > 0.0)
941 {
942 d->anchor_rel_x = CLAMP((x - tr_x) / logical_width, 0.0, 1.0);
943 d->anchor_rel_y = CLAMP((y - tr_y) / logical_height, 0.0, 1.0);
944 }
945 d->anchor_px = x;
946 d->anchor_py = y;
947 d->anchor_pending = TRUE;
948 }
949 else
950 {
952 d->anchor_pending = FALSE;
953 }
954
955 d->pan_x = d->pan_y = 0.0;
956 d->panning = FALSE;
958}
959
960void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
961{
963
965 {
966 if(d->picker_dragging_box)
967 {
968 float point[2] = { 0.0f };
970 {
971 const dt_boundingbox_t box = { MIN(d->picker_box_anchor[0], point[0]),
972 MIN(d->picker_box_anchor[1], point[1]),
973 MAX(d->picker_box_anchor[0], point[0]),
974 MAX(d->picker_box_anchor[1], point[1]) };
977 }
978 }
979 else if(d->dev->color_picker.primary_sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
980 {
981 // Point mode: the picker follows the pointer for as long as the button
982 // stays down, matching darkroom's mouse_moved.
983 float point[2] = { 0.0f };
985 {
988 }
989 }
990 return;
991 }
992
993 if(!d->panning) return;
994
995 d->pan_x = d->pan_start_x - (x - d->drag_start_x);
996 d->pan_y = d->pan_start_y - (y - d->drag_start_y);
998}
999
1010static void _studio_picker_left_click(dt_studio_capture_t *d, const float point[2], const double logical_width,
1011 const double logical_height)
1012{
1013 dt_colorpicker_sample_t *const sample = d->dev->color_picker.primary_sample;
1014
1015 if(sample->size != DT_LIB_COLORPICKER_SIZE_BOX)
1016 {
1017 d->picker_dragging_box = FALSE;
1019 return;
1020 }
1021
1022 float image_box[4] = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
1023 dt_dev_coordinates_raw_norm_to_image_norm(d->dev, image_box, 2);
1024
1025 // 6 widget pixels of corner-grab slop, converted to image-normalized units.
1026 const float handle_x = (logical_width > 0.0) ? (float)(6.0 / logical_width) : 0.02f;
1027 const float handle_y = (logical_height > 0.0) ? (float)(6.0 / logical_height) : 0.02f;
1028
1029 gboolean on_corner = TRUE;
1030 float opposite[2] = { 0.0f };
1031 if(fabsf(point[0] - image_box[0]) <= handle_x) opposite[0] = image_box[2];
1032 else if(fabsf(point[0] - image_box[2]) <= handle_x) opposite[0] = image_box[0];
1033 else on_corner = FALSE;
1034
1035 if(fabsf(point[1] - image_box[1]) <= handle_y) opposite[1] = image_box[3];
1036 else if(fabsf(point[1] - image_box[3]) <= handle_y) opposite[1] = image_box[1];
1037 else on_corner = FALSE;
1038
1039 if(on_corner)
1040 {
1041 d->picker_box_anchor[0] = opposite[0];
1042 d->picker_box_anchor[1] = opposite[1];
1043 }
1044 else
1045 {
1046 // The default box is a small square, 1% of the image width, centered on the click.
1047 const float delta = 0.01f;
1048 d->picker_box_anchor[0] = point[0];
1049 d->picker_box_anchor[1] = point[1];
1050 const dt_boundingbox_t box = { fmaxf(0.0f, point[0] - delta), fmaxf(0.0f, point[1] - delta),
1051 fminf(1.0f, point[0] + delta), fminf(1.0f, point[1] + delta) };
1053 }
1054
1055 d->picker_dragging_box = TRUE;
1056 dt_control_change_cursor(GDK_FLEUR);
1057}
1058
1064static void _studio_picker_right_click(dt_studio_capture_t *d, const float point[2], const double logical_width,
1065 const double logical_height)
1066{
1067 dt_develop_t *const dev = d->dev;
1069
1071 {
1072 const float slop_x = (logical_width > 0.0) ? (float)(20.0 / logical_width) : 0.05f;
1073 const float slop_y = (logical_height > 0.0) ? (float)(20.0 / logical_height) : 0.05f;
1074
1075 for(GSList *l = dev->color_picker.samples; l; l = g_slist_next(l))
1076 {
1077 dt_colorpicker_sample_t *live_sample = l->data;
1078 if(live_sample->size == DT_LIB_COLORPICKER_SIZE_BOX && sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1079 {
1080 float live_box[4] = { live_sample->box[0], live_sample->box[1], live_sample->box[2],
1081 live_sample->box[3] };
1083 if(point[0] < live_box[0] || point[0] > live_box[2] || point[1] < live_box[1] || point[1] > live_box[3])
1084 continue;
1086 return;
1087 }
1088 else if(live_sample->size == DT_LIB_COLORPICKER_SIZE_POINT && sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
1089 {
1090 float live_point[2] = { live_sample->point[0], live_sample->point[1] };
1092 if(fabsf(point[0] - live_point[0]) > slop_x || fabsf(point[1] - live_point[1]) > slop_y) continue;
1094 return;
1095 }
1096 }
1097 }
1098
1099 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1100 {
1101 const dt_boundingbox_t reset = { 0.01f, 0.01f, 0.99f, 0.99f };
1103 }
1104}
1105
1106int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
1107{
1109
1110 // The color picker owns the center view while active: it takes precedence
1111 // over zoom toggling and panning, exactly like darkroom.
1112 if(dt_iop_color_picker_is_visible(d->dev) && (which == 1 || which == 3))
1113 {
1114 float point[2] = { 0.0f };
1115 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
1116 _studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height);
1117
1119 {
1120 if(which == 1)
1121 _studio_picker_left_click(d, point, logical_width, logical_height);
1122 else
1123 _studio_picker_right_click(d, point, logical_width, logical_height);
1125 }
1126 return 1;
1127 }
1128
1129 if(which == 2 || (which == 1 && type == GDK_2BUTTON_PRESS))
1130 {
1131 _studio_toggle_zoom(d, x, y);
1132 return 1;
1133 }
1134
1135 if(which == 1 && d->zoom == DT_THUMBTABLE_ZOOM_FULL)
1136 {
1137 d->panning = TRUE;
1138 d->drag_start_x = x;
1139 d->drag_start_y = y;
1140 d->pan_start_x = d->pan_x;
1141 d->pan_start_y = d->pan_y;
1142 dt_control_change_cursor(GDK_HAND1);
1143 return 1;
1144 }
1145
1146 return 0;
1147}
1148
1149int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
1150{
1152
1153 if(dt_iop_color_picker_is_visible(d->dev) && which == 1)
1154 {
1155 if(d->picker_dragging_box) dt_control_change_cursor(GDK_LEFT_PTR);
1156 d->picker_dragging_box = FALSE;
1158 return 1;
1159 }
1160
1161 if(d->panning && which == 1)
1162 {
1163 d->panning = FALSE;
1164 dt_control_change_cursor(GDK_LEFT_PTR);
1165 return 1;
1166 }
1167 return 0;
1168}
1169
1170int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
1171{
1173
1174 if(up && d->zoom == DT_THUMBTABLE_ZOOM_FIT)
1175 {
1176 _studio_toggle_zoom(d, x, y);
1177 return 1;
1178 }
1179 if(!up && d->zoom == DT_THUMBTABLE_ZOOM_FULL)
1180 {
1181 _studio_toggle_zoom(d, x, y);
1182 return 1;
1183 }
1184 return 0;
1185}
1186
1187int key_pressed(dt_view_t *self, GdkEventKey *event)
1188{
1190
1191 const gboolean shift = dt_modifier_is(event->state, GDK_SHIFT_MASK);
1192 const gboolean ctrl = dt_modifier_is(event->state, DT_PRIMARY_MASK);
1193 const gboolean ctrl_any = dt_modifiers_include(event->state, DT_PRIMARY_MASK);
1194 guint key = dt_keys_mainpad_alternatives(event->keyval);
1195
1196 // Studio Capture's visible surface is driven by d->zoom/pan_x/pan_y (see
1197 // expose()), not by d->dev->roi: that develop only feeds the Scopes module
1198 // in the background and never reaches the screen. Panning or zooming it
1199 // has no visible effect, so all navigation below must go through the same
1200 // fields the mouse handlers (button_pressed/mouse_moved/scrolled) use.
1201 if(ctrl_any && (key == GDK_KEY_plus || key == GDK_KEY_minus))
1202 {
1203 if((key == GDK_KEY_plus) == (d->zoom == DT_THUMBTABLE_ZOOM_FIT))
1204 _studio_toggle_zoom(d, d->width / 2.0, d->height / 2.0);
1205 return 1;
1206 }
1207
1208 const double multiplier = shift ? 4.0 : ctrl ? 0.5 : 1.0;
1209 const double step = 10.0 * multiplier;
1210
1211 switch(key)
1212 {
1213 case GDK_KEY_Return:
1214 {
1215 if(d->imgid > UNKNOWN_IMAGE)
1216 {
1217 // _studio_set_image() already synced the global selection to d->imgid.
1219 }
1220 else
1221 dt_control_log(_("No image to open in darkroom."));
1222 return 1;
1223 }
1224 case GDK_KEY_Up:
1225 {
1226 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1227 d->pan_y -= step;
1229 return 1;
1230 }
1231 case GDK_KEY_Down:
1232 {
1233 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1234 d->pan_y += step;
1236 return 1;
1237 }
1238 case GDK_KEY_Left:
1239 {
1240 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1241 d->pan_x -= step;
1243 return 1;
1244 }
1245 case GDK_KEY_Right:
1246 {
1247 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1248 d->pan_x += step;
1250 return 1;
1251 }
1252 case GDK_KEY_Escape:
1253 {
1254 dt_ctl_switch_mode_to("lighttable");
1255 return TRUE;
1256 }
1257 }
1258
1259 return 0;
1260}
1261
1262// clang-format off
1263// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1264// vim: shiftwidth=2 expandtab tabstop=2 cindent
1265// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1266// clang-format on
void dt_accels_connect_accels(dt_accels_t *accels)
Actually enable accelerators after having loaded user config.
void dt_accels_connect_active_group(dt_accels_t *accels, const gchar *group)
Connect the contextual active accels group to the window. Views can declare their own set of contextu...
void dt_accels_disconnect_active_group(dt_accels_t *accels)
Disconnect the contextual active accels group from the window.
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
struct dt_accels_t * dt_gui_get_accels(void)
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
void dt_gui_refocus_center()
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
#define m
Definition basecurve.c:283
static const float scaling
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
Definition collection.c:837
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:155
@ DT_COLLECTION_CHANGE_RELOAD
Definition collection.h:164
void dt_iop_color_picker_init(void)
void dt_iop_color_picker_cleanup(void)
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
@ DT_LIB_COLORPICKER_SIZE_POINT
Definition colorpicker.h:38
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:39
static const float x
const float delta
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:701
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:530
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:994
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
gboolean dt_control_button_down(int which)
Is mouse button which currently held down?
Definition control.c:563
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:1020
#define dt_control_change_cursor(cursor)
Definition control.h:121
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:621
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
void dt_dev_set_global(struct dt_develop_t *dev)
Definition darktable.c:543
void dt_dev_draw_iso12646_border(cairo_t *cr, double width, double height, int border)
Definition dev_backbuf.c:56
gboolean dt_dev_lock_pipe_surface(dt_develop_t *dev, dt_dev_pixelpipe_t *pipe, dt_dev_locked_surface_t *locked, dt_dev_pixelpipe_cache_wait_t *wait, const char *wait_owner_tag, gboolean keep_previous_on_fail)
gboolean dt_dev_render_locked_surface(cairo_t *cr, const dt_develop_t *dev, dt_dev_locked_surface_t *locked, const int width, const int height, const int border, const dt_aligned_pixel_t bg_color)
void dt_dev_get_background_color(const dt_develop_t *dev, dt_aligned_pixel_t bg_color)
Definition dev_backbuf.c:47
void dt_dev_release_locked_surface(dt_dev_locked_surface_t *locked)
Definition dev_backbuf.c:99
void dt_dev_draw_profile_mode_label(cairo_t *cri, int height)
Definition dev_backbuf.c:69
void dt_dev_history_free_history(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Free the whole history list attached to dev->history.
void dt_dev_pop_history_items(dt_develop_t *dev)
Thread-safe wrapper around dt_dev_pop_history_items_ext(), then update GUI.
void dt_dev_history_pixelpipe_update(dt_develop_t *dev, gboolean rebuild)
Rebuild or resync pixelpipes after backend history changes.
gboolean dt_dev_reload_history_items(dt_develop_t *dev, const int32_t imgid)
Reload history from DB and rebuild pipelines/GUI state.
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.
#define dt_dev_pixelpipe_rebuild_all(dev)
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
void dt_dev_toolbox_create(dt_develop_t *dev, dt_view_type_flags_t views, const dt_dev_toolbox_button_t *buttons, size_t n_buttons)
void dt_dev_toolbox_apply_iso_12646_size(dt_develop_t *dev)
Definition dev_toolbox.c:39
void dt_dev_toolbox_add_accels(dt_develop_t *dev, GtkAccelGroup *accel_group, const char *category, const dt_dev_toolbox_button_t *buttons, size_t n_buttons)
dt_dev_toolbox_button_t
Definition dev_toolbox.h:28
@ DT_DEV_TOOLBOX_DISPLAY
Definition dev_toolbox.h:34
@ DT_DEV_TOOLBOX_GAMUT
Definition dev_toolbox.h:33
@ DT_DEV_TOOLBOX_RAWOVEREXPOSED
Definition dev_toolbox.h:31
@ DT_DEV_TOOLBOX_OVEREXPOSED
Definition dev_toolbox.h:30
@ DT_DEV_TOOLBOX_SOFTPROOF
Definition dev_toolbox.h:32
@ DT_DEV_TOOLBOX_ISO_12646
Definition dev_toolbox.h:29
gboolean dt_dev_viewport_set_widget_size(dt_develop_t *dev, const int32_t widget_width, const int32_t widget_height)
int32_t dt_dev_viewport_border_size(const dt_develop_t *dev)
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:353
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:237
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:1000
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:143
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1278
GList * dt_dev_load_modules(dt_develop_t *dev)
Definition develop.c:98
void dt_dev_set_backbuf(dt_backbuf_t *backbuf, const int width, const int height, const size_t bpp, const int64_t hash, const int64_t history_hash)
Definition develop.c:2134
gboolean dt_dev_clip_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Clip the view to the ROI. WARNING: this must be done before any translation.
Definition develop.c:2028
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:2071
void dt_dev_start_all_pipelines(dt_develop_t *dev)
Definition develop.c:908
void dt_ui_panel_show(dt_ui_t *ui, const dt_ui_panel_t p, gboolean show, gboolean write)
shows/hide a panel
Definition display.c:129
GHashTable * selected
set of checked row labels, mirrored to conf on every change
GtkWidget * view
the tree view
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
const int res
Definition dtpthread.h:351
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
void dt_guides_draw(cairo_t *cr, const float left, const float top, const float width, const float height, const float zoom_scale)
Definition guides.c:800
#define UNKNOWN_IMAGE
Definition image.h:78
float dt_boundingbox_t[4]
Definition image.h:83
void dt_iop_cleanup_module(dt_iop_module_t *module)
Definition imageop.c:1113
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:736
void dt_iop_gui_cleanup_module(dt_iop_module_t *module)
Which mouse buttons are held, asked of the toolkit rather than remembered.
void dt_lib_colorpicker_set_box_area(dt_lib_t *lib, const dt_boundingbox_t box)
Definition lib.c:1229
void dt_lib_colorpicker_set_point(dt_lib_t *lib, const float pos[2])
Definition lib.c:1251
_lib_location_type_t type
Definition location.c:1
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
void dt_masks_gui_cleanup(dt_develop_t *dev)
Definition masks_gui.c:1436
void dt_masks_gui_init(dt_develop_t *dev)
Definition masks_gui.c:1420
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
void dt_masks_release_all_forms(dt_develop_t *dev)
#define M_PI
Definition math.h:47
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
const char * name
Definition pdf.h:90
void dt_dev_pixelpipe_cache_flush_clmem_for_pipe(const int devid)
Like dt_dev_pixelpipe_cache_flush_clmem(), for callers that do not hold the device lock (dt_opencl_re...
void dt_dev_pixelpipe_cache_unref_hash(const uint64_t hash)
Find the entry matching hash, and decrease its ref_count if found.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
void dt_dev_pixelpipe_cleanup_nodes(dt_dev_pixelpipe_t *pipe)
static void dt_dev_backbuf_set_hash(dt_backbuf_t *backbuf, const uint64_t hash)
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
int32_t dt_selection_get_first_id(struct dt_selection_t *selection)
Definition selection.c:103
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:297
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:98
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:207
@ DT_SIGNAL_IMAGE_IMPORT
This signal is raised when a new image is imported (not cloned) 1 uint32_t : the new image id no retu...
Definition signal.h:251
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
dt_lib_colorpicker_size_t size
Definition colorpicker.h:64
dt_boundingbox_t box
Definition colorpicker.h:63
dt_pthread_mutex_t busy_mutex
dt_backbuf_t backbuf
dt_atomic_int shutdown
dt_image_t image_storage
Definition develop.h:225
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:374
dt_backbuf_t display_histogram
Definition develop.h:320
GList * iop
Definition develop.h:269
dt_backbuf_t output_histogram
Definition develop.h:319
gboolean(* refresh_global_picker)(struct dt_lib_module_t *self)
Definition develop.h:382
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
GSList * samples
Definition develop.h:375
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
gboolean pipelines_started
Definition develop.h:328
GList * alliop
Definition develop.h:271
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:366
dt_backbuf_t raw_histogram
Definition develop.h:318
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
struct dt_colorpicker_sample_t * selected_sample
Definition develop.h:376
struct dt_lib_module_t * histogram_module
Definition develop.h:381
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
gboolean display_samples
Definition develop.h:377
int32_t id
Definition image.h:401
One consumer's outstanding request, owned by that consumer, not by the queue.
dt_view_image_surface_fetcher_t fetcher
dt_thumbtable_zoom_t zoom
dt_develop_t * saved_develop
cairo_surface_t * surface
dt_colorpicker_sample_t * own_primary_sample
dt_dev_locked_surface_t main_locked
dt_dev_pixelpipe_cache_wait_t main_wait
Track one asynchronous Cairo surface fetch request for a GUI widget.
Definition view.h:119
struct dt_view_t * view
Definition view.h:237
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@63 darkroom
GModule *void * data
Definition view.h:159
void init(dt_view_t *self)
static gboolean _studio_deferred_refresh(gpointer user_data)
Re-fetch the displayed surface once, after auto-styles had time to land.
void leave(dt_view_t *self)
int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
static void _studio_clamp_pan(dt_studio_capture_t *d, const double logical_width, const double logical_height)
Clamp the pan offsets so the viewport never leaves the surface.
void cleanup(dt_view_t *self)
static void _studio_history_changed_callback(gpointer instance, gpointer user_data)
Refresh the display when the history of the shown image changes (e.g. styles re-applied manually from...
static void _studio_set_image(dt_studio_capture_t *d, const int32_t imgid)
Switch the displayed image and reset the viewport to fit.
void reset(dt_view_t *self)
static void _studio_configure_dev_roi(dt_studio_capture_t *d, int width, int height)
Load an image into the viewer develop and start its pipelines.
static void _studio_image_imported_callback(gpointer instance, int32_t imgid, gpointer user_data)
Follow the shooting session: every new import becomes the displayed image.
void expose(dt_view_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
static void _studio_toggle_zoom(dt_studio_capture_t *d, const double x, const double y)
Toggle between fit and 100%, anchoring the zoom on the pointer.
int key_pressed(dt_view_t *self, GdkEventKey *event)
void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
static void _studio_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
static void _studio_dev_setup(dt_studio_capture_t *d, const int32_t imgid)
static gboolean _studio_surface_geometry(const dt_studio_capture_t *d, double *tr_x, double *tr_y, double *logical_width, double *logical_height)
Compute where the displayed surface currently sits in widget space.
int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
void enter(dt_view_t *self)
static void _studio_draw_one_sample(dt_studio_capture_t *d, cairo_t *cr, dt_colorpicker_sample_t *sample, const gboolean is_primary_sample, const gboolean selected)
Draw one point or box color picker sample, matching darkroom's _darkroom_pickers_draw visual style: a...
static void _studio_picker_left_click(dt_studio_capture_t *d, const float point[2], const double logical_width, const double logical_height)
Left-click handling while the color picker is active.
void configure(dt_view_t *self, int width, int height)
static void _studio_draw_pickers(dt_studio_capture_t *d, cairo_t *cr)
Draw the live samples list, then the active picker, matching darkroom's two-pass _darkroom_pickers_dr...
static void _studio_dev_teardown(dt_studio_capture_t *d)
Tear down the viewer develop pipelines and per-image resources.
void gui_init(dt_view_t *self)
static gboolean _studio_widget_to_image_norm(const dt_studio_capture_t *d, const double x, const double y, float point[2])
Convert a widget-space point to a normalized [0,1] image point.
static gboolean _studio_image_norm_to_widget(const dt_studio_capture_t *d, const float point[2], double *x, double *y)
Inverse of _studio_widget_to_image_norm(), for drawing the picker overlay.
int try_enter(dt_view_t *self)
static void _studio_picker_right_click(dt_studio_capture_t *d, const float point[2], const double logical_width, const double logical_height)
Right-click handling while the color picker is active: reuse an overlapping live sample's geometry (m...
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
int dt_thumbtable_scroll_to_selection(dt_thumbtable_t *table)
Scroll to show selected content.
Definition thumbtable.c:457
void dt_thumbtable_update_parent(dt_thumbtable_t *table)
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
dt_thumbtable_zoom_t
Zoom levels for thumbnail display.
Definition thumbtable.h:76
@ DT_THUMBTABLE_ZOOM_FULL
Definition thumbtable.h:79
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:77
static void dt_thumbtable_show(dt_thumbtable_t *table)
Show the thumbnail table widget.
Definition thumbtable.h:395
static void dt_thumbtable_hide(dt_thumbtable_t *table)
Hide the thumbnail table widget.
Definition thumbtable.h:410
void dt_view_image_surface_fetcher_invalidate(dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target)
Definition view.c:871
void dt_view_image_surface_fetcher_cleanup(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:847
void dt_view_image_surface_fetcher_init(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:836
dt_view_surface_value_t dt_view_image_get_surface_async(dt_view_image_surface_fetcher_t *fetcher, int32_t imgid, int width, int height, cairo_surface_t **target, GtkWidget *widget, int zoom)
Definition view.c:892
void dt_view_active_images_add(int32_t imgid, gboolean raise)
Definition view.c:1276
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1266
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:234
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
@ DT_VIEW_STUDIO_CAPTURE
Definition view.h:78
dt_view_surface_value_t
Definition view.h:104
@ DT_VIEW_SURFACE_OK
Definition view.h:105
cairo_filter_t dt_widget_image_filter(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
static gboolean dt_modifiers_include(GdkModifierType state, const GdkModifierType desired_modifier_mask)
@ DT_UI_PANEL_BOTTOM
@ DT_UI_PANEL_LEFT
@ DT_UI_PANEL_RIGHT