/* This file is part of darktable, Copyright (C) 2012-2026 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #include "bauhaus/bauhaus.h" #include "common/camera_control.h" #include "common/darktable.h" #include "common/image_cache.h" #include "common/mipmap_cache.h" #include "control/conf.h" #include "control/control.h" #include "control/jobs.h" #include "dtgtk/button.h" #include "gui/accelerators.h" #include "gui/gtk.h" #include "gui/guides.h" #include "libs/lib.h" #include "libs/lib_api.h" #include typedef enum dt_lib_live_view_focus_control_t { DT_FOCUS_NEAR = 0, DT_FOCUS_NEARER = 2, DT_FOCUS_FAR = 4, DT_FOCUS_FARTHER = 6 } dt_lib_live_view_focus_control_t; typedef enum dt_lib_live_view_flip_t { FLAG_FLIP_NONE = 0, FLAG_FLIP_HORIZONTAL = 1<<0, FLAG_FLIP_VERTICAL = 1<<1, FLAG_FLIP_BOTH = FLAG_FLIP_HORIZONTAL|FLAG_FLIP_VERTICAL } dt_lib_live_view_flip_t; typedef enum dt_lib_live_view_overlay_t { OVERLAY_NONE = 0, OVERLAY_SELECTED, OVERLAY_ID } dt_lib_live_view_overlay_t; #define HANDLE_SIZE 0.02 static const cairo_operator_t _overlay_modes[] = { CAIRO_OPERATOR_OVER, CAIRO_OPERATOR_XOR, CAIRO_OPERATOR_ADD, CAIRO_OPERATOR_SATURATE, CAIRO_OPERATOR_MULTIPLY, CAIRO_OPERATOR_SCREEN, CAIRO_OPERATOR_OVERLAY, CAIRO_OPERATOR_DARKEN, CAIRO_OPERATOR_LIGHTEN, CAIRO_OPERATOR_COLOR_DODGE, CAIRO_OPERATOR_COLOR_BURN, CAIRO_OPERATOR_HARD_LIGHT, CAIRO_OPERATOR_SOFT_LIGHT, CAIRO_OPERATOR_DIFFERENCE, CAIRO_OPERATOR_EXCLUSION, CAIRO_OPERATOR_HSL_HUE, CAIRO_OPERATOR_HSL_SATURATION, CAIRO_OPERATOR_HSL_COLOR, CAIRO_OPERATOR_HSL_LUMINOSITY }; DT_MODULE(1) typedef struct dt_lib_live_view_t { dt_imgid_t imgid; int splitline_rotation; double overlay_x0, overlay_x1, overlay_y0, overlay_y1; double splitline_x, splitline_y; // 0..1 gboolean splitline_dragging; GtkWidget *live_view, *live_view_zoom, *rotate_ccw, *rotate_cw, *flip; GtkWidget *auto_focus, *focus_out_small, *focus_out_big, *focus_in_small, *focus_in_big; GtkWidget *overlay, *overlay_id_box, *overlay_id, *overlay_mode, *overlay_splitline; } dt_lib_live_view_t; static void overlay_changed(GtkWidget *combo, dt_lib_live_view_t *lib) { int which = dt_bauhaus_combobox_get(combo); if(which == OVERLAY_NONE) { gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE); } else { gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), TRUE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), TRUE); } if(which == OVERLAY_ID) gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), TRUE); else gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE); } const char *name(dt_lib_module_t *self) { return _("live view"); } dt_view_type_flags_t views(dt_lib_module_t *self) { return DT_VIEW_TETHERING; } uint32_t container(dt_lib_module_t *self) { return DT_UI_CONTAINER_PANEL_RIGHT_CENTER; } void gui_reset(dt_lib_module_t *self) { } int position(const dt_lib_module_t *self) { return 998; } static void _rotate_ccw(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; cam->live_view_rotation = (cam->live_view_rotation + 1) % 4; // 0 -> 1 -> 2 -> 3 -> 0 -> ... } static void _rotate_cw(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; cam->live_view_rotation = (cam->live_view_rotation + 3) % 4; // 0 -> 3 -> 2 -> 1 -> 0 -> ... } // Congratulations to Simon for being the first one recognizing live // view in a screen shot ^^ static void _toggle_live_view_clicked(GtkWidget *widget, gpointer user_data) { if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { if(dt_camctl_camera_start_live_view(darktable.camctl) == FALSE) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE); } else { dt_camctl_camera_stop_live_view(darktable.camctl); } } // TODO: using a toggle button would be better, but this setting can // also be changed by right clicking on the canvas // (src/views/capture.c). maybe using a signal would work? i have no // idea. static void _zoom_live_view_clicked(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; if(cam->is_live_viewing) { cam->live_view_zoom = !cam->live_view_zoom; if(cam->live_view_zoom) dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "5"); else dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "1"); } } static void _auto_focus_button_clicked(GtkWidget *widget, gpointer user_data) { const char *property = "autofocusdrive"; CameraWidgetType property_type; if(dt_camctl_camera_get_property_type(darktable.camctl, NULL, property, &property_type)) { dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to get property type for %s", property); } else { if(property_type == GP_WIDGET_TOGGLE) { dt_camctl_camera_set_property_toggle(darktable.camctl, NULL, property); } else { // TODO evaluate if this is the right thing to do in default scenario dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to set %s for property type %d", property, property_type); } } } static void _focus_button_clicked(GtkWidget *widget, gpointer user_data) { int focus = GPOINTER_TO_INT(user_data); CameraWidgetType property_type; if(dt_camctl_camera_get_property_type(darktable.camctl, NULL, "manualfocusdrive", &property_type)) { // default to avoid breaking backwards compatibility // note that this might not work on non-Canon EOS cameras dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus); } else { // we need to check the property type here because of a peculiar // difference between the property type that gphoto2 supports for // Canon EOS and Nikon systems. In particular, if you have a // Canon, expect a TOGGLE or RADIO. If you have a Nikon, expect a // RANGE. switch(property_type) { case GP_WIDGET_RANGE: { float focus_amount; switch(focus) { case DT_FOCUS_NEARER: focus_amount = 250; break; case DT_FOCUS_NEAR: focus_amount = 50; break; case DT_FOCUS_FAR: focus_amount = -50; break; case DT_FOCUS_FARTHER: focus_amount = -250; break; default: focus_amount = 0; } dt_camctl_camera_set_property_float(darktable.camctl, NULL, "manualfocusdrive", focus_amount); break; } case GP_WIDGET_TOGGLE | GP_WIDGET_RADIO: dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus); break; default: // TODO evaluate if this is the right thing to do in default scenario dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to set manualfocusdrive for property type %d", property_type); break; } } } static void _toggle_flip_clicked(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; cam->live_view_flip = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } static void _overlay_id_changed(GtkWidget *widget, gpointer user_data) { dt_lib_live_view_t *lib = (dt_lib_live_view_t *)user_data; lib->imgid = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)); dt_conf_set_int("plugins/lighttable/live_view/overlay_imgid", lib->imgid); } static void _overlay_mode_changed(GtkWidget *combo, gpointer user_data) { dt_conf_set_int("plugins/lighttable/live_view/overlay_mode", dt_bauhaus_combobox_get(combo)); } static void _overlay_splitline_changed(GtkWidget *combo, gpointer user_data) { dt_conf_set_int("plugins/lighttable/live_view/splitline", dt_bauhaus_combobox_get(combo)); } void gui_init(dt_lib_module_t *self) { self->data = calloc(1, sizeof(dt_lib_live_view_t)); // Setup lib data dt_lib_live_view_t *lib = self->data; lib->splitline_x = lib->splitline_y = 0.5; // Setup gui self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0); GtkWidget *button; #define NEW_BUTTON(type, paint, direction, callback, data, action) \ button = dtgtk_##type##button_new(paint, direction, NULL); \ gtk_widget_set_tooltip_text(button, action); \ gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); \ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(callback), data); \ dt_action_define(DT_ACTION(self), NULL, action, button, *(#type)?&dt_action_def_toggle:&dt_action_def_button); lib->live_view = NEW_BUTTON(toggle, dtgtk_cairo_paint_eye, 0, _toggle_live_view_clicked, lib, N_("toggle live view")); dt_shortcut_register(dt_action_section(DT_ACTION(self), N_("toggle live view")), 0, 0, GDK_KEY_v, 0); lib->live_view_zoom = NEW_BUTTON(, dtgtk_cairo_paint_zoom, 0, _zoom_live_view_clicked, lib, N_("zoom live view")); // TODO: see _zoom_live_view_clicked dt_shortcut_register(dt_action_section(DT_ACTION(self), N_("zoom live view")), 0, 0, GDK_KEY_w, 0); lib->rotate_ccw = NEW_BUTTON(, dtgtk_cairo_paint_refresh, 0, _rotate_ccw, lib, N_("rotate 90 degrees CCW")); lib->rotate_cw = NEW_BUTTON(,dtgtk_cairo_paint_refresh, CPF_DIRECTION_UP, _rotate_cw, lib, N_("rotate 90 degrees CW")); lib->flip = NEW_BUTTON(toggle, dtgtk_cairo_paint_flip, CPF_DIRECTION_UP, _toggle_flip_clicked, lib, N_("flip live view horizontally")); // focus buttons box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0); lib->focus_in_big = NEW_BUTTON(,dtgtk_cairo_paint_solid_triangle, CPF_DIRECTION_LEFT, _focus_button_clicked, GINT_TO_POINTER(DT_FOCUS_NEARER), N_("move focus point in (big steps)")); lib->focus_in_small = NEW_BUTTON(,dtgtk_cairo_paint_arrow, CPF_DIRECTION_LEFT, _focus_button_clicked, GINT_TO_POINTER(DT_FOCUS_NEAR), N_("move focus point in (small steps)"));// TODO icon not centered lib->auto_focus = NEW_BUTTON(,dtgtk_cairo_paint_lock, 0, _auto_focus_button_clicked, GINT_TO_POINTER(1), N_("run autofocus")); lib->focus_out_small = NEW_BUTTON(,dtgtk_cairo_paint_arrow, CPF_DIRECTION_RIGHT, _focus_button_clicked, GINT_TO_POINTER(DT_FOCUS_FAR), N_("move focus point out (small steps)")); // TODO same here lib->focus_out_big = NEW_BUTTON(,dtgtk_cairo_paint_solid_triangle, CPF_DIRECTION_RIGHT, _focus_button_clicked, GINT_TO_POINTER(DT_FOCUS_FARTHER), N_("move focus point out (big steps)")); #undef NEW_BUTTON lib->overlay = dt_bauhaus_combobox_new_action(DT_ACTION(self)); dt_bauhaus_widget_set_label(lib->overlay, NULL, N_("overlay")); dt_bauhaus_combobox_add(lib->overlay, _("none")); dt_bauhaus_combobox_add(lib->overlay, _("selected image")); dt_bauhaus_combobox_add(lib->overlay, _("id")); gtk_widget_set_tooltip_text(lib->overlay, _("overlay another image over the live view")); g_signal_connect(G_OBJECT(lib->overlay), "value-changed", G_CALLBACK(overlay_changed), lib); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay, TRUE, TRUE, 0); lib->overlay_id_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *label = gtk_label_new(_("image id")); gtk_widget_set_halign(label, GTK_ALIGN_START); lib->overlay_id = gtk_spin_button_new_with_range(0, 1000000000, 1); gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lib->overlay_id), 0); gtk_widget_set_tooltip_text(lib->overlay_id, _("enter image id of the overlay manually")); g_signal_connect(G_OBJECT(lib->overlay_id), "value-changed", G_CALLBACK(_overlay_id_changed), lib); gtk_spin_button_set_value(GTK_SPIN_BUTTON(lib->overlay_id), dt_conf_get_int("plugins/lighttable/live_view/overlay_imgid")); gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), label, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), lib->overlay_id, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_id_box, TRUE, TRUE, 0); gtk_widget_show(lib->overlay_id); gtk_widget_show(label); lib->overlay_mode = dt_bauhaus_combobox_new_action(DT_ACTION(self)); dt_bauhaus_widget_set_label(lib->overlay_mode, NULL, N_("overlay mode")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "normal")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "xor")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "add")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "saturate")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "multiply")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "screen")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "overlay")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "darken")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "lighten")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color dodge")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color burn")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "hard light")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "soft light")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "difference")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "exclusion")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL hue")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL saturation")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL color")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL luminosity")); gtk_widget_set_tooltip_text(lib->overlay_mode, _("mode of the overlay")); dt_bauhaus_combobox_set(lib->overlay_mode, dt_conf_get_int("plugins/lighttable/live_view/overlay_mode")); g_signal_connect(G_OBJECT(lib->overlay_mode), "value-changed", G_CALLBACK(_overlay_mode_changed), lib); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_mode, TRUE, TRUE, 0); lib->overlay_splitline = dt_bauhaus_combobox_new_action(DT_ACTION(self)); dt_bauhaus_widget_set_label(lib->overlay_splitline, NULL, N_("split line")); dt_bauhaus_combobox_add(lib->overlay_splitline, _("off")); dt_bauhaus_combobox_add(lib->overlay_splitline, _("on")); gtk_widget_set_tooltip_text(lib->overlay_splitline, _("only draw part of the overlay")); dt_bauhaus_combobox_set(lib->overlay_splitline, dt_conf_get_int("plugins/lighttable/live_view/splitline")); g_signal_connect(G_OBJECT(lib->overlay_splitline), "value-changed", G_CALLBACK(_overlay_splitline_changed), lib); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_splitline, TRUE, TRUE, 0); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE); gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_mode), TRUE); gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_id_box), TRUE); gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_splitline), TRUE); } void gui_cleanup(dt_lib_module_t *self) { // dt_lib_live_view_t *lib = self->data; // g_list_free(lib->guides_widgets_list); // INTENTIONAL. it's supposed to be leaky until lua is fixed. free(self->data); self->data = NULL; } void view_enter(dt_lib_module_t *self, dt_view_t *old_view, dt_view_t *new_view) { // disable buttons that won't work with this camera // TODO: initialize tethering mode outside of libs/camera.s so we // can use darktable.camctl->active_camera here const dt_lib_live_view_t *lib = self->data; const dt_camera_t *cam = darktable.camctl->active_camera; if(cam == NULL) cam = darktable.camctl->wanted_camera; const gboolean sensitive = cam && cam->can_live_view_advanced; gtk_widget_set_sensitive(lib->live_view_zoom, sensitive); gtk_widget_set_sensitive(lib->focus_in_big, sensitive); gtk_widget_set_sensitive(lib->focus_in_small, sensitive); gtk_widget_set_sensitive(lib->focus_out_big, sensitive); gtk_widget_set_sensitive(lib->focus_out_small, sensitive); } void view_leave(dt_lib_module_t *self, dt_view_t *old_view, dt_view_t *new_view) { const dt_lib_live_view_t *lib = self->data; // there's no code to automatically restart live view when entering // the view, and besides the user may not want to jump right back // into live view if they've been out of tethering view doing other // things if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->live_view))) { dt_camctl_camera_stop_live_view(darktable.camctl); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lib->live_view), FALSE); } } // TODO: find out where the zoom window is and draw overlay + grid accordingly #define MARGIN 20 #define BAR_HEIGHT 18 /* see libs/camera.c */ void gui_post_expose(dt_lib_module_t *self, cairo_t *cr, const int32_t width, const int32_t height, const int32_t pointerx, const int32_t pointery) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; if (!cam) return; dt_lib_live_view_t *lib = self->data; if (!lib) return; if(!cam->is_live_viewing || cam->live_view_zoom) return; dt_pthread_mutex_lock(&cam->live_view_buffer_mutex); if(!cam->live_view_buffer) { dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex); return; } const double w = width - (MARGIN * 2.0f); const double h = height - (MARGIN * 2.0f) - BAR_HEIGHT; gint pw = cam->live_view_width; gint ph = cam->live_view_height; lib->overlay_x0 = lib->overlay_x1 = lib->overlay_y0 = lib->overlay_y1 = 0.0; const gboolean use_splitline = (dt_bauhaus_combobox_get(lib->overlay_splitline) == 1); // OVERLAY dt_imgid_t imgid = NO_IMGID; switch(dt_bauhaus_combobox_get(lib->overlay)) { case OVERLAY_SELECTED: imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager); break; case OVERLAY_ID: imgid = lib->imgid; break; } if(dt_is_valid_imgid(imgid)) { cairo_save(cr); const dt_image_t *img = dt_image_cache_testget(imgid, 'r'); // if the user points at this image, we really want it: if(!img) img = dt_image_cache_get(imgid, 'r'); const float imgwd = 0.97f; dt_mipmap_buffer_t buf; dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(imgwd * w, imgwd * h); dt_mipmap_cache_get(&buf, imgid, mip, 0, 'r'); float scale = 1.0; cairo_surface_t *surface = NULL; if(buf.buf) { const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf.width); surface = cairo_image_surface_create_for_data(buf.buf, CAIRO_FORMAT_RGB24, buf.width, buf.height, stride); scale = fminf(fminf(w, pw) / (float)buf.width, fminf(h, ph) / (float)buf.height); } // draw centered and fitted: cairo_translate(cr, width / 2.0, (height + BAR_HEIGHT) / 2.0f); cairo_scale(cr, scale, scale); if(buf.buf) { cairo_translate(cr, -.5f * buf.width, -.5f * buf.height); if(use_splitline) { double x0, y0, x1, y1; switch(lib->splitline_rotation) { case 0: x0 = 0.0; y0 = 0.0; x1 = buf.width * lib->splitline_x; y1 = buf.height; break; case 1: x0 = 0.0; y0 = 0.0; x1 = buf.width; y1 = buf.height * lib->splitline_y; break; case 2: x0 = buf.width * lib->splitline_x; y0 = 0.0; x1 = buf.width; y1 = buf.height; break; case 3: x0 = 0.0; y0 = buf.height * lib->splitline_y; x1 = buf.width; y1 = buf.height; break; default: dt_print(DT_DEBUG_ALWAYS, "OMFG, the world will collapse, this shouldn't be reachable!"); dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex); return; } cairo_rectangle(cr, x0, y0, x1, y1); cairo_clip(cr); } cairo_set_source_surface(cr, surface, 0, 0); // set filter no nearest: in skull/error mode, we want to see // big pixels. in 1 iir mode for the right mip, we want to see // exactly what the pipe gave us, 1:1 pixel for pixel. in // between, filtering just makes stuff go unsharp. if((buf.width <= 30 && buf.height <= 30) || fabsf(scale - 1.0f) < 0.01f) cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); cairo_rectangle(cr, 0, 0, buf.width, buf.height); const int overlay_modes_index = dt_bauhaus_combobox_get(lib->overlay_mode); if(overlay_modes_index >= 0) { const cairo_operator_t mode = _overlay_modes[overlay_modes_index]; cairo_set_operator(cr, mode); } cairo_fill(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_surface_destroy(surface); } cairo_restore(cr); if(buf.buf) dt_mipmap_cache_release(&buf); if(img) dt_image_cache_read_release(img); // ON CANVAS CONTROLS if(use_splitline) { scale = fminf(1.0, fminf(w / pw, h / ph)); // image coordinates lib->overlay_x0 = 0.5 * (width - pw * scale); lib->overlay_y0 = 0.5 * (height - ph * scale + BAR_HEIGHT); lib->overlay_x1 = lib->overlay_x0 + pw * scale; lib->overlay_y1 = lib->overlay_y0 + ph * scale; // splitline position to absolute coords: const double sl_x = lib->overlay_x0 + lib->splitline_x * pw * scale; const double sl_y = lib->overlay_y0 + lib->splitline_y * ph * scale; int x0 = sl_x, y0 = 0.0, x1 = x0, y1 = height; if(lib->splitline_rotation % 2 != 0) { x0 = 0.0; y0 = sl_y; x1 = width; y1 = y0; } const gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0) ? (fabs(sl_x - pointerx) < 5) : (fabs(sl_y - pointery) < 5); cairo_save(cr); cairo_set_source_rgb(cr, .7, .7, .7); cairo_set_line_width(cr, (mouse_over_control ? 2.0 : 0.5)); cairo_move_to(cr, x0, y0); cairo_line_to(cr, x1, y1); cairo_stroke(cr); /* if mouse over control lets draw center rotate control, hide * if split is dragged */ if(!lib->splitline_dragging && mouse_over_control) { cairo_set_line_width(cr, 0.5); const double s = width * HANDLE_SIZE; dtgtk_cairo_paint_refresh(cr, sl_x - (s * 0.5), sl_y - (s * 0.5), s, s, 1, NULL); } cairo_restore(cr); } } // GUIDES float scale; if(cam->live_view_rotation % 2 == 0) scale = fminf(w / pw, h / ph); else { const gint tmp = pw; pw = ph; ph = tmp; scale = fminf(w / ph, h / pw); } // ensure some sanity on the scale factor scale = fminf(10.0, scale); const double sw = scale * pw; const double sh = scale * ph; // draw guides const double left = (width - sw) * 0.5; const double top = (height + BAR_HEIGHT - sh) * 0.5; dt_guides_draw(cr, left, top, sw, sh, 1.0); dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex); } int button_released(dt_lib_module_t *self, const double x, const double y, const int which, const uint32_t state) { dt_lib_live_view_t *d = self->data; if(d->splitline_dragging) { d->splitline_dragging = FALSE; return 1; } return 0; } int button_pressed(dt_lib_module_t *self, const double x, const double y, const double pressure, const int which, const int type, const uint32_t state) { dt_lib_live_view_t *lib = self->data; int result = 0; dt_imgid_t imgid = NO_IMGID; switch(dt_bauhaus_combobox_get(lib->overlay)) { case OVERLAY_SELECTED: imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager); break; case OVERLAY_ID: imgid = lib->imgid; break; } if(dt_is_valid_imgid(imgid) && dt_bauhaus_combobox_get(lib->overlay_splitline)) { const double width = lib->overlay_x1 - lib->overlay_x0; const double height = lib->overlay_y1 - lib->overlay_y0; // splitline position to absolute coords: const double sl_x = lib->overlay_x0 + lib->splitline_x * width; const double sl_y = lib->overlay_y0 + lib->splitline_y * height; const gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0) ? (fabs(sl_x - x) < 5) : (fabs(sl_y - y) < 5); /* do the split rotating */ if(which == GDK_BUTTON_PRIMARY && fabs(sl_x - x) < 7 && fabs(sl_y - y) < 7) { /* let's rotate */ lib->splitline_rotation = (lib->splitline_rotation + 1) % 4; dt_control_queue_redraw_center(); result = 1; } /* do the dragging !? */ else if(which == GDK_BUTTON_PRIMARY && mouse_over_control) { lib->splitline_dragging = TRUE; dt_control_queue_redraw_center(); result = 1; } } return result; } int mouse_moved(dt_lib_module_t *self, const double x, const double y, const double pressure, const int which) { dt_lib_live_view_t *lib = self->data; int result = 0; if(lib->splitline_dragging) { const double width = lib->overlay_x1 - lib->overlay_x0; const double height = lib->overlay_y1 - lib->overlay_y0; // absolute coords to splitline position: lib->splitline_x = CLAMPS((x - lib->overlay_x0) / width, 0.0, 1.0); lib->splitline_y = CLAMPS((y - lib->overlay_y0) / height, 0.0, 1.0); result = 1; } return result; } // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; // clang-format on