Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
snapshots.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Alexandre Prokoudine.
4 Copyright (C) 2011 Henrik Andersson.
5 Copyright (C) 2011, 2014, 2016 johannes hanika.
6 Copyright (C) 2011-2012, 2014-2017 Jérémy Rosen.
7 Copyright (C) 2012 Richard Wonka.
8 Copyright (C) 2012-2018 Tobias Ellinghaus.
9 Copyright (C) 2013-2014, 2016 Roman Lebedev.
10 Copyright (C) 2013 Simon Spannagel.
11 Copyright (C) 2013 Wolfgang Goetz.
12 Copyright (C) 2015 parafin.
13 Copyright (C) 2018 Maurizio Paglia.
14 Copyright (C) 2019, 2023, 2025-2026 Aurélien PIERRE.
15 Copyright (C) 2019 jakubfi.
16 Copyright (C) 2019 luzpaz.
17 Copyright (C) 2020, 2022 Chris Elston.
18 Copyright (C) 2020, 2022 Diederik Ter Rahe.
19 Copyright (C) 2020 Heiko Bauke.
20 Copyright (C) 2020-2021 Pascal Obry.
21 Copyright (C) 2021 Bill Ferguson.
22 Copyright (C) 2021 Philippe Weyland.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2023 Alynx Zhou.
25
26 darktable is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 darktable is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with darktable. If not, see <http://www.gnu.org/licenses/>.
38*/
39
40#include "common/darktable.h"
41#include "bauhaus/bauhaus.h"
42#include "common/debug.h"
44#include "common/history.h"
45#include "common/iop_order.h"
46#include "common/mipmap_cache.h"
47#include "control/conf.h"
48#include "control/control.h"
49#include "develop/develop.h"
50#include "develop/dev_history.h"
53
54#include "gui/gtk.h"
55#include "gui/draw.h"
56#include "libs/lib.h"
57#include "libs/lib_api.h"
58
59DT_MODULE(1)
60
61#define DT_LIB_SNAPSHOTS_COUNT 4
62#define SNAP_LOG(...) dt_print(DT_DEBUG_DEV, __VA_ARGS__)
63#define HANDLE_SIZE DT_PIXEL_APPLY_DPI_DPP(36)
64
65/* a snapshot */
76
77
78typedef struct dt_lib_snapshots_t
79{
81
82 uint32_t selected;
83
84 /* current active snapshots */
85 uint32_t num_snapshots;
86
87 /* size of snapshots */
88 uint32_t size;
89
90 /* snapshots */
92
93 /* snapshot cairo surface */
94 cairo_surface_t *snapshot_image;
97
98
99 /* change snapshot overlay controls */
102 gboolean on_going;
104
107
108/* callback for take snapshot */
109static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data);
110static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data);
111
113{
114 if(IS_NULL_PTR(snap)) return;
115 if(!IS_NULL_PTR(snap->develop))
116 {
117 dt_dev_cleanup(snap->develop);
118 dt_free(snap->develop);
119 snap->develop = NULL;
120 }
121 snap->imgid = UNKNOWN_IMAGE;
122 snap->history_end = 0;
123 snap->sample_scale = 1.0f;
124}
125
138{
139 if(IS_NULL_PTR(snapshot) || IS_NULL_PTR(source))
140 {
141 SNAP_LOG("[snapshots] capture failed: invalid inputs snapshot=%p source=%p\n", (void *)snapshot,
142 (void *)source);
143 return 1;
144 }
145 if(source->image_storage.id <= 0)
146 {
147 SNAP_LOG("[snapshots] capture failed: invalid source imgid=%d\n", source->image_storage.id);
148 return 1;
149 }
150
152
153 dt_develop_t *frozen = (dt_develop_t *)calloc(1, sizeof(dt_develop_t));
154 if(IS_NULL_PTR(frozen)) return 1;
155 dt_dev_init(frozen, 0);
156
157 if(dt_dev_load_image(frozen, source->image_storage.id))
158 {
159 SNAP_LOG("[snapshots] capture failed: dt_dev_load_image failed for imgid=%d\n", source->image_storage.id);
160 dt_dev_cleanup(frozen);
161 dt_free(frozen);
162 return 1;
163 }
164
165 GList *history_copy = NULL;
166 GList *iop_order_copy = NULL;
167 int32_t history_end = 0;
168
170 history_copy = dt_history_duplicate(source->history);
171 iop_order_copy = dt_ioppr_iop_order_copy_deep(source->iop_order_list);
172 history_end = dt_dev_get_history_end_ext(source);
174
176 frozen->history = history_copy;
177 g_list_free_full(frozen->iop_order_list, dt_free_gpointer);
178 frozen->iop_order_list = iop_order_copy;
179
180 for(GList *history = g_list_first(frozen->history); history; history = g_list_next(history))
181 {
182 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
183 if(IS_NULL_PTR(hist)) continue;
184 hist->module = dt_dev_get_module_instance(frozen, hist->op_name, hist->multi_name, hist->multi_priority);
185 if(IS_NULL_PTR(hist->module))
186 hist->module = dt_dev_create_module_instance(frozen, hist->op_name, hist->multi_name, hist->multi_priority, FALSE);
187 if(IS_NULL_PTR(hist->module))
188 hist->module = dt_iop_get_module_by_op_priority(frozen->iop, hist->op_name, -1);
189 if(IS_NULL_PTR(hist->module))
190 {
191 SNAP_LOG("[snapshots] capture failed: unresolved module op=%s multi=%s priority=%d for imgid=%d\n",
192 hist->op_name, hist->multi_name, hist->multi_priority, source->image_storage.id);
193 dt_dev_cleanup(frozen);
194 dt_free(frozen);
195 return 1;
196 }
197 }
198
199 dt_dev_set_history_end_ext(frozen, history_end);
201
202 snapshot->develop = frozen;
203 snapshot->imgid = source->image_storage.id;
204 snapshot->history_end = history_end;
205 SNAP_LOG("[snapshots] capture success: imgid=%d history_end=%d items=%d\n", snapshot->imgid,
206 snapshot->history_end, g_list_length(frozen->history));
207 return 0;
208}
209
223{
224 int status = 1;
225 gboolean pipe_ready = FALSE;
226 dt_dev_pixelpipe_t snapshot_pipe = { 0 };
227 dt_mipmap_buffer_t buf = { 0 };
228 gboolean input_ready = FALSE;
229 const char *fail_reason = "unknown";
230 if(IS_NULL_PTR(self) || IS_NULL_PTR(snap) || IS_NULL_PTR(self->data)) return 1;
233 dt_develop_t *snapshot_dev = snap->develop;
234 if(IS_NULL_PTR(dev) || !dev->gui_attached)
235 {
236 SNAP_LOG("[snapshots] refresh failed: darkroom dev unavailable\n");
237 return 1;
238 }
239 if(IS_NULL_PTR(snapshot_dev) || snapshot_dev->image_storage.id <= 0)
240 {
241 SNAP_LOG("[snapshots] refresh failed: snapshot dev unavailable selected=%u imgid=%d\n", d->selected,
242 IS_NULL_PTR(snapshot_dev) ? -1 : snapshot_dev->image_storage.id);
243 return 1;
244 }
245 const dt_dev_pixelpipe_t *preview_pipe = dev->preview_pipe;
246 if(IS_NULL_PTR(preview_pipe))
247 {
248 SNAP_LOG("[snapshots] refresh failed: preview pipe unavailable\n");
249 return 1;
250 }
251
252 // Snapshot refresh is synchronous on the UI thread. Apply the busy cursor now,
253 // then restore the queued cursor once the blocking pipe work is complete.
255
257 DT_MIPMAP_BLOCKING, 'r');
258 input_ready = !IS_NULL_PTR(buf.buf) && buf.width > 0 && buf.height > 0;
259 if(!input_ready)
260 {
261 fail_reason = "mipmap full unavailable";
262 goto cleanup;
263 }
264
265 if(!dt_dev_pixelpipe_init_preview(&snapshot_pipe, snapshot_dev))
266 {
267 fail_reason = "pixelpipe init preview failed";
268 goto cleanup;
269 }
270 pipe_ready = TRUE;
271 dt_dev_pixelpipe_set_input(&snapshot_pipe, snapshot_dev->image_storage.id, buf.width, buf.height, buf.iscale,
273 dt_dev_pixelpipe_create_nodes(&snapshot_pipe);
274 dt_dev_pixelpipe_set_icc(&snapshot_pipe, preview_pipe->icc_type, preview_pipe->icc_filename,
275 preview_pipe->icc_intent);
276 dt_dev_pixelpipe_synch_all(&snapshot_pipe);
277 // Driven directly (not via dt_dev_pixelpipe_change()), so settle the format contract and
278 // disable incompatible nodes before ROI planning / global hashing.
280 dt_dev_pixelpipe_get_roi_out(&snapshot_pipe, snapshot_pipe.iwidth, snapshot_pipe.iheight,
281 &snapshot_pipe.processed_width, &snapshot_pipe.processed_height);
282
283 dt_iop_roi_t roi = { 0 };
284 roi.x = 0;
285 roi.y = 0;
286 roi.width = snapshot_pipe.processed_width;
287 roi.height = snapshot_pipe.processed_height;
288 roi.scale = 1.0f;
289 if(roi.width <= 0 || roi.height <= 0 || roi.scale <= 0.0f)
290 {
291 fail_reason = "invalid output roi";
292 goto cleanup;
293 }
294
295 if(dt_dev_pixelpipe_process(&snapshot_pipe, roi))
296 {
297 fail_reason = "pixelpipe process failed";
298 goto cleanup;
299 }
300
301 const uint64_t hash = dt_dev_backbuf_get_hash(&snapshot_pipe.backbuf);
303 {
304 fail_reason = "backbuffer hash invalid";
305 goto cleanup;
306 }
307
308 /*if(hash != pipe_hash || backbuf_hist != pipe_hist)
309 SNAP_LOG("[snapshots] refresh note: non-strict backbuf validity hash=%" PRIu64 " pipe_hash=%" PRIu64
310 " backbuf_hist=%" PRIu64 " pipe_hist=%" PRIu64 "\n",
311 hash, pipe_hash, backbuf_hist, pipe_hist);
312 */
313 dt_pixel_cache_entry_t *entry = NULL;
314 void *data = NULL;
317 snapshot_pipe.devid, NULL)
318 || IS_NULL_PTR(data) || IS_NULL_PTR(entry))
319 {
320 fail_reason = "cache peek failed";
321 goto cleanup;
322 }
323
324 const int bw = snapshot_pipe.backbuf.width;
325 const int bh = snapshot_pipe.backbuf.height;
326 if(bw <= 0 || bh <= 0)
327 {
328 fail_reason = "invalid backbuffer size";
329 goto cleanup;
330 }
331
332 const int src_stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, bw);
333 const size_t required = (size_t)src_stride * (size_t)bh;
334 if(dt_pixel_cache_entry_get_size(entry) < required)
335 {
336 fail_reason = "cache entry too small";
337 goto cleanup;
338 }
339
340 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, bw, bh);
341 if(IS_NULL_PTR(surface))
342 {
343 fail_reason = "cairo surface create failed";
344 goto cleanup;
345 }
346 cairo_surface_set_device_scale(surface, darktable.gui->ppd, darktable.gui->ppd);
347
349 uint8_t *dst = cairo_image_surface_get_data(surface);
350 const int dst_stride = cairo_image_surface_get_stride(surface);
351 for(int y = 0; y < bh; y++)
352 memcpy(dst + (size_t)y * dst_stride, (const uint8_t *)data + (size_t)y * src_stride, (size_t)src_stride);
353 cairo_surface_mark_dirty(surface);
355
356 if(d->snapshot_image) cairo_surface_destroy(d->snapshot_image);
357 d->snapshot_image = surface;
358 d->snapshot_imgid = dev->image_storage.id;
359 d->snapshot_zoom_level = dt_dev_get_zoom_level(dev);
360 snap->sample_scale = roi.scale;
361 status = 0;
362 //SNAP_LOG("[snapshots] refresh success: snapshot_imgid=%d selected=%u roi=%dx%d backbuf=%dx%d hash=%" PRIu64 "\n",
363 // d->snapshot_imgid, d->selected, roi.width, roi.height, bw, bh, hash);
364
365cleanup:
366 if(status != 0)
367 SNAP_LOG("[snapshots] refresh failed: reason=%s snapshot_imgid=%d selected=%u frozen_imgid=%d in=%ux%u "
368 "pipe_in=%dx%d pipe_out=%dx%d\n",
369 fail_reason, d->snapshot_imgid, d->selected, snapshot_dev->image_storage.id, buf.width, buf.height,
370 snapshot_pipe.iwidth, snapshot_pipe.iheight, snapshot_pipe.processed_width, snapshot_pipe.processed_height);
371 if(input_ready) dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
372 if(pipe_ready) dt_dev_pixelpipe_cleanup(&snapshot_pipe);
374
375 return status;
376}
377
378const char *name(struct dt_lib_module_t *self)
379{
380 return _("Snapshots");
381}
382
383const char **views(dt_lib_module_t *self)
384{
385 static const char *v[] = {"darkroom", NULL};
386 return v;
387}
388
390{
392}
393
395{
396 return 800;
397}
398
399// draw snapshot sign
400static void _draw_sym(cairo_t *cr, float x, float y, gboolean vertical, gboolean inverted)
401{
402 const double inv = inverted ? -0.1 : 1.0;
403
404 PangoRectangle ink;
405 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
406 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
407 pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(12) * PANGO_SCALE);
408 PangoLayout *layout = pango_cairo_create_layout(cr);
409 pango_layout_set_font_description(layout, desc);
410 pango_layout_set_text(layout, C_("snapshot sign", "S"), -1);
411 pango_layout_get_pixel_extents(layout, &ink, NULL);
412
413 if(vertical)
414 cairo_move_to(cr, x - (inv * ink.width * 1.2f), y - (ink.height / 2.0f) - DT_PIXEL_APPLY_DPI(3));
415 else
416 cairo_move_to(cr, x - (ink.width / 2.0), y + (-inv * (ink.height * 1.2f) - DT_PIXEL_APPLY_DPI(2)));
417
419 pango_cairo_show_layout(cr, layout);
420 pango_font_description_free(desc);
421 g_object_unref(layout);
422}
423
424/* expose snapshot over center viewport */
425void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx,
426 int32_t pointery)
427{
429 if(IS_NULL_PTR(d)) return;
431
432 if(!IS_NULL_PTR(d->snapshot_image))
433 {
434 if(d->selected >= 1 && d->selected <= d->size)
435 {
436 dt_lib_snapshot_t *s = d->snapshot + (d->selected - 1);
437 const float current_zoom_level = dt_dev_get_zoom_level(dev);
438 if(d->snapshot_imgid != s->imgid
439 || fabsf(d->snapshot_zoom_level - current_zoom_level) > 1e-6f)
440 {
442 if(!d->snapshot_image) return;
443 }
444 }
445
446 float snapshot_scale = 1.0f;
447 if(d->selected >= 1 && d->selected <= d->size)
448 {
449 const dt_lib_snapshot_t *s = d->snapshot + (d->selected - 1);
450 if(s->sample_scale > 1e-6f) snapshot_scale = s->sample_scale;
451 }
452 const float zoom_level = dt_dev_get_zoom_level(dev);
453 const float render_scale = zoom_level / snapshot_scale;
454 const float surface_width = cairo_image_surface_get_width(d->snapshot_image) / darktable.gui->ppd;
455 const float surface_height = cairo_image_surface_get_height(d->snapshot_image) / darktable.gui->ppd;
456 const double tx = 0.5 * width - dev->roi.x * surface_width * render_scale;
457 const double ty = 0.5 * height - dev->roi.y * surface_height * render_scale;
458
459 float image_box[4] = { 0.0f };
461 if(image_box[2] <= 0.0f || image_box[3] <= 0.0f) return;
462 d->vp_x = image_box[0];
463 d->vp_y = image_box[1];
464 d->vp_width = image_box[2];
465 d->vp_height = image_box[3];
466 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
467 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
468
469 /* set x,y,w,h of surface depending on split align and invert */
470 double x = d->vp_x;
471 double y = d->vp_y;
472 double w = d->vp_width;
473 double h = d->vp_height;
474 if(d->vertical)
475 {
476 x = d->inverted ? d->vp_x + d->vp_width * split_x : d->vp_x;
477 w = d->inverted ? d->vp_width * (1.0 - split_x) : d->vp_width * split_x;
478 }
479 else
480 {
481 y = d->inverted ? d->vp_y + d->vp_height * split_y : d->vp_y;
482 h = d->inverted ? d->vp_height * (1.0 - split_y) : d->vp_height * split_y;
483 }
484
485 const double size = DT_PIXEL_APPLY_DPI(d->inverted ? -15 : 15);
486
487 cairo_save(cri);
488 cairo_rectangle(cri, x, y, w, h);
489 cairo_clip(cri);
490 cairo_translate(cri, tx, ty);
491 cairo_scale(cri, render_scale, render_scale);
492 cairo_set_source_surface(cri, d->snapshot_image, 0.0, 0.0);
493 cairo_pattern_set_filter(cairo_get_source(cri), CAIRO_FILTER_NEAREST);
494 cairo_paint(cri);
495 cairo_restore(cri);
496
497 // draw the split line using the selected overlay color
499
500 cairo_set_line_width(cri, 1.);
501
502 if(d->vertical)
503 {
504 const double lx = d->vp_x + d->vp_width * split_x;
505 const double center = d->vp_y + 0.5 * d->vp_height;
506
507 // line
508 cairo_move_to(cri, lx, d->vp_y);
509 cairo_line_to(cri, lx, d->vp_y + d->vp_height);
510 cairo_stroke(cri);
511
512 if(!d->dragging)
513 {
514 // triangle
515 cairo_move_to(cri, lx, center - size);
516 cairo_line_to(cri, lx - (size * 1.2), center);
517 cairo_line_to(cri, lx, center + size);
518 cairo_close_path(cri);
519 cairo_fill(cri);
520
521 // symbol
522 _draw_sym(cri, lx, center, TRUE, d->inverted);
523 }
524 }
525 else
526 {
527 const double ly = d->vp_y + d->vp_height * split_y;
528 const double center = d->vp_x + 0.5 * d->vp_width;
529
530 // line
531 cairo_move_to(cri, d->vp_x, ly);
532 cairo_line_to(cri, d->vp_x + d->vp_width, ly);
533 cairo_stroke(cri);
534
535 if(!d->dragging)
536 {
537 // triangle
538 cairo_move_to(cri, center - size, ly);
539 cairo_line_to(cri, center, ly - (size * 1.2));
540 cairo_line_to(cri, center + size, ly);
541 cairo_close_path(cri);
542 cairo_fill(cri);
543
544 // symbol
545 _draw_sym(cri, center, ly, FALSE, d->inverted);
546 }
547 }
548
549 /* if mouse over control, lets draw center rotate control, hide if split is dragged */
550 if(!d->dragging)
551 {
552 const double half_handle_size = HANDLE_SIZE * 0.5;
553 const gint rx = (d->vertical ? d->vp_x + d->vp_width * split_x : d->vp_x + d->vp_width * 0.5)
554 - half_handle_size;
555 const gint ry = (d->vertical ? d->vp_y + d->vp_height * 0.5 : d->vp_y + d->vp_height * split_y)
556 - half_handle_size;
557
558 dt_draw_set_color_overlay(cri, TRUE, d->hover_rotation ? 1.0 : 0.3);
559
560 cairo_set_line_width(cri, 0.5);
561 dtgtk_cairo_paint_refresh(cri, rx, ry, HANDLE_SIZE, HANDLE_SIZE, 0, NULL);
562 }
563
564 d->on_going = FALSE;
565
566 if(d->hover_rotation) dt_control_queue_cursor_by_name("exchange");
567 else if(d->dragging) dt_control_queue_cursor_by_name("grabbing");
568 else
569 {
573 else
575 }
576
577 }
578}
579
580int button_released(struct dt_lib_module_t *self, double x, double y, int which, uint32_t state)
581{
583 if(d->snapshot_image && which == 1)
584 {
585 if(d->dragging)
586 {
587 d->dragging = FALSE;
588 d->hover_rotation = FALSE;
589 }
590 // Refresh mouse_moved event
591 return mouse_moved(self, x, y, 0.0, which);
592 }
593 return 0;
594}
595
597
598int button_pressed(struct dt_lib_module_t *self, double x, double y, double pressure, int which, int type,
599 uint32_t state)
600{
601 // only react to left click
602 if(which != 1) return 0;
603
605
606 if(d->snapshot_image)
607 {
608 if(d->on_going) return 1;
609 if(d->vp_width <= 0.0 || d->vp_height <= 0.0) return 0;
610 if(x < d->vp_x || x > d->vp_x + d->vp_width || y < d->vp_y || y > d->vp_y + d->vp_height) return 0;
611
612 const double xp = CLAMP((x - d->vp_x) / d->vp_width, 0.0, 1.0);
613 const double yp = CLAMP((y - d->vp_y) / d->vp_height, 0.0, 1.0);
614
615 if(d->hover_rotation)
616 {
617 /* let's rotate */
619
620 d->vertical = !d->vertical;
621 if(_lib_snapshot_rotation_cnt % 2) d->inverted = !d->inverted;
622
623 d->vp_xpointer = xp;
624 d->vp_ypointer = yp;
625 d->vp_xrotate = xp;
626 d->vp_yrotate = yp;
627 d->on_going = TRUE;
629 }
630 /* do the dragging !? */
631 else
632 {
633 d->dragging = TRUE;
634 d->vp_ypointer = yp;
635 d->vp_xpointer = xp;
636 d->vp_xrotate = 0.0;
637 d->vp_yrotate = 0.0;
639 }
640 return 1;
641 }
642 return 0;
643}
644
645int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
646{
648
649 if(d->snapshot_image)
650 {
651 if(d->vp_width <= 0.0 || d->vp_height <= 0.0) return 0;
652 const double xp = CLAMP((x - d->vp_x) / d->vp_width, 0.0, 1.0);
653 const double yp = CLAMP((y - d->vp_y) / d->vp_height, 0.0, 1.0);
654 d->hover_rotation = FALSE;
655
656 if(!d->dragging)
657 {
658 const double split_x = CLAMP(d->vp_xpointer, 0.0, 1.0);
659 const double split_y = CLAMP(d->vp_ypointer, 0.0, 1.0);
660 const double handle_mouse = (DT_GUI_MOUSE_EFFECT_RADIUS + HANDLE_SIZE) * 0.5;
661 const double rxc = d->vertical ? d->vp_x + d->vp_width * split_x : d->vp_x + d->vp_width * 0.5;
662 const double ryc = d->vertical ? d->vp_y + d->vp_height * 0.5 : d->vp_y + d->vp_height * split_y;
663 const double dx = x - rxc;
664 const double dy = y - ryc;
665 d->hover_rotation = (dx * dx + dy * dy) < (handle_mouse * handle_mouse);
666 }
667 else
668 {
669 /* update pointer pos */
670 d->vp_xpointer = xp;
671 d->vp_ypointer = yp;
672 }
674 return 1;
675 }
676
677 return 0;
678}
679
681{
683 d->num_snapshots = 0;
684 d->hover_rotation = FALSE;
685 if(d->snapshot_image)
686 {
687 cairo_surface_destroy(d->snapshot_image);
688 d->snapshot_image = NULL;
689 }
690 d->snapshot_imgid = UNKNOWN_IMAGE;
691 d->snapshot_zoom_level = -1.0f;
692
693 for(uint32_t k = 0; k < d->size; k++)
694 {
695 _lib_snapshot_clear_state(d->snapshot + k);
696 gtk_widget_hide(d->snapshot[k].button);
697 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), FALSE);
698 }
699
701}
702
704{
705 /* initialize ui widgets */
707 self->data = (void *)d;
708
709 /* initialize snapshot storages */
710 d->size = 4;
711 d->snapshot = (dt_lib_snapshot_t *)g_malloc0_n(d->size, sizeof(dt_lib_snapshot_t));
712 d->vp_x = 0.0;
713 d->vp_y = 0.0;
714 d->vp_width = 1.0;
715 d->vp_height = 1.0;
716 d->vp_xpointer = 0.5;
717 d->vp_ypointer = 0.5;
718 d->vp_xrotate = 0.0;
719 d->vp_yrotate = 0.0;
720 d->vertical = TRUE;
721 d->on_going = FALSE;
722 d->hover_rotation = FALSE;
723 d->snapshot_imgid = UNKNOWN_IMAGE;
724 d->snapshot_zoom_level = -1.0f;
725
726 /* initialize ui containers */
727 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
728 d->snapshots_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
729
730 /* create take snapshot button */
731 d->take_button = dt_action_button_new(self, N_("take snapshot"), _lib_snapshots_add_button_clicked_callback, self,
732 _("take snapshot to compare with another image "
733 "or the same image at another stage of development"), 0, 0);
734
735 /*
736 * initialize snapshots
737 */
738 char wdname[32] = { 0 };
739 char localtmpdir[PATH_MAX] = { 0 };
740 dt_loc_get_tmp_dir(localtmpdir, sizeof(localtmpdir));
741
742 for(int k = 0; k < d->size; k++)
743 {
744 /* create snapshot button */
745 d->snapshot[k].button = gtk_toggle_button_new_with_label(wdname);
746 GtkWidget *label = gtk_bin_get_child(GTK_BIN(d->snapshot[k].button));
747 gtk_widget_set_halign(label, GTK_ALIGN_START);
748 gtk_label_set_xalign(GTK_LABEL(label), 0);
749 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
750
751 g_signal_connect(G_OBJECT(d->snapshot[k].button), "clicked",
752 G_CALLBACK(_lib_snapshots_toggled_callback), self);
753
754 /* assign snapshot number to widget */
755 g_object_set_data(G_OBJECT(d->snapshot[k].button), "snapshot", GINT_TO_POINTER(k + 1));
756
757 /* setup filename for snapshot */
758 gchar *snapshot_file = g_strdup_printf("dt_snapshot_%d.png", k);
759 dt_concat_path_file(d->snapshot[k].filename, localtmpdir, snapshot_file);
760 dt_free(snapshot_file);
761
762 /* add button to snapshot box */
763 gtk_box_pack_start(GTK_BOX(d->snapshots_box), GTK_WIDGET(d->snapshot[k].button), FALSE, FALSE, 0);
764
765 /* prevent widget to show on external show all */
766 gtk_widget_set_no_show_all(d->snapshot[k].button, TRUE);
767 }
768
769 /* add snapshot box and take snapshot button to widget ui*/
770 gtk_box_pack_start(GTK_BOX(self->widget),
771 dt_ui_scroll_wrap(d->snapshots_box, 1, "plugins/darkroom/snapshots/windowheight",
773 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->take_button), TRUE, TRUE, 0);
774}
775
777{
778 if(IS_NULL_PTR(self->data)) return;
780
781 if(!IS_NULL_PTR(d) && !IS_NULL_PTR(d->snapshot_image))
782 {
783 cairo_surface_destroy(d->snapshot_image);
784 d->snapshot_image = NULL;
785 }
786 for(uint32_t k = 0; k < d->size; k++) _lib_snapshot_clear_state(d->snapshot + k);
787 dt_free(d->snapshot);
788
789 dt_free(self->data);
790}
791
792static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data)
793{
794 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
795 if(IS_NULL_PTR(self->data)) return;
797 if(IS_NULL_PTR(d)) return;
798
799 /* backup last snapshot slot */
800 if(d->size <= 0) return;
801 dt_lib_snapshot_t last = d->snapshot[d->size - 1];
802
803 /* rotate slots down to make room for new one on top */
804 for(int k = d->size - 1; k > 0; k--)
805 {
806 GtkWidget *b = d->snapshot[k].button;
807 d->snapshot[k] = d->snapshot[k - 1];
808 d->snapshot[k].button = b;
809 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k].button))),
810 gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[k - 1].button)))));
811 }
812
813 /* update top slot with new snapshot */
814 char label[64];
815 GtkWidget *b = d->snapshot[0].button;
816 d->snapshot[0] = last;
817 d->snapshot[0].button = b;
818 const gchar *name = _("original");
819 gchar *dynamic_name = NULL;
821 {
822 dt_dev_history_item_t *history_item = g_list_nth_data(darktable.develop->history,
824 if(!IS_NULL_PTR(history_item) && !IS_NULL_PTR(history_item->module))
825 {
826 dynamic_name = dt_history_item_get_name(history_item->module);
827 if(!IS_NULL_PTR(dynamic_name)) name = dynamic_name;
828 }
829 else
830 name = _("unknown");
831 }
832 g_snprintf(label, sizeof(label), "%s (%d)", name, dt_dev_get_history_end_ext(darktable.develop));
833 if(!IS_NULL_PTR(dynamic_name)) dt_free(dynamic_name);
834 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->snapshot[0].button))), label);
835
836 // Save current darkroom ROI and zoom as the reference view for this history snapshot.
837 dt_lib_snapshot_t *s = d->snapshot + 0;
838 if(IS_NULL_PTR(s)) return;
842 {
844 return;
845 }
846
847 /* update slots used */
848 if(d->num_snapshots != d->size) d->num_snapshots++;
849
850 /* show active snapshot slots */
851 for(uint32_t k = 0; k < d->num_snapshots; k++) gtk_widget_show(d->snapshot[k].button);
852
853}
854
855static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data)
856{
857 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
859 /* get current snapshot index */
860 int which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "snapshot"));
861
862 /* check if snapshot is activated */
863 if(gtk_toggle_button_get_active(widget))
864 {
865 /* free previous snapshot image before loading the newly-selected one */
866 if(!IS_NULL_PTR(d->snapshot_image))
867 {
868 cairo_surface_destroy(d->snapshot_image);
869 d->snapshot_image = NULL;
870 }
871
872 /* lets deactivate all togglebuttons except for self */
873 for(uint32_t k = 0; k < d->size; k++)
874 if(GTK_WIDGET(widget) != d->snapshot[k].button)
875 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->snapshot[k].button), FALSE);
876
877 /* setup snapshot */
878 dt_lib_snapshot_t *s = d->snapshot + (which - 1);
880 d->selected = which;
881 else
882 d->selected = 0;
883 }
884 else if(d->selected == (uint32_t)which)
885 {
886 d->selected = 0;
887 if(!IS_NULL_PTR(d->snapshot_image))
888 {
889 cairo_surface_destroy(d->snapshot_image);
890 d->snapshot_image = NULL;
891 }
892 }
893
894 /* redraw center view */
896}
897
898// clang-format off
899// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
900// vim: shiftwidth=2 expandtab tabstop=2 cindent
901// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
902// clang-format on
static double * inv
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:164
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
int type
char * name
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
void dt_control_change_cursor_by_name_and_flush(const char *curs_str)
Apply a named cursor immediatelly and flush display updates for immediate feedback.
Definition control.c:326
void dt_control_commit_cursor()
Definition control.c:334
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:398
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
void dt_concat_path_file(char destination[PATH_MAX], const char path[PATH_MAX], const char *const file)
Definition darktable.c:1889
darktable_t darktable
Definition darktable.c:181
#define UNKNOWN_IMAGE
Definition darktable.h:182
#define DT_MODULE(MODVER)
Definition darktable.h:140
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define PATH_MAX
Definition darktable.h:1062
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
dt_iop_module_t * dt_dev_get_module_instance(dt_develop_t *dev, const char *op, const char *multi_name, const int multi_priority)
Find a module instance by op name and instance metadata.
dt_iop_module_t * dt_dev_create_module_instance(dt_develop_t *dev, const char *op, const char *multi_name, const int multi_priority, gboolean use_next_priority)
Create a new module instance from an existing base .so.
void dt_dev_history_free_history(dt_develop_t *dev)
Free the whole history list attached to dev->history.
uint64_t dt_dev_history_compute_hash(dt_develop_t *dev)
Get the integrity checksum of the whole history stack. This should be done ONLY when history is chang...
void dt_dev_set_history_end_ext(struct dt_develop_t *dev, const uint32_t index)
Set the history end index (GUI perspective).
Definition develop.c:1665
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1659
void dt_dev_pixelpipe_propagate_formats(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_get_roi_out(dt_dev_pixelpipe_t *pipe, const int width_in, const int height_in, int *width, int *height)
void dt_dev_get_image_box_in_widget(const dt_develop_t *dev, const int32_t width, const int32_t height, float *box)
Get the displayed image rectangle in darkroom widget coordinates.
Definition develop.c:1730
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:188
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:887
float dt_dev_get_zoom_level(const dt_develop_t *dev)
Definition develop.c:1745
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:128
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1493
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:509
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:106
void dtgtk_cairo_paint_refresh(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
#define dt_pthread_rwlock_rdlock
Definition dtpthread.h:393
void dt_loc_get_tmp_dir(char *tmpdir, size_t bufsize)
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2713
#define DT_GUI_MOUSE_EFFECT_RADIUS
Definition gtk.h:70
@ DT_UI_RESIZE_DYNAMIC
Definition gtk.h:260
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
GList * dt_history_duplicate(GList *hist)
Deep-copy a history list.
dt_iop_module_t * dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
Definition imageop.c:3081
GList * dt_ioppr_iop_order_copy_deep(GList *iop_order_list)
Deep-copy an order list.
Definition iop_order.c:1996
static const float x
const float v
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1563
float *const restrict const size_t k
size_t size
Definition mipmap_cache.c:3
#define dt_mipmap_cache_get(A, B, C, D, E, F)
@ DT_MIPMAP_BLOCKING
#define dt_mipmap_cache_release(A, B)
@ DT_MIPMAP_FULL
gboolean dt_dev_pixelpipe_cache_peek(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry, const int preferred_devid, void **cl_mem_output)
Non-owning lookup of an existing cache line.
size_t dt_pixel_cache_entry_get_size(dt_pixel_cache_entry_t *entry)
Peek the size (in bytes) reserved for the host buffer of a cache entry.
void dt_dev_pixelpipe_cache_rdlock_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
void dt_dev_pixelpipe_set_input(dt_dev_pixelpipe_t *pipe, int32_t imgid, int width, int height, float iscale, dt_mipmap_size_t size)
void dt_dev_pixelpipe_set_icc(dt_dev_pixelpipe_t *pipe, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent)
int dt_dev_pixelpipe_init_preview(dt_dev_pixelpipe_t *pipe, dt_develop_t *dev)
void dt_dev_pixelpipe_create_nodes(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_cleanup(dt_dev_pixelpipe_t *pipe)
int dt_dev_pixelpipe_process(dt_dev_pixelpipe_t *pipe, dt_iop_roi_t roi)
#define dt_dev_pixelpipe_synch_all(pipe)
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
static int _lib_snapshot_rotation_cnt
Definition snapshots.c:596
void gui_reset(dt_lib_module_t *self)
Definition snapshots.c:680
#define HANDLE_SIZE
Definition snapshots.c:63
static void _draw_sym(cairo_t *cr, float x, float y, gboolean vertical, gboolean inverted)
Definition snapshots.c:400
static void _lib_snapshots_add_button_clicked_callback(GtkWidget *widget, gpointer user_data)
Definition snapshots.c:792
static int _lib_snapshot_capture_state(dt_lib_snapshot_t *snapshot, dt_develop_t *source)
Freeze the current darkroom develop state into one snapshot-local develop context.
Definition snapshots.c:137
static void _lib_snapshots_toggled_callback(GtkToggleButton *widget, gpointer user_data)
Definition snapshots.c:855
void gui_cleanup(dt_lib_module_t *self)
Definition snapshots.c:776
static void _lib_snapshot_clear_state(dt_lib_snapshot_t *snap)
Definition snapshots.c:112
int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
Definition snapshots.c:645
static int _lib_snapshots_refresh_pipe_image(dt_lib_module_t *self, dt_lib_snapshot_t *snap)
Recompute the selected snapshot image from a dedicated preview pipe at one history fence.
Definition snapshots.c:222
uint32_t container(dt_lib_module_t *self)
Definition snapshots.c:389
int button_pressed(struct dt_lib_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition snapshots.c:598
void gui_init(dt_lib_module_t *self)
Definition snapshots.c:703
int position()
Definition snapshots.c:394
const char ** views(dt_lib_module_t *self)
Definition snapshots.c:383
void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition snapshots.c:425
int button_released(struct dt_lib_module_t *self, double x, double y, int which, uint32_t state)
Definition snapshots.c:580
#define SNAP_LOG(...)
Definition snapshots.c:62
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:790
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_mipmap_cache_t * mipmap_cache
Definition darktable.h:776
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_view_manager_t * view_manager
Definition darktable.h:772
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
dt_colorspaces_color_profile_type_t icc_type
dt_backbuf_t backbuf
dt_iop_color_intent_t icc_intent
int32_t gui_attached
Definition develop.h:162
GList * iop_order_list
Definition develop.h:285
dt_image_t image_storage
Definition develop.h:259
GList * iop
Definition develop.h:279
dt_pthread_rwlock_t history_mutex
Definition develop.h:263
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
GList * history
Definition develop.h:275
struct dt_develop_t::@17 roi
double ppd
Definition gtk.h:200
int32_t id
Definition image.h:319
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
dt_develop_t * develop
Definition snapshots.c:71
GtkWidget * button
Definition snapshots.c:68
int32_t history_end
Definition snapshots.c:73
char filename[PATH_MAX]
Definition snapshots.c:74
uint32_t num_snapshots
Definition snapshots.c:85
GtkWidget * take_button
Definition snapshots.c:105
int32_t snapshot_imgid
Definition snapshots.c:95
uint32_t selected
Definition snapshots.c:82
gboolean hover_rotation
Definition snapshots.c:103
dt_lib_snapshot_t * snapshot
Definition snapshots.c:91
cairo_surface_t * snapshot_image
Definition snapshots.c:94
float snapshot_zoom_level
Definition snapshots.c:96
GtkWidget * snapshots_box
Definition snapshots.c:80
struct dt_view_manager_t::@67 proxy
void(* set_default_cursor)(struct dt_view_t *view, double x, double y)
Definition view.h:246
struct dt_view_t * view
Definition view.h:244
struct dt_view_manager_t::@67::@70 darkroom
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER