Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
preview_window.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2025 Aurélien PIERRE.
4 Copyright (C) 2026 Guillaume Stutin.
5
6 Ansel is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Ansel is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
18*/
19#include "common/logging.h"
20#include "colorprofiles/colorspaces.h" // dt_colorprofiles_xyz_to_display()
21#include "common/colorspaces_inline_conversions.h" // dt_Lab_to_XYZ
22#include "common/conf.h"
23#include "system/macros.h"
24#include "system/mem_alloc.h"
25#include "system/simd.h"
26#include "common/times.h"
27#include "control/signal.h"
28#include "gui/application.h"
29#include "control/control.h"
31#include "caches/image_cache.h"
32#include "views/view.h"
33
34#include <gtk/gtk.h>
36
37#ifdef GDK_WINDOWING_QUARTZ
38#include "osx/osx.h"
39#endif
40
50
51void _preview_redraw(gpointer instance, dt_preview_window_t *preview);
52
53static void _preview_window_destroy(GtkWidget *dialog, gpointer user_data)
54{
56 // Disconnect from DARKROOM_UI_CHANGED before freeing: otherwise the handler keeps
57 // firing with a dangling `preview` pointer and crashes on the next UI change
58 // (e.g. moving the border-size slider).
62}
63
64static void _close_preview_popup(GtkWidget *dialog, gint response_id, gpointer data)
65{
66 gtk_widget_destroy(dialog);
67}
68
69static void _preview_window_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
70{
72 if(IS_NULL_PTR(preview) || IS_NULL_PTR(allocation)) return;
73 if(allocation->width < 2 || allocation->height < 2) return;
74 if(preview->width == allocation->width && preview->height == allocation->height) return;
75
76 preview->width = allocation->width;
77 preview->height = allocation->height;
78
79 /* The async fetcher can stop its current pixelpipe through the request-owned
80 * shutdown flag. Trigger that as soon as the popup size changes so we don't
81 * keep rendering a surface for an obsolete allocation. */
83 gtk_widget_queue_draw(widget);
84}
85
86// TODO: we color-manage the preview window assuming it sits on the same screen as the main window
87// Will need to grab the color profile of the actual monitor where it sits.
88void _colormanage_ui_color(const float L, const float a, const float b, dt_aligned_pixel_t RGB)
89{
90 dt_aligned_pixel_t Lab = { L, a, b, 1.f };
91 dt_aligned_pixel_t XYZ = { 0.f, 0.f, 0.f, 1.f };
94}
95
96static gboolean
97_thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
98{
100 if(IS_NULL_PTR(preview)) return TRUE;
101
102 const double start = dt_get_wtime();
103
104 int w = gtk_widget_get_allocated_width(widget);
105 int h = gtk_widget_get_allocated_height(widget);
106
107 // Paint background
108 dt_aligned_pixel_t bg_color;
109 _colormanage_ui_color((float)dt_conf_get_int("display/brightness"), 0., 0., bg_color);
110 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
111 cairo_paint(cr);
112
113 int borders = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
114 w -= 2 * borders;
115 h -= 2 * borders;
116
117 // FIXME: this gets the color-managed image surface using the color profile of the monitor
118 // where the main window sits. Need to detect the monitor of the preview window instead.
120 dt_view_image_get_surface_async(&preview->fetcher, preview->imgid, w, h, &preview->surface, widget, 0);
121
122 if(preview->surface && res == DT_VIEW_SURFACE_OK)
123 {
124 // The image is immediately available
125 int width = cairo_image_surface_get_width(preview->surface);
126 int height = cairo_image_surface_get_height(preview->surface);
127 double sx = 1.0, sy = 1.0;
128 cairo_surface_get_device_scale(preview->surface, &sx, &sy);
129 const double logical_width = width / sx;
130 const double logical_height = height / sy;
131
132 // we draw the image
133 cairo_save(cr);
134 double x_offset = (w - logical_width) / 2. + borders;
135 double y_offset = (h - logical_height) / 2. + borders;
136 cairo_set_source_surface(cr, preview->surface, x_offset, y_offset);
137
138 // get the transparency value
139 GdkRGBA im_color;
140 GtkStyleContext *context = gtk_widget_get_style_context(widget);
141 gtk_style_context_get_color(context, gtk_widget_get_state_flags(widget), &im_color);
142 cairo_paint_with_alpha(cr, im_color.alpha);
143
144 // and eventually the image border
145 gtk_render_frame(context, cr, borders, borders, w, h);
146 cairo_restore(cr);
147 }
148 else
149 {
150 dt_control_draw_busy_msg(cr, w, h);
151 }
152
153 dt_print(DT_DEBUG_LIGHTTABLE, "Redrawing the preview window for %i in %0.04f sec\n", preview->imgid,
154 dt_get_wtime() - start);
155
156 return TRUE;
157}
158
159
161{
162 gtk_widget_queue_draw(preview->area);
163}
164
165
166void dt_preview_window_spawn(const int32_t imgid)
167{
168 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
169 if(IS_NULL_PTR(img))
170 {
171 dt_control_log(_("No image to open !"));
172 return;
173 }
174 gchar *name = g_strdup_printf(_("Ansel - Preview : %s"), img->filename);
176
177 dt_preview_window_t *preview = calloc(1, sizeof(dt_preview_window_t));
178 preview->imgid = imgid;
180
181 GtkWidget *dialog = gtk_dialog_new();
182 gtk_window_set_title(GTK_WINDOW(dialog), name);
183 dt_free(name);
184
185#ifdef GDK_WINDOWING_QUARTZ
187 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
188#endif
189
190 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
191 gtk_window_set_modal(GTK_WINDOW(dialog), FALSE);
192 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(dt_gui_main_window()));
193 gtk_window_set_default_size(GTK_WINDOW(dialog), 350, 350);
194 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(_close_preview_popup), NULL);
195 g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(_preview_window_destroy), preview);
196
197 preview->area = gtk_drawing_area_new();
198 gtk_widget_set_hexpand(preview->area, TRUE);
199 gtk_widget_set_vexpand(preview->area, TRUE);
200 gtk_widget_set_halign(preview->area, GTK_ALIGN_FILL);
201 gtk_widget_set_valign(preview->area, GTK_ALIGN_FILL);
202 gtk_widget_set_size_request(preview->area, 350, 350);
203 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), preview->area, TRUE, TRUE, 0);
204 g_signal_connect(G_OBJECT(preview->area), "draw", G_CALLBACK(_thumb_draw_image), preview);
205 g_signal_connect(G_OBJECT(preview->area), "size-allocate", G_CALLBACK(_preview_window_size_allocate), preview);
207
208 gtk_widget_set_visible(preview->area, TRUE);
209 gtk_widget_show_all(dialog);
210}
GtkWidget * dt_gui_main_window(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_colorprofiles_xyz_to_display(const dt_aligned_pixel_t XYZ, dt_aligned_pixel_t RGB)
Convert one D50 XYZ pixel to display RGB.
The colour-profile module's API: which profiles exist, and how to apply one.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
dt_Lab_to_XYZ(Lab, XYZ)
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t Lab
static dt_aligned_pixel_t RGB
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:545
void dt_control_log(const char *msg,...)
Definition control.c:839
GtkWidget * preview
what the selected row actually captures
const int res
Definition dtpthread.h:351
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
@ DT_DEBUG_LIGHTTABLE
Definition logging.h:60
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#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
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
const char * name
Definition pdf.h:90
static void _close_preview_popup(GtkWidget *dialog, gint response_id, gpointer data)
static void _preview_window_destroy(GtkWidget *dialog, gpointer user_data)
void _preview_redraw(gpointer instance, dt_preview_window_t *preview)
static gboolean _thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
static void _preview_window_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
void dt_preview_window_spawn(const int32_t imgid)
void _colormanage_ui_color(const float L, const float a, const float b, dt_aligned_pixel_t RGB)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_DARKROOM_UI_CHANGED
Signal that the darkroom GUI color changed.
Definition signal.h:228
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
cairo_surface_t * surface
dt_view_image_surface_fetcher_t fetcher
Track one asynchronous Cairo surface fetch request for a GUI widget.
Definition view.h:119
static double dt_get_wtime(void)
Definition times.h:43
Telling the user something happened.
void dt_view_image_surface_fetcher_invalidate(dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target)
Definition view.c:876
void dt_view_image_surface_fetcher_cleanup(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:852
void dt_view_image_surface_fetcher_init(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:841
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:897
dt_view_surface_value_t
Definition view.h:104
@ DT_VIEW_SURFACE_OK
Definition view.h:105
#define DT_PIXEL_APPLY_DPI(value)