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 "common/atomic.h"
26#include "common/collection.h"
27#include "common/darktable.h"
28#include "common/debug.h"
29#include "common/selection.h"
30#include "control/conf.h"
31#include "control/control.h"
32#include "develop/develop.h"
33#include "develop/dev_history.h"
35#include "develop/imageop.h"
36#include "develop/masks.h"
39#include "dtgtk/thumbtable.h"
41#include "gui/gdkkeys.h"
42#include "gui/gtk.h"
43#include "gui/guides.h"
44#include "libs/colorpicker.h"
45#include "libs/lib.h"
46#include "views/dev_backbuf.h"
47#include "views/dev_toolbox.h"
48#include "views/view.h"
49#include "views/view_api.h"
50
51#include <gdk/gdkkeysyms.h>
52#include <math.h>
53
54DT_MODULE(1)
55
56typedef struct dt_studio_capture_t
57{
59 cairo_surface_t *surface;
60 int32_t imgid;
61
62 // Last known center size, updated on expose.
63 int width;
64 int height;
65
66 // DT_THUMBTABLE_ZOOM_FIT or DT_THUMBTABLE_ZOOM_FULL (100% + panning)
68
69 // Top-left corner of the viewport inside the 100% surface, in surface pixels.
70 double pan_x;
71 double pan_y;
72
73 // The 100% surface arrives asynchronously: remember the relative image point
74 // (0..1) that was under the pointer when zooming so the first 100% expose can
75 // center the pan there.
79 double anchor_px;
80 double anchor_py;
81
82 // Pan dragging state
83 gboolean panning;
88
89 // Color picker box-drag state (image-normalized anchor corner)
91 float picker_box_anchor[2];
92
93 // Deferred refresh after an import: styles are applied right after the
94 // import signal fires, so the first fetched surface may predate them.
96
97 // Own develop instance. While the atelier is active it is published as
98 // darktable.develop and runs the pixelpipe on the displayed image so the
99 // scopes (which read darktable.develop->preview_pipe) have data to show.
100 // At DT_THUMBTABLE_ZOOM_FIT, expose() also prefers this pipe's own live
101 // backbuf (main_locked/main_wait below) over the plain surface fetcher when
102 // it is ready, since that is the only path that bakes in ISO 12646/
103 // overexposed/raw overexposed/softproof/gamut. The fetcher's surface stays
104 // the fallback (cold start, or 100% zoom, which this dev's ROI never
105 // reflects - see _studio_configure_dev_roi) and the only source the color
106 // picker's coordinate mapping (_studio_widget_to_image_norm) ever reads.
108 dt_develop_t *saved_develop; // global develop pointer to restore on leave
109 gboolean dev_loaded; // an image is loaded and its pipelines are set up
110 int dev_width; // last center allocation, for dt_dev_configure()
112
113 // dev->pipe's live backbuf, GUI-side view (see dev_backbuf.h). Must be
114 // released (dt_dev_release_locked_surface) before dev's pipeline nodes are
115 // torn down - see _studio_dev_teardown().
118
119 // The Scopes module's "current pick" readout (numeric label + swatch) is a
120 // GTK widget pair created once, at startup, hard-wired via signal user_data
121 // to whichever develop's primary_sample was live at that moment: always
122 // darkroom's. Our own dt_dev_init()-allocated primary_sample has no such
123 // widgets, so its values compute correctly but never reach the screen. We
124 // share darkroom's instance while active (own_primary_sample stashes ours)
125 // and always restore it before dt_dev_cleanup() would otherwise free
126 // darkroom's shared instance instead of our own.
129
130const char *name(const dt_view_t *self)
131{
132 return _("Studio Capture");
133}
134
135uint32_t view(const dt_view_t *self)
136{
138}
139
140void init(dt_view_t *self)
141{
142 dt_studio_capture_t *d = calloc(1, sizeof(dt_studio_capture_t));
143 d->imgid = UNKNOWN_IMAGE;
145 // calloc() zero-initializes main_locked.hash to 0, which looks like a valid hash, not the
146 // "never locked" sentinel dt_dev_lock_pipe_surface()/dt_dev_release_locked_surface() expect.
147 d->main_locked.hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
149
150 // gui_attached = 1: the scopes only read a develop whose preview pipe is a
151 // GUI-observable source (see the dev->gui_attached guards in libs/histogram.c).
152 d->dev = (dt_develop_t *)malloc(sizeof(dt_develop_t));
153 dt_dev_init(d->dev, 1);
154 d->own_primary_sample = d->dev->color_picker.primary_sample;
155
156 self->data = d;
157}
158
160{
162
163 // Own instances, wired to this view's own dev, so the toggles' state and
164 // their popovers' controls are actually correct here rather than mutating
165 // darkroom's separate dev. The effect still won't be visible: it's
166 // darkroom's own expose() that paints it on the pixelpipe backbuffer, a
167 // path this view's surface-fetcher-based center never touches. Display's
168 // popover only gets the generic controls (brightness, margins) here:
169 // darkroom's own rendering-size and mask-preview-checkerboard extras don't
170 // apply without an editing layer, so we don't append them.
171 static const dt_dev_toolbox_button_t studio_capture_toolbox_buttons[]
174 dt_dev_toolbox_create(d->dev, DT_VIEW_STUDIO_CAPTURE, studio_capture_toolbox_buttons,
175 G_N_ELEMENTS(studio_capture_toolbox_buttons));
176 gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(d->dev->display.floating_window)));
177
178 // Accelerators for the same buttons, bound to the accel group this view
179 // actually connects (see enter(): dt_accels_connect_active_group(...,
180 // "lighttable")) instead of darkroom's darkroom_accels.
181 dt_dev_toolbox_add_accels(d->dev, darktable.gui->accels->lighttable_accels, N_("Studio capture/Toolbox"),
182 studio_capture_toolbox_buttons, G_N_ELEMENTS(studio_capture_toolbox_buttons));
183}
184
186{
188 if(d->refresh_timeout) g_source_remove(d->refresh_timeout);
189 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
191 // Guard against leave() never having run (e.g. app shutdown while this view
192 // was active): dt_dev_cleanup() must free our own primary sample, never
193 // darkroom's shared one that enter() may have swapped in.
194 d->dev->color_picker.primary_sample = d->own_primary_sample;
195 dt_dev_cleanup(d->dev);
196 dt_free(d->dev);
197 dt_free(self->data);
198}
199
201{
202 // The viewer can always be entered; it shows whatever image the filmstrip
203 // points at, or an empty center until an image is selected or imported.
204 return 0;
205}
206
225{
226 d->dev->roi.orig_width = width;
227 d->dev->roi.orig_height = height;
229}
230
231static void _studio_dev_setup(dt_studio_capture_t *d, const int32_t imgid)
232{
233 dt_develop_t *dev = d->dev;
234
235 dev->exit = 0;
237 dev->gui_module = NULL;
238
239 if(IS_NULL_PTR(dev->iop)) dev->iop = dt_dev_load_modules(dev);
240
241 // From here the develop holds per-image resources that teardown must release.
242 d->dev_loaded = TRUE;
243
244 if(dt_dev_load_image(dev, imgid))
245 {
246 dt_control_log(_("Studio capture: could not load the image in the viewer."));
247 return;
248 }
249
253
254 // Size the pipelines to the current center allocation so the preview pipe
255 // (the scopes' source) produces a valid buffer.
256 if(d->dev_width > 0 && d->dev_height > 0)
257 _studio_configure_dev_roi(d, d->dev_width, d->dev_height);
258
260}
261
286{
287 if(!d->dev_loaded) return;
288 d->dev_loaded = FALSE;
289
290 // Must happen before the pipe nodes/backbufs below are torn down: main_locked otherwise keeps
291 // referencing a cache entry that is about to be invalidated/reused by the next image's pipe.
292 dt_dev_release_locked_surface(&d->main_locked);
293 dt_dev_pixelpipe_cache_wait_cleanup(&d->main_wait, "studio-capture-release-main");
294
295 dt_develop_t *dev = d->dev;
296
297 dev->exit = 1;
302
303 // Stop module background threads before freeing the nodes/history they read.
304 for(GList *m = dev->iop; m; m = g_list_next(m))
305 {
306 dt_iop_module_t *mod = (dt_iop_module_t *)m->data;
307 if(mod && mod->quiesce) mod->quiesce(mod);
308 }
309
310 // Taking each busy lock waits for the running pipe to release it.
316
324
325 if(dev->virtual_pipe)
326 {
334 }
335
337 if(dev->preview_pipe->last_devid != dev->pipe->last_devid)
339
343
344 while(dev->iop)
345 {
346 dt_iop_module_t *module = (dt_iop_module_t *)(dev->iop->data);
347 if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
348 dt_iop_cleanup_module(module);
349 dt_free(module);
350 dev->iop = g_list_delete_link(dev->iop, dev->iop);
351 }
352 while(dev->alliop)
353 {
354 dt_iop_module_t *module = (dt_iop_module_t *)dev->alliop->data;
355 if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
356 dt_iop_cleanup_module(module);
357 dt_free(module);
358 dev->alliop = g_list_delete_link(dev->alliop, dev->alliop);
359 }
360 dev->iop = dev->alliop = NULL;
361
362 if(dev->form_gui)
363 {
364 dev->gui_module = NULL;
366 // dt_masks_gui_cleanup() frees but does not clear the pointer; NULL it so a
367 // teardown not followed by a setup (reset to no image) cannot double-free.
368 dev->form_gui = NULL;
369 }
370
372 g_list_free_full(dev->forms, (void (*)(void *))dt_masks_free_form);
373 dev->forms = NULL;
374 g_list_free_full(dev->allforms, (void (*)(void *))dt_masks_free_form);
375 dev->allforms = NULL;
377
378 dev->image_storage.id = -1;
379
380 // Release the histogram backbuf cache references.
387}
388
392static void _studio_set_image(dt_studio_capture_t *d, const int32_t imgid)
393{
394 if(imgid == d->imgid) return;
395
396 // Drop the previous image's pipeline before loading the next.
398
399 d->imgid = imgid;
401 d->pan_x = d->pan_y = 0.0;
402 d->anchor_pending = FALSE;
403 d->panning = FALSE;
404 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
405
407 if(imgid > UNKNOWN_IMAGE)
408 {
410 // Keep the library selection in sync with whatever this view displays
411 // (a new import, a filmstrip activate...): darkroom's try_enter() falls
412 // back to the selection, not to our own active-image tracking, and the
413 // user expects "what's shown" and "what's selected" to be the same thing.
415 // darkroom's try_enter() checks mouse_over_id BEFORE falling back to the
416 // selection, and its own enter() sets both mouse_over_id and
417 // keyboard_over_id to the image it is about to load. Mirror that here so
418 // switching to darkroom from Studio Capture resolves to the displayed
419 // image even if a stale mouse_over_id is lingering from filmstrip hover.
422 // Only feed the scopes while the atelier owns the global develop pointer.
423 if(darktable.develop == d->dev) _studio_dev_setup(d, imgid);
424 }
425
427}
428
429static void _studio_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
430{
431 dt_view_t *self = (dt_view_t *)user_data;
432 if(imgid > UNKNOWN_IMAGE) _studio_set_image((dt_studio_capture_t *)self->data, imgid);
433}
434
438static gboolean _studio_deferred_refresh(gpointer user_data)
439{
441 d->refresh_timeout = 0;
442 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
444 return G_SOURCE_REMOVE;
445}
446
450static void _studio_image_imported_callback(gpointer instance, int32_t imgid, gpointer user_data)
451{
452 dt_view_t *self = (dt_view_t *)user_data;
454 if(imgid <= UNKNOWN_IMAGE) return;
455
456 _studio_set_image(d, imgid);
457
458 // The import signal fires before the survey styles are applied to the new
459 // image, so the surface fetched right away may show the unstyled state.
460 // Schedule one deferred re-fetch after the styles have been written.
461 if(d->refresh_timeout) g_source_remove(d->refresh_timeout);
462 d->refresh_timeout = g_timeout_add(1500, _studio_deferred_refresh, d);
463}
464
469static void _studio_history_changed_callback(gpointer instance, gpointer user_data)
470{
471 dt_view_t *self = (dt_view_t *)user_data;
473 if(d->imgid <= UNKNOWN_IMAGE) return;
474
475 // The style was written straight to DB (common/styles.c uses a throwaway dev, never
476 // d->dev), so d->dev's in-memory history/pipeline are now stale for the displayed
477 // image. Reload and reprocess them here so the scopes/color-picker samples — which
478 // read from d->dev's preview pipe, not from the fetcher's mipmap-based surface —
479 // catch up at the same time as the center image. Skip dt_dev_history_gui_update():
480 // it walks dev->iop expecting module GUIs, which this viewer never initializes.
481 if(d->dev_loaded && darktable.develop == d->dev)
482 {
483 dt_dev_reload_history_items(d->dev, d->imgid);
485 }
486
487 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
489}
490
491void enter(dt_view_t *self)
492{
494
495 // Publish our develop so the scopes/navigation modules and the pixelpipe read
496 // this viewer's pipeline while the atelier is active.
497 d->saved_develop = darktable.develop;
498 darktable.develop = d->dev;
499
500 // The Scopes module binds its instance and refresh callback onto whichever
501 // develop is active at application startup (always darkroom's, since that
502 // develop is created first). Copy that binding onto our own develop.
503 // The primary sample's readout widgets (numeric label + swatch) are
504 // likewise created once at startup and wired to darkroom's specific
505 // primary_sample struct: share that instance while we are active so the
506 // readout displays here too (own_primary_sample is restored in leave()).
507 const dt_view_t *const darkroom_view = darktable.view_manager->proxy.darkroom.view;
508 if(!IS_NULL_PTR(darkroom_view) && !IS_NULL_PTR(darkroom_view->data))
509 {
510 const dt_develop_t *const darkroom_dev = (const dt_develop_t *)darkroom_view->data;
511 d->dev->color_picker.histogram_module = darkroom_dev->color_picker.histogram_module;
512 d->dev->color_picker.refresh_global_picker = darkroom_dev->color_picker.refresh_global_picker;
513 if(!IS_NULL_PTR(darkroom_dev->color_picker.primary_sample))
514 d->dev->color_picker.primary_sample = darkroom_dev->color_picker.primary_sample;
515 // The Scopes panel's picker toggle button is the same physical widget
516 // regardless of the active view, wired at startup to darkroom_dev's
517 // dt_iop_color_picker_t. _refresh_active_picker() (gui/color_picker_proxy.c)
518 // bails out unconditionally when dev->color_picker.picker is NULL — without
519 // this, our own dev never resamples on pipe-finished/cacheline-ready events,
520 // so the primary sample keeps showing whatever value darkroom last computed
521 // instead of the image displayed here.
522 d->dev->color_picker.picker = darkroom_dev->color_picker.picker;
523 }
525
527
531
532 // Attach shortcuts
535
536 gtk_widget_show(dt_ui_center(darktable.gui->ui));
539
541 G_CALLBACK(_studio_filmstrip_activate_callback), self);
543 G_CALLBACK(_studio_image_imported_callback), self);
545 G_CALLBACK(_studio_history_changed_callback), self);
546
548
549 // Start from the current selection when there is one.
550 const int32_t imgid = dt_selection_get_first_id(darktable.selection);
551 if(imgid > UNKNOWN_IMAGE) _studio_set_image(d, imgid);
552
555}
556
557void leave(dt_view_t *self)
558{
560
562 (gpointer)self);
564 (gpointer)self);
566 (gpointer)self);
567
568 if(d->refresh_timeout)
569 {
570 g_source_remove(d->refresh_timeout);
571 d->refresh_timeout = 0;
572 }
573
574 // Release the color picker while our develop is still the global one: both
575 // read/write darktable.develop directly, not a passed-in pointer.
577 if(d->dev->color_picker.picker)
578 dt_iop_color_picker_reset(d->dev->color_picker.picker->module, FALSE);
579 d->picker_dragging_box = FALSE;
580
581 // Stop sharing darkroom's primary sample instance (see enter()): our own
582 // develop must own a real, private instance again before it can be freed.
583 d->dev->color_picker.primary_sample = d->own_primary_sample;
584
585 // Tear down the pipeline while our develop is still the global one, then
586 // restore the previous global develop pointer.
588 darktable.develop = d->saved_develop;
589 // Force a fresh setup on the next enter even if the same image is shown.
590 d->imgid = UNKNOWN_IMAGE;
591
592 dt_view_image_surface_fetcher_invalidate(&d->fetcher, &d->surface);
593 d->panning = FALSE;
594
596
599}
600
601void configure(dt_view_t *self, int width, int height)
602{
604
605 // Only the active view owns a valid center allocation.
607
608 d->dev_width = width;
609 d->dev_height = height;
610
611 // Re-size the running pipelines to the new center allocation.
612 if(d->dev_loaded) _studio_configure_dev_roi(d, width, height);
613}
614
620
624static void _studio_clamp_pan(dt_studio_capture_t *d, const double logical_width, const double logical_height)
625{
626 const double max_x = MAX(0.0, logical_width - d->width);
627 const double max_y = MAX(0.0, logical_height - d->height);
628 d->pan_x = CLAMP(d->pan_x, 0.0, max_x);
629 d->pan_y = CLAMP(d->pan_y, 0.0, max_y);
630}
631
642static gboolean _studio_surface_geometry(const dt_studio_capture_t *d, double *tr_x, double *tr_y,
643 double *logical_width, double *logical_height)
644{
645 if(IS_NULL_PTR(d->surface)) return FALSE;
646
647 const int surface_width = cairo_image_surface_get_width(d->surface);
648 const int surface_height = cairo_image_surface_get_height(d->surface);
649 double sx = 1.0, sy = 1.0;
650 cairo_surface_get_device_scale(d->surface, &sx, &sy);
651 *logical_width = surface_width / sx;
652 *logical_height = surface_height / sy;
653
654 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT)
655 {
656 *tr_x = (d->width < *logical_width) ? 0.0 : (d->width - *logical_width) * .5;
657 *tr_y = (d->height < *logical_height) ? 0.0 : (d->height - *logical_height) * .5;
658 }
659 else
660 {
661 *tr_x = (*logical_width <= d->width) ? (d->width - *logical_width) * .5 : -d->pan_x;
662 *tr_y = (*logical_height <= d->height) ? (d->height - *logical_height) * .5 : -d->pan_y;
663 }
664 return TRUE;
665}
666
675static gboolean _studio_widget_to_image_norm(const dt_studio_capture_t *d, const double x, const double y,
676 float point[2])
677{
678 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
679 if(!_studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height)) return FALSE;
680 if(logical_width <= 0.0 || logical_height <= 0.0) return FALSE;
681
682 point[0] = CLAMP((float)((x - tr_x) / logical_width), 0.0f, 1.0f);
683 point[1] = CLAMP((float)((y - tr_y) / logical_height), 0.0f, 1.0f);
684 return TRUE;
685}
686
690static gboolean _studio_image_norm_to_widget(const dt_studio_capture_t *d, const float point[2], double *x,
691 double *y)
692{
693 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
694 if(!_studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height)) return FALSE;
695
696 *x = tr_x + point[0] * logical_width;
697 *y = tr_y + point[1] * logical_height;
698 return TRUE;
699}
700
714 const gboolean is_primary_sample, const gboolean selected)
715{
716 const double line_scale = selected ? 2.0 : 1.0;
717
718 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
719 {
720 float image_box[4] = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
722 double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0;
723 if(!_studio_image_norm_to_widget(d, &image_box[0], &x0, &y0)
724 || !_studio_image_norm_to_widget(d, &image_box[2], &x1, &y1))
725 return;
726
727 cairo_rectangle(cr, x0, y0, x1 - x0, y1 - y0);
728 if(is_primary_sample)
729 {
730 const double hw = 5.0;
731 cairo_rectangle(cr, x0 - hw, y0 - hw, 2. * hw, 2. * hw);
732 cairo_rectangle(cr, x0 - hw, y1 - hw, 2. * hw, 2. * hw);
733 cairo_rectangle(cr, x1 - hw, y0 - hw, 2. * hw, 2. * hw);
734 cairo_rectangle(cr, x1 - hw, y1 - hw, 2. * hw, 2. * hw);
735 }
736
737 cairo_set_line_width(cr, 3.0 * line_scale);
738 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.4);
739 cairo_stroke_preserve(cr);
740
741 const gboolean draw_dashed = !is_primary_sample && !selected;
742 const double dashes[1] = { 4.0 };
743 cairo_set_line_width(cr, line_scale);
744 cairo_set_dash(cr, dashes, draw_dashed ? 1 : 0, 0.0);
745 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.8);
746 cairo_stroke(cr);
747 cairo_set_dash(cr, NULL, 0, 0.0);
748 // Darkroom does not fill a swatch on-canvas for box samples either: their
749 // color readout only shows in the Scopes panel's live sample list.
750 }
751 else
752 {
753 float image_point[2] = { sample->point[0], sample->point[1] };
754 dt_dev_coordinates_raw_norm_to_image_norm(d->dev, image_point, 1);
755 double px = 0.0, py = 0.0;
756 if(!_studio_image_norm_to_widget(d, image_point, &px, &py)) return;
757
758 // Darkroom's constant-size fallback (min_half_px_device): the zoom-scaled
759 // half_px it computes almost always bottoms out at this value in practice.
760 const double half_px = 4.0;
761 double crosshair = (is_primary_sample ? 4.0 : 5.0) * half_px;
762 if(selected) crosshair *= 2.0;
763
764 if(is_primary_sample) cairo_arc(cr, px, py, crosshair, 0., 2. * M_PI);
765 cairo_move_to(cr, px - crosshair, py);
766 cairo_line_to(cr, px + crosshair, py);
767 cairo_move_to(cr, px, py - crosshair);
768 cairo_line_to(cr, px, py + crosshair);
769
770 cairo_set_line_width(cr, 3.0 * line_scale);
771 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.4);
772 cairo_stroke_preserve(cr);
773
774 cairo_set_line_width(cr, line_scale);
775 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.8);
776 cairo_stroke(cr);
777
778 // Darkroom fills a rectangle here only when zoomed in enough that half_px
779 // reflects an actual preview-pixel size; at our constant device-pixel
780 // half_px that case does not apply, so the swatch is always a circle
781 // (radius doubled for the selected sample), matching darkroom's own
782 // fallback behavior at typical (non-extreme) zoom levels.
783 cairo_arc(cr, px, py, selected ? half_px * 2.0 : half_px, 0., 2. * M_PI);
784 cairo_set_source_rgba(cr, sample->swatch.red, sample->swatch.green, sample->swatch.blue,
785 sample->swatch.alpha);
786 cairo_fill(cr);
787 }
788}
789
797{
798 dt_develop_t *dev = d->dev;
799 dt_colorpicker_sample_t *const selected_sample = dev->color_picker.selected_sample;
800
801 cairo_save(cr);
802 cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
803
804 if(dev->color_picker.samples
806 || (selected_sample && selected_sample != dev->color_picker.primary_sample)))
807 {
808 const gboolean only_selected = selected_sample && !dev->color_picker.display_samples;
809 for(GSList *l = dev->color_picker.samples; l; l = g_slist_next(l))
810 {
811 dt_colorpicker_sample_t *sample = l->data;
812 if(only_selected && sample != selected_sample) continue;
813 _studio_draw_one_sample(d, cr, sample, FALSE, sample == selected_sample);
814 }
815 }
816
819 dev->color_picker.primary_sample == selected_sample);
820
821 cairo_restore(cr);
822}
823
824void expose(dt_view_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
825{
827 d->width = width;
828 d->height = height;
829
830 // The view's configure() callback only fires on window resizes, not on view
831 // entry. Size the running pipelines here so the preview pipe (the scopes'
832 // source) always gets a valid ROI, and follow later center resizes.
833 if(d->dev_loaded && (d->dev_width != width || d->dev_height != height))
834 {
835 d->dev_width = width;
836 d->dev_height = height;
838 }
839
840 dt_aligned_pixel_t bg_color = { 0.0f };
841 dt_dev_get_background_color(d->dev, bg_color);
842 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
843 cairo_paint(cr);
844
845 if(d->imgid <= UNKNOWN_IMAGE) return;
846
847 // Always fetch/refresh the mipmap surface first: _studio_widget_to_image_norm() (the color
848 // picker's click-to-image-point mapping, in button_pressed/mouse_moved) keys off its
849 // dimensions via _studio_surface_geometry(), so both must stay valid and current even on
850 // frames where the live backbuf below ends up being what actually gets painted. Cheap: this
851 // is a cached async fetch, not a re-decode, on frames where nothing relevant changed.
853 = dt_view_image_get_surface_async(&d->fetcher, d->imgid, MAX(2, width), MAX(2, height), &d->surface,
854 dt_ui_center(darktable.gui->ui), d->zoom);
855 if(res != DT_VIEW_SURFACE_OK || IS_NULL_PTR(d->surface))
856 {
859 return;
860 }
861
862 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
863 // First 100% frame after a zoom request: place the image point that was
864 // under the pointer back under the pointer. Needs the surface's logical
865 // size, so peek it once before the shared geometry helper applies pan/clamp.
866 if(d->zoom != DT_THUMBTABLE_ZOOM_FIT && d->anchor_pending)
867 {
868 const int surface_width = cairo_image_surface_get_width(d->surface);
869 const int surface_height = cairo_image_surface_get_height(d->surface);
870 double sx = 1.0, sy = 1.0;
871 cairo_surface_get_device_scale(d->surface, &sx, &sy);
872 d->pan_x = d->anchor_rel_x * (surface_width / sx) - d->anchor_px;
873 d->pan_y = d->anchor_rel_y * (surface_height / sy) - d->anchor_py;
874 d->anchor_pending = FALSE;
875 }
876 _studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height);
877 if(d->zoom != DT_THUMBTABLE_ZOOM_FIT) _studio_clamp_pan(d, logical_width, logical_height);
878
879 // Prefer the live main pipe's backbuf over the plain mipmap thumbnail: it is what actually
880 // bakes in ISO 12646/overexposed/raw overexposed/softproof/gamut, none of which the mipmap
881 // fetcher above is aware of. Only meaningful at FIT: d->dev->roi never reflects zoom/pan (see
882 // _studio_configure_dev_roi), so at 100% this pipe is always framed as "fit", not "100%" -
883 // trying to show it there would just display the wrong framing under the wrong label. Falls
884 // back to the mipmap surface (translated/sized by the geometry above) whenever the live
885 // backbuf is not ready yet (cold start, image just switched) or zoom is 100%.
886 gboolean drew_live = FALSE;
887 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT
888 && dt_dev_lock_pipe_surface(d->dev, d->dev->pipe, &d->main_locked, &d->main_wait, "studio-capture-main", TRUE)
889 && d->main_locked.surface)
890 {
891 // dt_dev_render_locked_surface() translates cr directly with no save/restore of its own:
892 // darkroom always calls it on a throwaway intermediate context it discards right after, but
893 // this view draws everything (including the guides below) on the same cr, so the translate
894 // must not leak past this call.
895 cairo_save(cr);
896 drew_live = dt_dev_render_locked_surface(cr, d->dev, &d->main_locked, width, height,
897 d->dev->roi.border_size, bg_color);
898 cairo_restore(cr);
899 }
900
901 if(!drew_live)
902 {
903 cairo_save(cr);
904 cairo_translate(cr, tr_x, tr_y);
905 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT && d->dev->iso_12646.enabled)
906 dt_dev_draw_iso12646_border(cr, logical_width, logical_height, d->dev->roi.border_size);
907 cairo_set_source_surface(cr, d->surface, 0, 0);
908 cairo_pattern_set_filter(cairo_get_source(cr), darktable.gui->filter_image);
909 cairo_rectangle(cr, 0, 0, logical_width, logical_height);
910 cairo_fill(cr);
911 cairo_restore(cr);
912 }
913
914 // Guide lines: the toggle button (darktable.view_manager->guides_toggle, wired up in
915 // darkroom.c's gui_init) is already shared into this view's toolbox, but its state and
916 // dt_guides_draw() itself read only global conf, independent of any dev - only the
917 // clip/transform setup below is dev-driven. Only meaningful at FIT, same reasoning as the
918 // live backbuf above: d->dev->roi always describes "fit", never this view's own 100%/pan.
919 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT)
920 {
921 const float wd = d->dev->roi.preview_width;
922 const float ht = d->dev->roi.preview_height;
923 const float scaling = dt_dev_get_overlay_scale(d->dev);
924
925 cairo_save(cr);
926 // don't draw guides on image margins
927 dt_dev_clip_roi(d->dev, cr, width, height);
928 // place origin at top-left corner of image
929 dt_dev_rescale_roi(d->dev, cr, width, height);
930
931 dt_guides_draw(cr, 0, 0, wd, ht, scaling);
932 cairo_restore(cr);
933 }
934
937}
938
942static void _studio_toggle_zoom(dt_studio_capture_t *d, const double x, const double y)
943{
944 if(d->imgid <= UNKNOWN_IMAGE) return;
945
946 if(d->zoom == DT_THUMBTABLE_ZOOM_FIT)
947 {
949
950 // Convert the pointer position to a relative image point using the
951 // currently displayed fit surface.
952 d->anchor_rel_x = 0.5;
953 d->anchor_rel_y = 0.5;
954 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
955 if(_studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height) && logical_width > 0.0
956 && logical_height > 0.0)
957 {
958 d->anchor_rel_x = CLAMP((x - tr_x) / logical_width, 0.0, 1.0);
959 d->anchor_rel_y = CLAMP((y - tr_y) / logical_height, 0.0, 1.0);
960 }
961 d->anchor_px = x;
962 d->anchor_py = y;
963 d->anchor_pending = TRUE;
964 }
965 else
966 {
968 d->anchor_pending = FALSE;
969 }
970
971 d->pan_x = d->pan_y = 0.0;
972 d->panning = FALSE;
974}
975
976void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
977{
979
982 {
983 if(d->picker_dragging_box)
984 {
985 float point[2] = { 0.0f };
987 {
988 const dt_boundingbox_t box = { MIN(d->picker_box_anchor[0], point[0]),
989 MIN(d->picker_box_anchor[1], point[1]),
990 MAX(d->picker_box_anchor[0], point[0]),
991 MAX(d->picker_box_anchor[1], point[1]) };
994 }
995 }
996 else if(d->dev->color_picker.primary_sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
997 {
998 // Point mode: the picker follows the pointer for as long as the button
999 // stays down, matching darkroom's mouse_moved.
1000 float point[2] = { 0.0f };
1002 {
1005 }
1006 }
1007 return;
1008 }
1009
1010 if(!d->panning) return;
1011
1012 d->pan_x = d->pan_start_x - (x - d->drag_start_x);
1013 d->pan_y = d->pan_start_y - (y - d->drag_start_y);
1015}
1016
1027static void _studio_picker_left_click(dt_studio_capture_t *d, const float point[2], const double logical_width,
1028 const double logical_height)
1029{
1030 dt_colorpicker_sample_t *const sample = d->dev->color_picker.primary_sample;
1031
1032 if(sample->size != DT_LIB_COLORPICKER_SIZE_BOX)
1033 {
1034 d->picker_dragging_box = FALSE;
1036 return;
1037 }
1038
1039 float image_box[4] = { sample->box[0], sample->box[1], sample->box[2], sample->box[3] };
1040 dt_dev_coordinates_raw_norm_to_image_norm(d->dev, image_box, 2);
1041
1042 // 6 widget pixels of corner-grab slop, converted to image-normalized units.
1043 const float handle_x = (logical_width > 0.0) ? (float)(6.0 / logical_width) : 0.02f;
1044 const float handle_y = (logical_height > 0.0) ? (float)(6.0 / logical_height) : 0.02f;
1045
1046 gboolean on_corner = TRUE;
1047 float opposite[2] = { 0.0f };
1048 if(fabsf(point[0] - image_box[0]) <= handle_x) opposite[0] = image_box[2];
1049 else if(fabsf(point[0] - image_box[2]) <= handle_x) opposite[0] = image_box[0];
1050 else on_corner = FALSE;
1051
1052 if(fabsf(point[1] - image_box[1]) <= handle_y) opposite[1] = image_box[3];
1053 else if(fabsf(point[1] - image_box[3]) <= handle_y) opposite[1] = image_box[1];
1054 else on_corner = FALSE;
1055
1056 if(on_corner)
1057 {
1058 d->picker_box_anchor[0] = opposite[0];
1059 d->picker_box_anchor[1] = opposite[1];
1060 }
1061 else
1062 {
1063 // The default box is a small square, 1% of the image width, centered on the click.
1064 const float delta = 0.01f;
1065 d->picker_box_anchor[0] = point[0];
1066 d->picker_box_anchor[1] = point[1];
1067 const dt_boundingbox_t box = { fmaxf(0.0f, point[0] - delta), fmaxf(0.0f, point[1] - delta),
1068 fminf(1.0f, point[0] + delta), fminf(1.0f, point[1] + delta) };
1070 }
1071
1072 d->picker_dragging_box = TRUE;
1073 dt_control_change_cursor(GDK_FLEUR);
1074}
1075
1081static void _studio_picker_right_click(dt_studio_capture_t *d, const float point[2], const double logical_width,
1082 const double logical_height)
1083{
1084 dt_develop_t *const dev = d->dev;
1086
1088 {
1089 const float slop_x = (logical_width > 0.0) ? (float)(20.0 / logical_width) : 0.05f;
1090 const float slop_y = (logical_height > 0.0) ? (float)(20.0 / logical_height) : 0.05f;
1091
1092 for(GSList *l = dev->color_picker.samples; l; l = g_slist_next(l))
1093 {
1094 dt_colorpicker_sample_t *live_sample = l->data;
1095 if(live_sample->size == DT_LIB_COLORPICKER_SIZE_BOX && sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1096 {
1097 float live_box[4] = { live_sample->box[0], live_sample->box[1], live_sample->box[2],
1098 live_sample->box[3] };
1100 if(point[0] < live_box[0] || point[0] > live_box[2] || point[1] < live_box[1] || point[1] > live_box[3])
1101 continue;
1103 return;
1104 }
1105 else if(live_sample->size == DT_LIB_COLORPICKER_SIZE_POINT && sample->size == DT_LIB_COLORPICKER_SIZE_POINT)
1106 {
1107 float live_point[2] = { live_sample->point[0], live_sample->point[1] };
1109 if(fabsf(point[0] - live_point[0]) > slop_x || fabsf(point[1] - live_point[1]) > slop_y) continue;
1111 return;
1112 }
1113 }
1114 }
1115
1116 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
1117 {
1118 const dt_boundingbox_t reset = { 0.01f, 0.01f, 0.99f, 0.99f };
1120 }
1121}
1122
1123int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
1124{
1126
1127 // The color picker owns the center view while active: it takes precedence
1128 // over zoom toggling and panning, exactly like darkroom.
1129 if(dt_iop_color_picker_is_visible(d->dev) && (which == 1 || which == 3))
1130 {
1131 float point[2] = { 0.0f };
1132 double tr_x = 0.0, tr_y = 0.0, logical_width = 0.0, logical_height = 0.0;
1133 _studio_surface_geometry(d, &tr_x, &tr_y, &logical_width, &logical_height);
1134
1136 {
1137 if(which == 1)
1138 _studio_picker_left_click(d, point, logical_width, logical_height);
1139 else
1140 _studio_picker_right_click(d, point, logical_width, logical_height);
1142 }
1143 return 1;
1144 }
1145
1146 if(which == 2 || (which == 1 && type == GDK_2BUTTON_PRESS))
1147 {
1148 _studio_toggle_zoom(d, x, y);
1149 return 1;
1150 }
1151
1152 if(which == 1 && d->zoom == DT_THUMBTABLE_ZOOM_FULL)
1153 {
1154 d->panning = TRUE;
1155 d->drag_start_x = x;
1156 d->drag_start_y = y;
1157 d->pan_start_x = d->pan_x;
1158 d->pan_start_y = d->pan_y;
1159 dt_control_change_cursor(GDK_HAND1);
1160 return 1;
1161 }
1162
1163 return 0;
1164}
1165
1166int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
1167{
1169
1170 if(dt_iop_color_picker_is_visible(d->dev) && which == 1)
1171 {
1172 if(d->picker_dragging_box) dt_control_change_cursor(GDK_LEFT_PTR);
1173 d->picker_dragging_box = FALSE;
1175 return 1;
1176 }
1177
1178 if(d->panning && which == 1)
1179 {
1180 d->panning = FALSE;
1181 dt_control_change_cursor(GDK_LEFT_PTR);
1182 return 1;
1183 }
1184 return 0;
1185}
1186
1187int scrolled(dt_view_t *self, double x, double y, int up, int state, int delta_y)
1188{
1190
1191 if(up && d->zoom == DT_THUMBTABLE_ZOOM_FIT)
1192 {
1193 _studio_toggle_zoom(d, x, y);
1194 return 1;
1195 }
1196 if(!up && d->zoom == DT_THUMBTABLE_ZOOM_FULL)
1197 {
1198 _studio_toggle_zoom(d, x, y);
1199 return 1;
1200 }
1201 return 0;
1202}
1203
1204int key_pressed(dt_view_t *self, GdkEventKey *event)
1205{
1207
1208 const gboolean shift = dt_modifier_is(event->state, GDK_SHIFT_MASK);
1209 const gboolean ctrl = dt_modifier_is(event->state, GDK_CONTROL_MASK);
1210 const gboolean ctrl_any = dt_modifiers_include(event->state, GDK_CONTROL_MASK);
1211 guint key = dt_keys_mainpad_alternatives(event->keyval);
1212
1213 // Studio Capture's visible surface is driven by d->zoom/pan_x/pan_y (see
1214 // expose()), not by d->dev->roi: that develop only feeds the Scopes module
1215 // in the background and never reaches the screen. Panning or zooming it
1216 // has no visible effect, so all navigation below must go through the same
1217 // fields the mouse handlers (button_pressed/mouse_moved/scrolled) use.
1218 if(ctrl_any && (key == GDK_KEY_plus || key == GDK_KEY_minus))
1219 {
1220 if((key == GDK_KEY_plus) == (d->zoom == DT_THUMBTABLE_ZOOM_FIT))
1221 _studio_toggle_zoom(d, d->width / 2.0, d->height / 2.0);
1222 return 1;
1223 }
1224
1225 const double multiplier = shift ? 4.0 : ctrl ? 0.5 : 1.0;
1226 const double step = 10.0 * multiplier;
1227
1228 switch(key)
1229 {
1230 case GDK_KEY_Return:
1231 {
1232 if(d->imgid > UNKNOWN_IMAGE)
1233 {
1234 // _studio_set_image() already synced darktable.selection to d->imgid.
1236 }
1237 else
1238 dt_control_log(_("No image to open in darkroom."));
1239 return 1;
1240 }
1241 case GDK_KEY_Up:
1242 {
1243 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1244 d->pan_y -= step;
1246 return 1;
1247 }
1248 case GDK_KEY_Down:
1249 {
1250 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1251 d->pan_y += step;
1253 return 1;
1254 }
1255 case GDK_KEY_Left:
1256 {
1257 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1258 d->pan_x -= step;
1260 return 1;
1261 }
1262 case GDK_KEY_Right:
1263 {
1264 if(d->zoom != DT_THUMBTABLE_ZOOM_FULL) return 0;
1265 d->pan_x += step;
1267 return 1;
1268 }
1269 case GDK_KEY_Escape:
1270 {
1271 dt_ctl_switch_mode_to("lighttable");
1272 return TRUE;
1273 }
1274 }
1275
1276 return 0;
1277}
1278
1279// clang-format off
1280// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1281// vim: shiftwidth=2 expandtab tabstop=2 cindent
1282// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1283// 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.
#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:278
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
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)
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:142
@ DT_COLLECTION_CHANGE_RELOAD
Definition collection.h:151
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:37
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:38
const float delta
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
int type
char * name
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:673
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:521
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:947
void dt_control_log(const char *msg,...)
Definition control.c:777
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:973
#define dt_control_change_cursor(cursor)
Definition control.h:116
darktable_t darktable
Definition darktable.c:183
static gboolean dt_modifiers_include(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:953
#define UNKNOWN_IMAGE
Definition darktable.h:194
float dt_boundingbox_t[4]
Definition darktable.h:721
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:468
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:945
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
void dt_dev_draw_iso12646_border(cairo_t *cr, double width, double height, int border)
Definition dev_backbuf.c:53
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:44
void dt_dev_release_locked_surface(dt_dev_locked_surface_t *locked)
Definition dev_backbuf.c:87
void dt_dev_draw_profile_mode_label(cairo_t *cri, int height)
Definition dev_backbuf.c:61
void dt_dev_history_free_history(dt_develop_t *dev)
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)
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:38
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:27
@ DT_DEV_TOOLBOX_DISPLAY
Definition dev_toolbox.h:33
@ DT_DEV_TOOLBOX_GAMUT
Definition dev_toolbox.h:32
@ DT_DEV_TOOLBOX_RAWOVEREXPOSED
Definition dev_toolbox.h:30
@ DT_DEV_TOOLBOX_OVEREXPOSED
Definition dev_toolbox.h:29
@ DT_DEV_TOOLBOX_SOFTPROOF
Definition dev_toolbox.h:31
@ DT_DEV_TOOLBOX_ISO_12646
Definition dev_toolbox.h:28
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:356
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:225
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1783
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:940
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:128
void dt_dev_coordinates_raw_norm_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1184
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:1957
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:1852
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:1895
void dt_dev_start_all_pipelines(dt_develop_t *dev)
Definition develop.c:864
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:124
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:534
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
void dt_gui_refocus_center()
Definition gtk.c:3482
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
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:783
void dt_iop_gui_cleanup_module(dt_iop_module_t *module)
Definition imageop.c:2035
void dt_iop_cleanup_module(dt_iop_module_t *module)
Definition imageop.c:1577
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:1132
static const float x
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
void dt_masks_free_form(dt_masks_form_t *form)
void dt_masks_gui_init(struct dt_develop_t *dev)
void dt_masks_gui_cleanup(struct dt_develop_t *dev)
#define M_PI
Definition math.h:45
float dt_aligned_pixel_t[4]
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_flush_clmem_for_pipe(dt_dev_pixelpipe_cache_t *cache, const int devid)
Like dt_dev_pixelpipe_cache_flush_clmem(), for callers that do not hold darktable....
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:69
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:289
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:95
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:204
@ 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:248
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
const float uint32_t state[4]
struct dt_lib_t * lib
Definition darktable.h:789
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:808
struct dt_gui_gtk_t * gui
Definition darktable.h:793
struct dt_collection_t * collection
Definition darktable.h:799
struct dt_selection_t * selection
Definition darktable.h:800
struct dt_control_signal_t * signals
Definition darktable.h:792
struct dt_develop_t * develop
Definition darktable.h:788
struct dt_view_manager_t * view_manager
Definition darktable.h:790
struct dt_control_t * control
Definition darktable.h:791
GtkAccelGroup * lighttable_accels
dt_lib_colorpicker_size_t size
Definition colorpicker.h:63
dt_boundingbox_t box
Definition colorpicker.h:62
int button_down_which
Definition control.h:220
int button_down
Definition control.h:220
dt_pthread_mutex_t busy_mutex
dt_backbuf_t backbuf
dt_atomic_int shutdown
struct dt_develop_t::@21 color_picker
Authoritative darkroom color-picker state.
dt_image_t image_storage
Definition develop.h:259
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:397
dt_backbuf_t display_histogram
Definition develop.h:343
GList * iop
Definition develop.h:285
dt_backbuf_t output_histogram
Definition develop.h:342
gboolean(* refresh_global_picker)(struct dt_lib_module_t *self)
Definition develop.h:405
struct dt_iop_module_t * gui_module
Definition develop.h:165
dt_pthread_rwlock_t history_mutex
Definition develop.h:263
GSList * samples
Definition develop.h:398
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
gboolean pipelines_started
Definition develop.h:351
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
GList * alliop
Definition develop.h:287
struct dt_iop_module_t *struct dt_iop_color_picker_t * picker
Definition develop.h:389
GList * allforms
Definition develop.h:335
dt_backbuf_t raw_histogram
Definition develop.h:341
struct dt_masks_form_gui_t * form_gui
Definition develop.h:333
struct dt_colorpicker_sample_t * selected_sample
Definition develop.h:399
struct dt_lib_module_t * histogram_module
Definition develop.h:404
dt_pthread_rwlock_t masks_mutex
Definition develop.h:339
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
gboolean display_samples
Definition develop.h:400
GList * forms
Definition develop.h:327
dt_accels_t * accels
Definition gtk.h:199
dt_ui_t * ui
Definition gtk.h:165
cairo_filter_t filter_image
Definition gtk.h:236
int32_t id
Definition image.h:319
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
dt_thumbtable_t * thumbtable_filmstrip
Track one asynchronous Cairo surface fetch request for a GUI widget.
Definition view.h:118
struct dt_view_manager_t::@69 proxy
struct dt_view_t * view
Definition view.h:245
struct dt_view_manager_t::@69::@72 darkroom
GModule *void * data
Definition view.h:158
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)
uint32_t view(const dt_view_t *self)
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:444
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:75
@ DT_THUMBTABLE_ZOOM_FULL
Definition thumbtable.h:78
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:76
static void dt_thumbtable_show(dt_thumbtable_t *table)
Show the thumbnail table widget.
Definition thumbtable.h:389
static void dt_thumbtable_hide(dt_thumbtable_t *table)
Hide the thumbnail table widget.
Definition thumbtable.h:404
void dt_view_image_surface_fetcher_invalidate(dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target)
Definition view.c:867
void dt_view_image_surface_fetcher_cleanup(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:843
void dt_view_image_surface_fetcher_init(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:832
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:888
void dt_view_active_images_add(int32_t imgid, gboolean raise)
Definition view.c:1307
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1298
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:236
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:141
@ DT_VIEW_STUDIO_CAPTURE
Definition view.h:78
dt_view_surface_value_t
Definition view.h:103
@ DT_VIEW_SURFACE_OK
Definition view.h:104
@ DT_UI_PANEL_BOTTOM
@ DT_UI_PANEL_LEFT
@ DT_UI_PANEL_RIGHT