Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dev_history.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2015, 2018 johannes hanika.
4 Copyright (C) 2010 Alexandre Prokoudine.
5 Copyright (C) 2010-2011 Bruce Guenter.
6 Copyright (C) 2010-2012 Henrik Andersson.
7 Copyright (C) 2011 Karl Mikaelsson.
8 Copyright (C) 2011 Mikko Ruohola.
9 Copyright (C) 2011 Omari Stephens.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2011 Rostyslav Pidgornyi.
12 Copyright (C) 2011-2019 Tobias Ellinghaus.
13 Copyright (C) 2012-2014, 2016, 2020-2021 Aldric Renaudin.
14 Copyright (C) 2012 Antony Dovgal.
15 Copyright (C) 2012 Moritz Lipp.
16 Copyright (C) 2012 Richard Wonka.
17 Copyright (C) 2012-2014, 2016-2017 Ulrich Pegelow.
18 Copyright (C) 2013-2022 Pascal Obry.
19 Copyright (C) 2014, 2020 Dan Torop.
20 Copyright (C) 2014 parafin.
21 Copyright (C) 2014-2015 Pedro Côrte-Real.
22 Copyright (C) 2014-2017 Roman Lebedev.
23 Copyright (C) 2016 Alexander V. Smal.
24 Copyright (C) 2017, 2021 luzpaz.
25 Copyright (C) 2018-2019 Edgardo Hoszowski.
26 Copyright (C) 2019 Alexander Blinne.
27 Copyright (C) 2019-2020, 2022-2026 Aurélien PIERRE.
28 Copyright (C) 2019-2021 Diederik Ter Rahe.
29 Copyright (C) 2019-2022 Hanno Schwalm.
30 Copyright (C) 2019 Heiko Bauke.
31 Copyright (C) 2019-2020 Philippe Weyland.
32 Copyright (C) 2020-2021 Chris Elston.
33 Copyright (C) 2020 GrahamByrnes.
34 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
35 Copyright (C) 2020 Hubert Kowalski.
36 Copyright (C) 2020 JP Verrue.
37 Copyright (C) 2020-2021 Ralf Brown.
38 Copyright (C) 2021 paolodepetrillo.
39 Copyright (C) 2021 Sakari Kapanen.
40 Copyright (C) 2022 Martin Bařinka.
41 Copyright (C) 2023 Alynx Zhou.
42 Copyright (C) 2023 lologor.
43 Copyright (C) 2023 Luca Zulberti.
44 Copyright (C) 2023 Ricky Moon.
45 Copyright (C) 2025-2026 Guillaume Stutin.
46 Copyright (C) 2025 Miguel Moquillon.
47
48 darktable is free software: you can redistribute it and/or modify
49 it under the terms of the GNU General Public License as published by
50 the Free Software Foundation, either version 3 of the License, or
51 (at your option) any later version.
52
53 darktable is distributed in the hope that it will be useful,
54 but WITHOUT ANY WARRANTY; without even the implied warranty of
55 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56 GNU General Public License for more details.
57
58 You should have received a copy of the GNU General Public License
59 along with darktable. If not, see <http://www.gnu.org/licenses/>.
60*/
62#include "control/control.h"
63#include "common/conf.h"
64#include "history/history.h"
65#include "history/notify.h"
66#include "history/presets.h" // FOR_RAW/FOR_LDR/... matched against the image at auto-apply
68
69#include "common/undo.h"
70#include "caches/image_cache.h"
72#include "develop/iop_order.h"
73#include "develop/dev_history.h"
74#include "develop/blend.h"
75#include "develop/develop.h"
76#include "develop/imageop.h"
77#include "develop/masks.h"
78#include "develop/supervisor.h"
80#include "system/atomic.h"
81
82
83#include <inttypes.h>
84
85#include <glib.h>
86#include "common/hash.h"
87#include "gui/application.h"
88
89static void _process_history_db_entry(dt_develop_t *dev, const int32_t imgid, const int id, const int num,
90 const int modversion, const char *operation, const void *module_params,
91 const int param_length, const gboolean enabled, const void *blendop_params,
92 const int bl_length, const int blendop_version, const int multi_priority,
93 const char *multi_name, const char *preset_name, int *legacy_params,
94 const gboolean presets);
95
103
104gboolean dt_dev_init_default_history(dt_develop_t *dev, const int32_t imgid, gboolean apply_auto_presets);
105
126static void _dev_history_db_row_cb(void *user_data, const int32_t id, const int num, const int modversion,
127 const char *operation, const void *module_params, const int param_length,
128 const gboolean enabled, const void *blendop_params, const int bl_length,
129 const int blendop_version, const int multi_priority, const char *multi_name,
130 const char *preset_name)
131{
133 _process_history_db_entry(ctx->dev, ctx->imgid, id, num, modversion, operation, module_params, param_length, enabled,
134 blendop_params, bl_length, blendop_version, multi_priority, multi_name, preset_name,
135 ctx->legacy_params, ctx->presets);
136}
137
138// returns the first history item with hist->module == module
140{
141 dt_dev_history_item_t *hist_mod = NULL;
142 for(GList *history = g_list_first(history_list); history; history = g_list_next(history))
143 {
144 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
145
146 if(hist->module == module)
147 {
148 hist_mod = hist;
149 break;
150 }
151 }
152 return hist_mod;
153}
154
156{
157 dt_dev_history_item_t *hist_mod = NULL;
158 for(GList *history = g_list_nth(history_list, history_end -1); history; history = g_list_previous(history))
159 {
160 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
161
162 if(hist->module == module)
163 {
164 hist_mod = hist;
165 break;
166 }
167 }
168 return hist_mod;
169}
170
172 const gboolean enabled, const void *params, const int32_t params_size,
173 const dt_develop_blend_params_t *blend_params, GList *forms)
174{
175 if(IS_NULL_PTR(hist) || IS_NULL_PTR(module)) return FALSE;
176
177 if(IS_NULL_PTR(hist->params))
178 {
179 hist->params = g_malloc0(module->params_size);
180 if(IS_NULL_PTR(hist->params)) return FALSE;
181 }
182
183 if(IS_NULL_PTR(hist->blend_params))
184 {
185 hist->blend_params = g_malloc0(sizeof(dt_develop_blend_params_t));
186 if(IS_NULL_PTR(hist->blend_params)) return FALSE;
187 }
188
189 if(!IS_NULL_PTR(hist->forms))
190 {
191 g_list_free_full(hist->forms, (void (*)(void *))dt_masks_form_unref);
192 hist->forms = NULL;
193 }
194 hist->forms = forms;
195
196 module->enabled = enabled;
197 hist->enabled = enabled;
198 hist->module = module;
199 hist->params_size = module->params_size;
200 hist->module_version = module->version();
203 hist->iop_order = module->iop_order;
204 hist->multi_priority = module->multi_priority;
205 g_strlcpy(hist->op_name, module->op, sizeof(hist->op_name));
206 g_strlcpy(hist->multi_name, module->multi_name, sizeof(hist->multi_name));
207
208 const void *src_params = params ? params : module->params;
209 const int32_t src_size = params ? params_size : module->params_size;
210 const int32_t sz = MIN(module->params_size, src_size);
211 if(!IS_NULL_PTR(src_params) && !IS_NULL_PTR(hist->params) && sz > 0) memcpy(hist->params, src_params, sz);
212
213 const dt_develop_blend_params_t *src_blend = blend_params ? blend_params : module->blend_params;
214 if(src_blend && hist->blend_params) memcpy(hist->blend_params, src_blend, sizeof(dt_develop_blend_params_t));
215
216 // Apply to module to keep the hash in sync.
217 if(hist->params && module->params && module->params_size > 0)
218 memcpy(module->params, hist->params, module->params_size);
220
221 // Committing a new/updated item: when no forms snapshot was taken (include_masks declined),
222 // the live dev->forms IS the state being persisted, so hash against it — a module whose
223 // blend params link a mask group must never hash blind to that group's content (#1060).
224 // History REPLAY must not use this helper's fallback: it hashes via _history_to_module()
225 // against the snapshot accumulated at the item's position instead.
226 dt_iop_compute_module_hash(module, !IS_NULL_PTR(hist->forms) ? hist->forms : dev->forms);
227 hist->hash = module->hash;
228
229 return TRUE;
230}
231
233{
234 int max_priority = 0;
235 for(const GList *l = g_list_first(dev->iop); l; l = g_list_next(l))
236 {
237 const dt_iop_module_t *m = (const dt_iop_module_t *)l->data;
238 if(strcmp(m->op, op)) continue;
239 max_priority = MAX(max_priority, m->multi_priority);
240 }
241 return max_priority + 1;
242}
243
244dt_iop_module_t *dt_dev_get_module_instance(dt_develop_t *dev, const char *op, const char *multi_name,
245 const int multi_priority)
246{
247 const char *name = (multi_name && *multi_name) ? multi_name : NULL;
248 dt_iop_module_t *module = dt_iop_get_module_by_instance_name(dev->iop, op, name);
249 if(IS_NULL_PTR(module) && (IS_NULL_PTR(name) || *name == '\0'))
250 module = dt_iop_get_module_by_op_priority(dev->iop, op, multi_priority);
251 if(IS_NULL_PTR(module) && (IS_NULL_PTR(name) || *name == '\0'))
252 module = dt_iop_get_module_by_op_priority(dev->iop, op, 0);
253 return module;
254}
255
256dt_iop_module_t *dt_dev_create_module_instance(dt_develop_t *dev, const char *op, const char *multi_name,
257 const int multi_priority, gboolean use_next_priority)
258{
260 if(IS_NULL_PTR(base)) base = dt_iop_get_module_by_op_priority(dev->iop, op, -1);
261 if(IS_NULL_PTR(base)) return NULL;
262
263 if((base->flags() & IOP_FLAGS_ONE_INSTANCE) == IOP_FLAGS_ONE_INSTANCE)
264 return base;
265
266 dt_iop_module_t *module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
267 if(IS_NULL_PTR(module)) return NULL;
268
269 if(dt_iop_load_module(module, base->so, dev))
270 {
271 fprintf(stderr, "[dt_dev_create_module_instance] can't load module %s\n", op);
272 dt_free(module);
273 return NULL;
274 }
275
276 module->instance = base->instance;
277 module->enabled = FALSE;
278 module->multi_priority = use_next_priority ? dt_dev_next_multi_priority_for_op(dev, op) : multi_priority;
279 g_strlcpy(module->multi_name, multi_name ? multi_name : "", sizeof(module->multi_name));
280
281 dev->iop = g_list_append(dev->iop, module);
282 return module;
283}
284
286 const dt_iop_module_t *mod_src)
287{
288 mod_dest->enabled = mod_src->enabled;
289
290 const int32_t sz_dest = mod_dest->params_size;
291 const int32_t sz_src = mod_src->params_size;
292
293 // If both param sizes don't match, we have a serious problem
294 assert(sz_dest == sz_src);
295 if(sz_dest != sz_src) return 1;
296
297 const int32_t sz = MIN(sz_dest, sz_src);
298 if(sz > 0) memcpy(mod_dest->params, mod_src->params, sz);
299
300 if(mod_src->blend_params) dt_iop_commit_blend_params(mod_dest, mod_src->blend_params);
301
302 if(dev_src && dt_masks_copy_used_forms_for_module(dev_dest, dev_src, mod_src)) return 1;
303 return 0;
304}
305
307 const dt_dev_history_item_t *hist_src, dt_iop_module_t *mod_dest,
308 dt_dev_history_item_t **out_hist)
309{
310 if(IS_NULL_PTR(hist_src) || IS_NULL_PTR(hist_src->module) || IS_NULL_PTR(mod_dest) || IS_NULL_PTR(out_hist))
311 {
313 "[dt_dev_history_item_from_source_history_item] invalid input: hist=%s hist_module=%s dest=%s out=%s\n",
314 hist_src ? "yes" : "no", hist_src && hist_src->module ? "yes" : "no",
315 mod_dest ? "yes" : "no", out_hist ? "yes" : "no");
316 return 1;
317 }
318
320 if(IS_NULL_PTR(hist))
321 {
323 "[dt_dev_history_item_from_source_history_item] allocation failed: src=%s multi='%s'\n",
324 hist_src->module->op, hist_src->module->multi_name);
325 return 1;
326 }
327
328 if(dt_masks_copy_used_forms_for_module(dev_dest, dev_src, hist_src->module))
329 {
331 "[dt_dev_history_item_from_source_history_item] Forms copy failed: src=%s multi='%s'\n",
332 hist_src->module->op, hist_src->module->multi_name);
334 return 1;
335 }
336
337 gboolean raster_used = FALSE;
338 gboolean drawn_used = FALSE;
339 gboolean parametric_used = FALSE;
340 GList *forms_snapshot = NULL;
341 if(dt_iop_module_needs_mask_history_ext(hist_src->module, &raster_used, &drawn_used, &parametric_used))
342 {
343 if(drawn_used)
344 {
345 forms_snapshot = dt_masks_snapshot_current_forms(dev_dest, FALSE);
346 if(IS_NULL_PTR(forms_snapshot))
347 {
349 "[dt_dev_history_item_from_source_history_item] %s '%s' uses drawn mask but there is no destination mask forms to snapshot\n",
350 hist_src->module->op, hist_src->module->multi_name);
351
353 return 1;
354 }
355 }
356 }
357
358 if(!dt_dev_history_item_update_from_params(dev_dest, hist, mod_dest, hist_src->enabled, hist_src->params,
359 hist_src->module->params_size, hist_src->blend_params, forms_snapshot))
360 {
362 "[dt_dev_history_item_from_source_history_item] params update failed: src=%s multi='%s' "
363 "dest=%s multi='%s' src_params=%d dest_params=%d\n",
364 hist_src->module->op, hist_src->module->multi_name, mod_dest->op, mod_dest->multi_name,
365 hist_src->module->params_size, mod_dest->params_size);
367 return 1;
368 }
369
370 *out_hist = hist;
371 return 0;
372}
373
374int dt_dev_merge_history_into_image(dt_develop_t *dev_src, int32_t dest_imgid, const GList *mod_list,
375 gboolean merge_iop_order, const dt_history_merge_strategy_t mode,
376 const gboolean paste_instances, const char *source_label,
377 dt_hm_batch_state_t *batch)
378{
379 if(dest_imgid <= 0) return 1;
380 if(IS_NULL_PTR(mod_list)) return 0;
381
382 dt_develop_t dev_dest = { 0 };
383 dt_dev_init(&dev_dest, FALSE);
384 const gboolean first_run = dt_dev_reload_history_items(&dev_dest, dest_imgid);
385
386 if(first_run)
387 {
388 /* Match the persistent state produced by opening the destination in darkroom
389 * after a history deletion: the first-run defaults, auto-presets, image
390 * flags and resulting module order must exist before paste/style merging.
391 */
392 dt_image_t *image = dt_image_cache_get(dest_imgid, 'w');
393 if(!IS_NULL_PTR(image))
394 {
395 *image = dev_dest.image_storage;
397 }
398
399 dt_dev_write_history_ext(&dev_dest, dest_imgid);
400 }
401
402 /* Honor the high-level "use source pipeline order" choice on every image, including freshly recreated
403 * destinations. The topological solve still falls back to destination order when source constraints are
404 * unsatisfiable, so this stays safe while making the batch decision apply uniformly. */
405 const int ret_val = dt_history_merge(&dev_dest, dev_src, dest_imgid, mod_list, merge_iop_order, mode,
406 paste_instances, source_label, batch);
407
408 if(ret_val == 0)
409 {
411 dt_dev_write_history(&dev_dest, FALSE);
412 }
413
414 dt_dev_cleanup(&dev_dest);
415 return ret_val;
416}
417
425static dt_dev_history_item_t *_search_history_by_op(dt_develop_t *dev, const dt_iop_module_t *module) REQUIRES_SHARED(dev->history_mutex)
426{
427 dt_dev_history_item_t *hist_mod = NULL;
428 for(GList *history = g_list_first(dev->history); history; history = g_list_next(history))
429 {
430 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
431 if(!hist || !hist->module) continue;
432
433 if(strcmp(hist->module->op, module->op) == 0)
434 {
435 hist_mod = hist;
436 break;
437 }
438 }
439 return hist_mod;
440}
441
454 const dt_iop_module_t *mod_src,
455 gboolean *created,
456 gboolean *reused_base) REQUIRES_SHARED(dev_dest->history_mutex)
457{
458 *created = FALSE;
459 *reused_base = FALSE;
460
461 if(mod_src->flags() & IOP_FLAGS_ONE_INSTANCE)
462 return dt_iop_get_module_by_op_priority(dev_dest->iop, mod_src->op, -1);
463
464 dt_iop_module_t *module
465 = dt_dev_get_module_instance(dev_dest, mod_src->op, mod_src->multi_name, mod_src->multi_priority);
466 if(module) return module;
467
468 if(_search_history_by_op(dev_dest, mod_src) == NULL)
469 {
470 module = dt_iop_get_module_by_op_priority(dev_dest->iop, mod_src->op, -1);
471 if(!IS_NULL_PTR(module))
472 {
473 *reused_base = TRUE;
474 return module;
475 }
476 }
477
478 module = dt_dev_create_module_instance(dev_dest, mod_src->op, mod_src->multi_name, mod_src->multi_priority, FALSE);
479 if(module) *created = TRUE;
480
481 return module;
482}
483
484// dev_src is used only to copy masks, if no mask will be copied it can be null
486 REQUIRES(dev_dest->history_mutex)
487{
488 gboolean created = FALSE;
489 gboolean reused_base = FALSE;
490 dt_iop_module_t *module = _history_merge_resolve_dest_instance(dev_dest, mod_src, &created, &reused_base);
491 if(IS_NULL_PTR(module)) return 0;
492
493 if(mod_src->flags() & IOP_FLAGS_ONE_INSTANCE)
494 {
495 dt_print(DT_DEBUG_HISTORY, "[dt_history_merge_module_into_history] %s (%s) will be overriden in target history by parameters from source history\n",
496 mod_src->name(), mod_src->multi_name);
497 }
498 else if(created)
499 {
500 dt_print(DT_DEBUG_HISTORY, "[dt_history_merge_module_into_history] %s (%s) will be inserted as a new instance in target history\n",
501 mod_src->name(), mod_src->multi_name);
502 }
503 else if(reused_base)
504 {
505 dt_print(DT_DEBUG_HISTORY, "[dt_history_merge_module_into_history] %s (%s) will be enabled in target history with parameters from source history\n",
506 mod_src->name(), mod_src->multi_name);
507 }
508
509 g_strlcpy(module->multi_name, mod_src->multi_name, sizeof(module->multi_name));
510
511 if(dt_dev_copy_module_contents(dev_dest, dev_src, module, mod_src)) return 0;
512
513 dt_dev_add_history_item_ext(dev_dest, module, FALSE, FALSE);
514
515 return 1;
516}
525GList *dt_history_duplicate(GList *hist)
526{
527 GList *result = NULL;
528 for(GList *h = g_list_first(hist); h; h = g_list_next(h))
529 {
530 const dt_dev_history_item_t *old = (dt_dev_history_item_t *)(h->data);
532
533 memcpy(new, old, sizeof(dt_dev_history_item_t));
534 dt_atomic_set_int(&new->refcount, 1); // independent deep copy, not a shared reference
535
536 dt_iop_module_t *module = (old->module) ? old->module : dt_iop_get_module(old->op_name);
537
538 if(old->params && old->params_size > 0)
539 {
540 new->params = malloc(old->params_size);
541 memcpy(new->params, old->params, old->params_size);
542 }
543
544 if(IS_NULL_PTR(module))
545 fprintf(stderr, "[_duplicate_history] can't find base module for %s\n", old->op_name);
546
547 if(old->blend_params && old->blendop_params_size > 0)
548 {
549 new->blend_params = malloc(old->blendop_params_size);
550 memcpy(new->blend_params, old->blend_params, old->blendop_params_size);
551 }
552
553 if(old->forms)
554 {
555 // Share references with old->forms instead of deep-copying: forms are refcounted
556 // and cloned on write (dt_masks_cow_touch), not on snapshot (see masks_history.h).
557 new->forms = g_list_copy(old->forms);
558 for(GList *form_node = new->forms; form_node; form_node = g_list_next(form_node))
559 dt_masks_form_ref((dt_masks_form_t *)form_node->data);
560 }
561
562 result = g_list_prepend(result, new);
563 }
564
565 return g_list_reverse(result); // list was built in reverse order, so un-reverse it
566}
567
568/* Installed by dt_dev_history_gui_init(); absent under ansel-cli and in tests. */
570
575
577
582
591
593{
594 dt_iop_module_t *module;
596};
597
605static void _history_invalidate_cb(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item)
606{
607 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
608 dt_undo_history_t *hist = (dt_undo_history_t *)item;
609 dt_dev_invalidate_history_module(hist->before_snapshot, module);
610 dt_dev_invalidate_history_module(hist->after_snapshot, module);
611}
612
618
624static void _history_undo_data_free(gpointer data)
625{
626 dt_undo_history_t *hist = (dt_undo_history_t *)data;
627 if(IS_NULL_PTR(hist)) return;
628 g_list_free_full(hist->before_snapshot, dt_dev_free_history_item);
629 hist->before_snapshot = NULL;
630 g_list_free_full(hist->after_snapshot, dt_dev_free_history_item);
631 hist->after_snapshot = NULL;
632 g_list_free_full(hist->before_iop_order_list, dt_free_gpointer);
633 hist->before_iop_order_list = NULL;
634 g_list_free_full(hist->after_iop_order_list, dt_free_gpointer);
635 hist->after_iop_order_list = NULL;
636 dt_free(hist);
637}
638
650/* No REQUIRES: this is a dt_undo_t callback, so its signature is fixed and carries no
651 * dt_develop_t to name the lock on -- it reaches the dev through user_data. The lock is
652 * taken inside. NO_THREAD_SAFETY_ANALYSIS rather than a contract clang cannot express. */
653static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs) NO_THREAD_SAFETY_ANALYSIS
654{
655 if(type != DT_UNDO_HISTORY) return;
656
657 dt_develop_t *dev = (dt_develop_t *)user_data;
658 dt_undo_history_t *hist = (dt_undo_history_t *)data;
659 if(IS_NULL_PTR(dev) || IS_NULL_PTR(hist)) return;
660
661 GList *snapshot = (action == DT_ACTION_UNDO) ? hist->before_snapshot : hist->after_snapshot;
662 const int history_end = (action == DT_ACTION_UNDO) ? hist->before_end : hist->after_end;
663
664 GList *iop_order_list
665 = (action == DT_ACTION_UNDO) ? hist->before_iop_order_list : hist->after_iop_order_list;
666
667 GList *history_temp = dt_history_duplicate(snapshot);
668 GList *iop_order_temp = dt_ioppr_iop_order_copy_deep(iop_order_list);
669
672 dev->history = history_temp;
673 dt_dev_set_history_end_ext(dev, history_end);
674 g_list_free_full(dev->iop_order_list, dt_free_gpointer);
675 dev->iop_order_list = iop_order_temp;
679
681 // TODO: check if we need to rebuild the full pipeline and do it only if needed
683
684 /* Republish the darkroom geometry, like every other path that replays history in bulk:
685 * dt_dev_pop_history_items() does it, so does the history-row click in libs/history.c, and
686 * so do image load and compress/truncate. This one did not, and undo is the path where it
687 * matters most -- undoing a crop changes the processed size, and without this the geometry
688 * record (hence natural_scale, the preview dimensions, and everything the pipes plan from)
689 * kept describing the pre-undo image until some unrelated later caller happened to refresh
690 * it. dt_dev_history_pixelpipe_update() above rebuilds the virtual pipe, so this only folds
691 * and publishes.
692 *
693 * After the history lock is released, never inside it: dt_dev_get_thumbnail_size() resyncs
694 * the virtual pipe, which takes history_mutex as reader, and the same-thread reentrancy that
695 * makes this survivable is a safety net, not a licence -- the sibling call sites all note the
696 * same ordering constraint. */
698
699 if(dev->gui_module)
700 {
703 // the blending panel's widgets are the GUI half's to poke (dev_history_gui.c)
706 }
707
708 // Ensure all UI pieces (history treeview, iop order, etc.) resync after undo/redo.
709 // Undo callbacks bypass dt_dev_undo_end_record(), so we need to raise the change signal here.
710 // The interactive session's panels resync through the changed-handler; the
711 // dev == dt_dev_get_global() test is engine knowledge (only the interactive dev has
712 // panels), so it stays here rather than in the handler.
713 if(dev->gui_attached && dev == dt_dev_get_global())
715}
716
724
725void dt_dev_history_undo_start_record_locked(dt_develop_t *dev) REQUIRES(dev->history_mutex)
726{
727 if(IS_NULL_PTR(dev)) return;
728
729 if(dev->undo_history_depth == 0)
730 {
731 g_list_free_full(dev->undo_history_before_snapshot, dt_dev_free_history_item);
732 dev->undo_history_before_snapshot = NULL;
733 g_list_free_full(dev->undo_history_before_iop_order_list, dt_free_gpointer);
734 dev->undo_history_before_iop_order_list = NULL;
735 dev->undo_history_before_end = 0;
736
737 dev->undo_history_before_snapshot = dt_history_duplicate(dev->history);
738 dev->undo_history_before_end = dt_dev_get_history_end_ext(dev);
739 dev->undo_history_before_iop_order_list = dt_ioppr_iop_order_copy_deep(dev->iop_order_list);
740 }
741
742 dev->undo_history_depth++;
743}
744
752
753void dt_dev_history_undo_end_record_locked(dt_develop_t *dev) REQUIRES(dev->history_mutex)
754{
755 if(IS_NULL_PTR(dev) || dev->undo_history_depth <= 0) return;
756
757 dev->undo_history_depth--;
758 if(dev->undo_history_depth != 0) return;
759
760 if(IS_NULL_PTR(dev->undo_history_before_snapshot)) return;
761
762 dt_undo_history_t *hist = malloc(sizeof(dt_undo_history_t));
763 hist->before_snapshot = dev->undo_history_before_snapshot;
764 hist->before_end = dev->undo_history_before_end;
765 hist->before_iop_order_list = dev->undo_history_before_iop_order_list;
766 dev->undo_history_before_snapshot = NULL;
767 dev->undo_history_before_end = 0;
768 dev->undo_history_before_iop_order_list = NULL;
769
770 hist->after_snapshot = dt_history_duplicate(dev->history);
772 hist->after_iop_order_list = dt_ioppr_iop_order_copy_deep(dev->iop_order_list);
773
774 if(dev->gui_module)
775 {
776 hist->mask_edit_mode = dt_masks_get_edit_mode(dev->gui_module);
777 hist->request_mask_display = dev->gui_module->request_mask_display;
778 }
779 else
780 {
783 }
784
786}
787
788
796static void _remove_history_leaks(dt_develop_t *dev) REQUIRES(dev->history_mutex)
797{
798 GList *history = g_list_nth(dev->history, dt_dev_get_history_end_ext(dev));
799 while(history)
800 {
801 // We need to use a while because we are going to dynamically remove entries at the end
802 // of the list, so we can't know the number of iterations
803 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
804 if(IS_NULL_PTR(hist) || IS_NULL_PTR(hist->module))
805 {
807 "[dt_dev_add_history_item_ext] archival history item %s at %i is past history limit (%i) and will be kept\n",
808 hist ? hist->op_name : "(null)", g_list_index(dev->history, hist), dt_dev_get_history_end_ext(dev) - 1);
809 history = g_list_next(history);
810 continue;
811 }
812
813 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] history item %s at %i is past history limit (%i)\n",
814 hist->module->op, g_list_index(dev->history, hist), dt_dev_get_history_end_ext(dev) - 1);
815
816 // In case user wants to insert new history items before auto-enabled or mandatory modules,
817 // we forbid it, unless we already have at least one lower history entry.
818
819 // Check if an earlier instance of mandatory module exists
820 gboolean earlier_entry = FALSE;
821 if((hist->module->hide_enable_button || hist->module->default_enabled))
822 {
823 for(GList *prior_history = g_list_previous(history); prior_history;
824 prior_history = g_list_previous(prior_history))
825 {
826 dt_dev_history_item_t *prior_hist = (dt_dev_history_item_t *)(prior_history->data);
827 if(!prior_hist || !prior_hist->module) continue;
828 if(prior_hist->module->so == hist->module->so)
829 {
830 earlier_entry = TRUE;
831 break;
832 }
833 }
834 }
835
836 // In case we delete the current link, we need to update the incrementer now
837 // to not loose the reference
838 GList *link = history;
839 history = g_list_next(history);
840
841 // Finally: attempt removing the obsoleted entry
842 if((!hist->module->hide_enable_button && !hist->module->default_enabled)
843 || earlier_entry)
844 {
845 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] removing obsoleted history item: %s at %i\n", hist->module->op, g_list_index(dev->history, hist));
847 dt_supervisor_history(DT_SV_DELETE, hist->hash, hist->module->op, hist->module->multi_priority,
848 hist->module->multi_name, hist->module->iop_order,
849 g_list_index(dev->history, hist), dev->image_storage.id, hist->enabled,
850 hist->module, hist->params, hist->blend_params, hist->forms);
852 dev->history = g_list_delete_link(dev->history, link);
853 }
854 else
855 {
856 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] obsoleted history item will be kept: %s at %i\n", hist->module->op, g_list_index(dev->history, hist));
857 }
858 }
859}
860
861gboolean dt_dev_add_history_item_ext(dt_develop_t *dev, struct dt_iop_module_t *module, gboolean enable,
862 gboolean force_new_item) REQUIRES(dev->history_mutex)
863{
864 // If this history item is the first for this module,
865 // we need to notify the pipeline that its topology may change (aka insert a new node).
866 // Since changing topology is expensive, we want to do it only when needed.
867 gboolean add_new_pipe_node = FALSE;
868
869 if(IS_NULL_PTR(module))
870 {
871 // module = NULL means a mask was changed from the mask manager and that's where this function is called.
872 // Find it now, even though it is not enabled and won't be.
873 module = dt_masks_get_mask_manager(dev);
874 if(!IS_NULL_PTR(module))
875 {
876 // Mask manager is an IOP that never processes pixel aka it's an ugly hack to record mask history
877 force_new_item = FALSE;
878 enable = FALSE;
879 }
880 else
881 {
882 return add_new_pipe_node;
883 }
884 }
885
886 // look for leaks on top of history
888
889 // Check if the current module to append to history is actually the same as the last one in history,
890 GList *last = g_list_last(dev->history);
891 gboolean new_is_old = FALSE;
892 if(last && last->data && !force_new_item)
893 {
894 dt_dev_history_item_t *last_item = (dt_dev_history_item_t *)last->data;
895 dt_iop_module_t *last_module = last_item->module;
896 new_is_old = dt_iop_check_modules_equal(module, last_module);
897 add_new_pipe_node = FALSE;
898 }
899 else
900 {
901 const dt_dev_history_item_t *previous_item =
902 dt_dev_history_get_last_item_by_module(dev->history, module, g_list_length(dev->history));
903 // check if NULL first or prevous_item->module will segfault
904 // We need to add a new pipeline node if:
905 add_new_pipe_node = (IS_NULL_PTR(previous_item)) // it's the first history entry for this module
906 || (previous_item->enabled != module->enabled); // the previous history entry is disabled
907 // if previous history entry is disabled and we don't have any other entry,
908 // it is possible the pipeline will not have this node.
909 }
910
912 const gboolean is_new_item = force_new_item || !new_is_old;
913 if(is_new_item)
914 {
915 // Create a new history entry
917 dev->history = g_list_append(dev->history, hist);
918 hist->num = g_list_index(dev->history, hist);
919 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] new history entry added for %s at position %i\n",
920 module->name(), hist->num);
921 }
922 else
923 {
924 // Reuse previous history entry. Copy-on-write: a slow reader (pipe resync snapshot, DB
925 // write job snapshot) may still be holding a reference to this exact item -- clone before
926 // mutating if so, never mutate a shared item in place.
927 hist = dt_dev_history_cow_touch(dev, (dt_dev_history_item_t *)last->data);
928
929 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] history entry reused for %s at position %i\n",
930 module->name(), hist->num);
931 }
932
933 // Always resync history with all module internals
934 if(enable) module->enabled = TRUE;
935
936 // Include masks if module supports blending and masks are in use, or if it's the mask manager.
937 gboolean include_masks = dt_iop_module_needs_mask_history(module);
938
939 // That predicate requires the matching mask_mode bit (e.g. DEVELOP_MASK_SHAPE) to be set,
940 // which is a *masking policy* decision — but whether the forms must be recorded is a *state
941 // integrity* decision: as soon as blend params reference an existing, non-empty group, the
942 // forms are part of this module's state. Skipping the snapshot here makes the history item
943 // hash blind to mask edits (stale pipe cache, issue #1060 family) and loses the mask on
944 // undo/redo (dt_dev_pop_history_items_ext() rebuilds dev->forms from the last item that
945 // carries one).
946 if(!include_masks && !IS_NULL_PTR(module->blend_params) && module->blend_params->mask_id != 0)
947 {
948 dt_masks_form_t *linked_group = dt_masks_get_from_id(dev, module->blend_params->mask_id);
949 include_masks = !IS_NULL_PTR(linked_group) && !IS_NULL_PTR(linked_group->points);
950 }
951
952 GList *forms_snapshot = NULL;
953 if(include_masks)
954 {
955 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] committing masks for module %s at history position %i\n", module->name(), hist->num);
956 // FIXME: this copies ALL drawn masks AND masks groups used by all modules to any module history using masks.
957 // Kudos to the idiots who thought it would be reasonable. Expect database bloating and perf penalty.
958 forms_snapshot = dt_masks_snapshot_current_forms(dev, TRUE);
959 }
960 // else forms_snapshot stays NULL
961
962 // Capture the previous parameter hash: an in-place reuse rewrites it below,
963 // which the supervisor must see as a rekey (delete old hash + add new hash).
964 const uint64_t old_param_hash = hist->hash;
965
966 // Fill history item and recompute hash (also applies params/blend_params to module to keep hash consistent).
967 dt_dev_history_item_update_from_params(dev, hist, module, module->enabled, module->params, module->params_size,
968 module->blend_params, forms_snapshot);
969
970 if(include_masks && hist->forms)
971 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] masks committed for module %s at history position %i\n", module->name(), hist->num);
972 else if(include_masks)
973 dt_print(DT_DEBUG_HISTORY, "[dt_dev_add_history_item_ext] masks NOT committed for module %s at history position %i\n", module->name(), hist->num);
974
975 // It is assumed that the last-added history entry is always on top
976 // so its cursor index is always equal to the number of elements,
977 // keeping in mind that history_end = 0 is the raw image, aka not a dev->history GList entry.
978 // So dev->history_end = index of last history entry + 1 = length of history
979 dt_dev_set_history_end_ext(dev, g_list_length(dev->history));
980
982 {
983 if(is_new_item)
984 dt_supervisor_history(DT_SV_CREATE, hist->hash, module->op, module->multi_priority,
985 module->multi_name, module->iop_order, hist->num, dev->image_storage.id,
986 hist->enabled, module, hist->params, hist->blend_params, hist->forms);
987 else if(old_param_hash != hist->hash)
988 {
989 // in-place overwrite changed the hash: delete old key, add new key, then
990 // refresh the new entry's current state (enabled/index may have changed).
991 dt_supervisor_rekey(old_param_hash, hist->hash);
992 dt_supervisor_history(DT_SV_UPDATE, hist->hash, module->op, module->multi_priority,
993 module->multi_name, module->iop_order, hist->num, dev->image_storage.id,
994 hist->enabled, module, hist->params, hist->blend_params, hist->forms);
995 }
996 else
997 dt_supervisor_history(DT_SV_UPDATE, hist->hash, module->op, module->multi_priority,
998 module->multi_name, module->iop_order, hist->num, dev->image_storage.id,
999 hist->enabled, module, hist->params, hist->blend_params, hist->forms);
1000 }
1001
1002 return add_new_pipe_node;
1003}
1004
1005uint64_t dt_dev_history_compute_hash(dt_develop_t *dev) REQUIRES_SHARED(dev->history_mutex)
1006{
1007 uint64_t hash = 5381;
1008 for(GList *hist = g_list_nth(dev->history, dt_dev_get_history_end_ext(dev) - 1);
1009 hist;
1010 hist = g_list_previous(hist))
1011 {
1012 dt_dev_history_item_t *item = (dt_dev_history_item_t *)hist->data;
1013 hash = dt_hash(hash, (const char *)&item->hash, sizeof(uint64_t));
1014 }
1015 dt_print(DT_DEBUG_HISTORY, "[dt_dev_history_get_hash] history hash: %" PRIu64 ", history end: %i, items %i\n", hash, dt_dev_get_history_end_ext(dev), g_list_length(dev->history));
1016 return hash;
1017}
1018
1019
1020/* The pending-commit throttle queue lives in dev_history_gui.c: it exists to coalesce
1021 * slider-drag input on the GTK main loop, and its headless timeout is zero -- the
1022 * engine entry it drains into is dt_dev_history_commit_item_now() below. */
1023
1025{
1026 gboolean add_new_pipe_node = FALSE;
1027
1030
1032 // Measures time actually spent blocked waiting for the lock (e.g. behind the
1033 // background dt_dev_write_history() job holding it as reader) -- not time spent
1034 // doing work while holding it. Threshold avoids logging the common sub-ms case.
1035 const double _wrlock_wait_start = dt_get_wtime();
1037 const double _wrlock_wait_ms = (dt_get_wtime() - _wrlock_wait_start) * 1000.0;
1038 if(_wrlock_wait_ms > 1.0)
1040 "[dt_dev_add_history_item_real] tid %lu (GUI thread) blocked %.2f ms acquiring history_mutex\n",
1041 (unsigned long)pthread_self(), _wrlock_wait_ms);
1042 add_new_pipe_node = dt_dev_add_history_item_ext(dev, module, enable, FALSE);
1045
1046 // Run the delayed post-commit actions if implemented
1047 if(!IS_NULL_PTR(module) && !IS_NULL_PTR(module->post_history_commit)) module->post_history_commit(module);
1048
1049 // Republish any dev->proxy state this module derives from its params (e.g. temperature's WB
1050 // coeffs) on the main thread, before the pipeline recompute below. This covers user edits and
1051 // resets; pipelines only read dev->proxy. (Bulk history loads go through pop_history_items_ext.)
1052 if(!IS_NULL_PTR(module) && !IS_NULL_PTR(module->commit_proxy)) module->commit_proxy(module);
1053
1054 // Figure out if the current history item includes masks/forms.
1055 // Read under the lock: the write lock was released above, so the background
1056 // dt_dev_write_history() job can be walking (and dropping references on) this very list.
1057 // Held only for the two list walks -- the image-cache update below stays outside it, so
1058 // the history_mutex -> image_cache order used by the write job is not introduced here.
1059 gboolean has_forms = FALSE;
1062 GList *last_history = g_list_nth(dev->history, dt_dev_get_history_end_ext(dev) - 1);
1063 if(last_history)
1064 {
1065 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)last_history->data;
1066 has_forms = (!IS_NULL_PTR(hist->forms));
1067 }
1068
1069 // We don't update history hash in dt_dev_add_history_item_ext
1070 // because it can be called within loops, so that can be expensive.
1071 history_hash = dt_dev_history_compute_hash(dev);
1073
1074 dt_dev_set_history_hash(dev, history_hash);
1075 if(dev->image_storage.id > 0)
1076 {
1077 dt_image_t *cache_img = dt_image_cache_get(dev->image_storage.id, 'w');
1078 if(cache_img)
1079 {
1080 cache_img->history_hash = dt_dev_get_history_hash(dev);
1082 }
1083 }
1084
1085 // Recompute pipeline last
1086 const gboolean has_raster = module && dt_iop_module_has_raster_mask(module);
1087 if(!IS_NULL_PTR(module) && !(has_forms || has_raster) && !add_new_pipe_node)
1088 {
1089 // If we have a module and it doesn't use drawn or raster masks,
1090 // we only need to resync the top-most history item with pipeline
1092 }
1093 else
1094 {
1095 // We either don't have a module, meaning we have the mask manager, or
1096 // we have a module and it uses masks (drawn or raster), or the current
1097 // history commit changes the pipeline topology. The latter happens for
1098 // example when enabling/disabling a module or when a module gets its first
1099 // history entry. In all these cases, updating only the top-most synced
1100 // history tail is insufficient because the set of active pipe nodes itself
1101 // may differ from the previous run.
1102 // Because masks can affect several modules anywhere, not necessarily sequentially,
1103 // we need a full resync of all pipeline with history.
1104 // Note that the blendop params (thus their hash) references the raster mask provider
1105 // in its consumer, and the consumer in its provider. So updating the whole pipe
1106 // resyncs the cumulative hashes too, and triggers a new recompute from the provider on update.
1108 }
1109
1111
1112 // The post-commit presentation work -- refreshing the viewport size when the module
1113 // changes ROI geometry, and syncing the enable toggle a param edit may have flipped --
1114 // is the GUI half's (dev_history_gui.c), reached through the handler.
1115 if(dev->gui_attached && !IS_NULL_PTR(module) && _commit_gui_handler)
1116 _commit_gui_handler(dev, module);
1117
1118 // Save history straight away. Regular GUI edits are the only place where
1119 // we accept an asynchronous write because `dev` is the long-lived darkroom
1120 // context and there is no immediate DB read on the same control path.
1122}
1123
1125{
1127 if(IS_NULL_PTR(item)) return NULL;
1128 dt_atomic_set_int(&item->refcount, 1);
1129 return item;
1130}
1131
1132void dt_dev_free_history_item(gpointer data)
1133{
1135 if(IS_NULL_PTR(item)) return; // nothing to free
1136
1137 // Despite the name (kept so every existing g_list_free_full/GDestroyNotify call site gets
1138 // refcount-safety for free), this is now dev_history's unref: dev->history and a form cloned
1139 // by dt_dev_history_cow_touch each hold their own reference. Only the last one out actually
1140 // frees the buffers.
1141 if(dt_atomic_sub_int(&item->refcount, 1) != 1) return;
1142
1143 dt_free(item->params);
1144 dt_free(item->blend_params);
1145 g_list_free_full(item->forms, (void (*)(void *))dt_masks_form_unref);
1146 item->forms = NULL;
1147 dt_free(item);
1148}
1149
1151{
1152 if(IS_NULL_PTR(item)) return NULL;
1153 dt_atomic_add_int(&item->refcount, 1);
1154 return item;
1155}
1156
1157// Same per-field duplication as dt_history_duplicate(), factored out for cow-touch's
1158// single-item clone: fresh struct, own params/blend_params buffers, forms shared by reference
1159// (already refcounted, cloned on write by dt_masks_cow_touch -- not on this snapshot).
1161{
1163 memcpy(new_item, old, sizeof(dt_dev_history_item_t));
1164 dt_atomic_set_int(&new_item->refcount, 1); // fresh object: reset, don't inherit old's refcount
1165
1166 new_item->params = NULL;
1167 if(old->params && old->params_size > 0)
1168 {
1169 new_item->params = malloc(old->params_size);
1170 memcpy(new_item->params, old->params, old->params_size);
1171 }
1172
1173 new_item->blend_params = NULL;
1174 if(old->blend_params && old->blendop_params_size > 0)
1175 {
1176 new_item->blend_params = malloc(old->blendop_params_size);
1177 memcpy(new_item->blend_params, old->blend_params, old->blendop_params_size);
1178 }
1179
1180 new_item->forms = NULL;
1181 if(old->forms)
1182 {
1183 new_item->forms = g_list_copy(old->forms);
1184 for(GList *form_node = new_item->forms; form_node; form_node = g_list_next(form_node))
1185 dt_masks_form_ref((dt_masks_form_t *)form_node->data);
1186 }
1187
1188 return new_item;
1189}
1190
1192{
1193 if(IS_NULL_PTR(dev) || IS_NULL_PTR(hist)) return hist;
1194 if(dt_atomic_get_int(&hist->refcount) <= 1) return hist;
1195
1196 // dt_pthread_rwlock_wrlock is same-thread-recursive (dtpthread.h), so this is safe whether
1197 // or not the caller already holds history_mutex as writer (dt_dev_add_history_item_real
1198 // does; other callers of dt_dev_add_history_item_ext may not).
1200
1201 GList *node = g_list_find(dev->history, hist);
1202 if(IS_NULL_PTR(node))
1203 {
1204 // Not (yet) a member of dev->history: nothing to splice.
1206 return hist;
1207 }
1208
1210 node->data = clone;
1211
1212 // Each pipe's own last-synced-item marker (used by the synch_top path to bound the resync
1213 // range) may name the item being cloned; re-point it at the clone so an in-place top-entry
1214 // rewrite keeps its bounded resync instead of degrading to a full one.
1215 //
1216 // The pipe HOLDS A REFERENCE on that marker and the resync writes it OUTSIDE history_mutex
1217 // (it runs against a snapshot), so this is a genuine cross-thread exchange, not a
1218 // compare-and-assign under a shared lock. The atomic exchange keeps the refcount honest in
1219 // every interleaving: this side takes a reference on the clone before publishing it, and
1220 // releases exactly the reference it displaced. If the worker publishes its own item between
1221 // the compare and the exchange, the slot ends up naming the clone, the worker's item is
1222 // released here, and the worst case is one full resync next frame -- never a leak or a
1223 // double release.
1224 dt_dev_pixelpipe_t *const pipes[] = { dev->pipe, dev->preview_pipe };
1225 for(size_t i = 0; i < G_N_ELEMENTS(pipes); i++)
1226 {
1227 if(IS_NULL_PTR(pipes[i])) continue;
1228 if(dt_atomic_get_ptr(&pipes[i]->last_history_item) != hist) continue;
1230 dt_dev_history_item_t *displaced
1231 = (dt_dev_history_item_t *)dt_atomic_exch_ptr(&pipes[i]->last_history_item, clone);
1232 dt_dev_free_history_item(displaced);
1233 }
1234
1236
1237 dt_dev_free_history_item(hist); // releases dev->history's old reference (unref semantics)
1238
1239 return clone;
1240}
1241
1243 REQUIRES_SHARED(dev->history_mutex)
1244{
1245 if(IS_NULL_PTR(snapshot)) return;
1246 snapshot->items = NULL;
1247 snapshot->history_end = 0;
1248 snapshot->history_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1249 if(IS_NULL_PTR(dev)) return;
1250
1251 // One reference per element, taken while the list cannot change under us. The copy is of
1252 // the list cells only: the items are shared, and the writer's copy-on-write gate is what
1253 // keeps them immutable for as long as this snapshot holds them.
1254 snapshot->items = g_list_copy(dev->history);
1255 for(GList *node = snapshot->items; node; node = g_list_next(node))
1257
1258 snapshot->history_end = dt_dev_get_history_end_ext(dev);
1259 snapshot->history_hash = dt_dev_get_history_hash(dev);
1260}
1261
1263{
1264 if(IS_NULL_PTR(snapshot)) return;
1265 g_list_free_full(snapshot->items, dt_dev_free_history_item);
1266 snapshot->items = NULL;
1267 snapshot->history_end = 0;
1268 snapshot->history_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1269}
1270
1271void dt_dev_history_free_history(dt_develop_t *dev) REQUIRES(dev->history_mutex)
1272{
1273 if(IS_NULL_PTR(dev->history)) return;
1274 // Canonical history is being cleared (image unload, compress rebuild, ...):
1275 // mark every item deleted in the supervisor before freeing the structs.
1277 for(GList *l = dev->history; l; l = g_list_next(l))
1278 {
1279 const dt_dev_history_item_t *h = (const dt_dev_history_item_t *)l->data;
1280 if(h && h->module)
1281 dt_supervisor_history(DT_SV_DELETE, h->hash, h->module->op, h->module->multi_priority,
1282 h->module->multi_name, h->module->iop_order, h->num,
1283 dev->image_storage.id, h->enabled, h->module, h->params, h->blend_params, h->forms);
1284 }
1285 g_list_free_full(g_steal_pointer(&dev->history), dt_dev_free_history_item);
1286 dev->history = NULL;
1287}
1288
1289gboolean dt_dev_reload_history_items(dt_develop_t *dev, const int32_t imgid)
1290{
1291 // Recreate the whole history from scratch.
1292 // Backend only: GUI updates and pixelpipe rebuilds need to be triggered by callers.
1295 const gboolean first_run = dt_dev_read_history_ext(dev, imgid);
1299 return first_run;
1300}
1301
1302
1317{
1318 for(GList *modules = g_list_first(dev->iop); modules; modules = g_list_next(modules))
1319 {
1320 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
1322
1323 // Reset the enabled state to the module default too. Neither dt_iop_load_default_params()
1324 // nor dt_iop_reload_defaults() touch it, so without this a module keeps whatever enabled
1325 // state the previous history point left it in. The history overlay below (_history_to_module)
1326 // only re-enables modules that have an entry within the current slice; a module whose only
1327 // history entry lies *after* history_end would otherwise stay stale-enabled while parked at
1328 // INT_MAX, becoming an enabled ghost node after gamma that the pixelpipe format pass disables
1329 // for "unexpected input buffer format" (issue #961). This restores the reset that was dropped
1330 // when the reload path was refactored (commit 892dad90a9).
1331 module->enabled = module->default_enabled;
1332
1333 if(module->multi_priority == 0)
1334 module->iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, module->op, module->multi_priority);
1335 else
1336 module->iop_order = INT_MAX;
1337
1338 // Last chance & desperate attempt at enabling/disabling critical modules
1339 // when history is garbled - This might prevent segfaults on invalid data
1340 if(module->force_enable)
1341 module->enabled = (module->force_enable(module, module->enabled) != 0);
1342
1343 // make sure that always-on modules are always on. duh.
1344 if(module->default_enabled == 1 && module->hide_enable_button == 1)
1345 module->enabled = TRUE;
1346
1347 dt_iop_compute_module_hash(module, dev->forms);
1348 }
1349}
1350
1351// Dump the content of an history entry into its associated module params, blendops, etc.
1360static inline void _history_to_module(const dt_dev_history_item_t *const hist, dt_iop_module_t *module,
1361 GList *current_forms)
1362{
1363 module->enabled = (hist->enabled != 0);
1364
1365 // Update IOP order stuff, that applies to all modules regardless of their internals
1366 module->iop_order = hist->iop_order;
1368
1369 // Copy instance name
1370 g_strlcpy(module->multi_name, hist->multi_name, sizeof(module->multi_name));
1371
1372 // Copy params from history entry to module internals
1373 memcpy(module->params, hist->params, module->params_size);
1375
1376 // Get the module hash. An item without its own forms snapshot must be hashed against the
1377 // snapshot accumulated up to its position in the replay (current_forms) — NOT against the
1378 // live dev->forms of the state being left: dt_iop_compute_blendop_hash()'s NULL fallback
1379 // reads the live list, and during replay that list is only replaced with the target
1380 // snapshot after this loop, so the restored hash would be tied to the wrong mask content.
1381 dt_iop_compute_module_hash(module, !IS_NULL_PTR(hist->forms) ? hist->forms : current_forms);
1382}
1383
1384
1385void dt_dev_pop_history_items_ext(dt_develop_t *dev) REQUIRES_SHARED(dev->history_mutex)
1386{
1387 dt_print(DT_DEBUG_HISTORY, "[dt_dev_pop_history_items_ext] loading history entries into modules...\n");
1388
1389 // Ensure `dev->image_storage` is up-to-date before modules reload their defaults.
1390 // This avoids using incomplete RAW metadata (WB coeffs, matrices) on newly-inited images.
1391 dt_dev_ensure_image_storage(dev, dev->image_storage.id);
1392
1393 // Reset every module to its default params first; the history overlay below then overwrites the
1394 // ones that have entries. Per-image defaults were already computed at history init / instance
1395 // creation, so we do NOT recompute them here (no reload_defaults: GUI-unsafe, and it re-derived
1396 // cross-module defaults against a pipeline-populated proxy). See _dt_dev_modules_reload_defaults.
1398
1399 const int history_end = dt_dev_get_history_end_ext(dev);
1400
1401 // go through history up to history_end and set modules params
1402 GList *history = g_list_first(dev->history);
1403 GList *forms = NULL;
1404 for(int i = 0; i < history_end && history; i++)
1405 {
1406 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
1407 dt_iop_module_t *module = hist->module;
1408
1409 // Update the reference to the form snapshot that doesn't belong
1410 // conceptually to history items. Advance it before applying the item so the
1411 // item's hash is computed against the masks state at its own position.
1412 if(hist->forms) forms = hist->forms;
1413
1414 if(module) _history_to_module(hist, module, forms);
1415
1416 history = g_list_next(history);
1417 }
1418
1419 // Nuke dev->forms and replace it with the last hist->forms in history.
1421
1422 // History (metadata + presets) is now fully applied to module params. Let modules publish any
1423 // dev->proxy state derived from their EFFECTIVE params (e.g. temperature's WB coeffs, consumed by
1424 // channelmixerrgb) on this main/history thread, BEFORE the pipeline resync below runs. dev->proxy
1425 // is a GUI/main-thread inter-module channel (pipelines must not touch it); this is the single
1426 // main-thread publish point for bulk history loads.
1427 for(GList *modules = g_list_first(dev->iop); modules; modules = g_list_next(modules))
1428 {
1429 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
1430 if(module->commit_proxy) module->commit_proxy(module);
1431 }
1432
1433 dt_ioppr_resync_pipeline(dev, 0, "dt_dev_pop_history_items_ext end", TRUE);
1434
1435 // Reloading defaults might have changed the global history hash
1437
1438 // Update darkroom sizes in case clipping & distortion changed.
1439 // This is now handled by the wrapper after releasing the history lock.
1440}
1441
1443{
1448 // Update darkroom sizes after releasing the history lock to avoid deadlocks.
1451}
1452
1453/* dt_dev_history_gui_update() lives in dev_history_gui.c: it attaches missing GTK
1454 * expanders and refreshes every module widget after a backend history reload. */
1455
1457{
1458 if(!dev->gui_attached) return;
1459
1460 if(rebuild)
1462 else
1464}
1465
1467{
1468 dt_develop_t *const dev = dt_dev_get_global();
1469 return !IS_NULL_PTR(dev) && g_list_find(imgs, GINT_TO_POINTER(dev->image_storage.id));
1470}
1471
1473{
1474 if(IS_NULL_PTR(dev)) return;
1475
1478 // Re-derive dev->roi.processed_{width,height} (and the natural scale) from the new
1479 // history, through the geometry service's size fold. Without this, a history change that alters the
1480 // final image geometry (e.g. removing a crop/ashift step) still renders at the old,
1481 // stale ROI -- same fix as _history_apply_history_end() in libs/history.c.
1485}
1486
1492static void _cleanup_history(const int32_t imgid)
1493{
1495}
1496
1497guint dt_dev_mask_history_overload(GList *dev_history, guint threshold)
1498{
1499 // Count all the mask forms used x history entries, up to a certain threshold.
1500 // Stop counting when the threshold is reached, for performance.
1501 guint states = 0;
1502 for(GList *history = g_list_first(dev_history); history; history = g_list_next(history))
1503 {
1504 dt_dev_history_item_t *hist_item = (dt_dev_history_item_t *)(history->data);
1505 states += g_list_length(hist_item->forms);
1506 if(states > threshold) break;
1507 }
1508 return states;
1509}
1510
1511void dt_dev_history_notify_change(dt_develop_t *dev, const int32_t imgid)
1512{
1513 if(IS_NULL_PTR(dev) || imgid <= 0) return;
1514
1515 if(dev->gui_attached)
1516 {
1517 // Every caller reaches this from an unlocked context (the write job unlocks immediately
1518 // before calling; the GUI and IOP call sites never hold it), so the walk of dev->history
1519 // takes the read lock here rather than demanding it from callers. Released before
1520 // dt_image_history_changed() below, which does mipmap and thumbnail work far too
1521 // expensive to hold history_mutex across.
1523 const guint states = dt_dev_mask_history_overload(dev->history, 250);
1525 if(states > 250)
1526 dt_history_toast(_("Image #%i history is storing %d mask states. n"
1527 "Consider compressing history and removing unused masks to keep reads/writes manageable."),
1528 imgid, states);
1529 }
1530
1531 // Reload metadata, drop the stale mipmap and refresh the lighttable thumbnail.
1532 // refresh_filmstrip = FALSE: in darkroom the filmstrip is best-effort, spawning another export
1533 // thread would slow down the current one and we want resources allocated to the realtime main
1534 // preview, not diverted to cosmetic GUI refreshes.
1536}
1537
1538
1539// helper used to synch a single history item with db
1540int dt_dev_write_history_item(const int32_t imgid, dt_dev_history_item_t *h, int32_t num)
1541{
1542 if(IS_NULL_PTR(h)) return 1;
1543
1544 dt_print(DT_DEBUG_HISTORY, "[dt_dev_write_history_item] writing history for module %s (%s) (enabled %i) at pipe position %i for image %i\n",
1545 h->op_name, h->multi_name, h->enabled, h->iop_order, imgid);
1546
1547 const char *operation = h->module ? h->module->op : h->op_name;
1548 const int params_size = h->module ? h->module->params_size : h->params_size;
1549 const int module_version = h->module ? h->module->version() : h->module_version;
1550 const int blendop_params_size
1551 = h->blend_params ? (h->blendop_params_size > 0 ? h->blendop_params_size : (int)sizeof(dt_develop_blend_params_t)) : 0;
1552 const int blendop_version
1554
1555 dt_history_repository_write_item(imgid, num, operation, h->params, params_size, module_version, h->enabled != 0,
1556 h->blend_params, blendop_params_size, blendop_version, h->multi_priority, h->multi_name);
1557
1558 // write masks (if any)
1559 if(h->forms)
1560 dt_print(DT_DEBUG_HISTORY, "[dt_dev_write_history_item] drawn mask found for module %s (%s) for image %i\n", h->op_name, h->multi_name, imgid);
1561
1562 for(GList *forms = g_list_first(h->forms); forms; forms = g_list_next(forms))
1563 {
1564 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1565 if (form)
1566 dt_masks_write_masks_history_item(imgid, num, form);
1567 }
1568
1569 return 0;
1570}
1571
1573{
1574 // No-op: SQL statement caching/cleanup for history lives in common/history.c (dt_history_repository_cleanup()).
1575}
1576
1577
1578
1579/* Everything the DB rewrite needs, frozen at one moment under history_mutex, so the rewrite
1580 * itself can run with the lock RELEASED. A dt_dev_history_snapshot_t carries the items,
1581 * history_end and the hash; the iop-order list is the one other thing the rewrite reads from
1582 * dev, and it is deep-copied because it is a plain GList of entries with no refcount. */
1588
1590 REQUIRES_SHARED(dev->history_mutex)
1591{
1592 // Re-derive the hash from the list and publish it, as the rewrite always has: the atomic
1593 // is normally maintained by dt_dev_set_history_end_ext(), this is the belt to its braces.
1595 dt_dev_history_snapshot_take(dev, &state->snapshot);
1596 state->iop_order_list = dt_ioppr_iop_order_copy_deep(dev->iop_order_list);
1597}
1598
1600{
1602 g_list_free_full(state->iop_order_list, dt_free_gpointer);
1603 state->iop_order_list = NULL;
1604}
1605
1606/* The rewrite proper. Needs NO lock: every item is kept alive by the snapshot's references and
1607 * kept unmodified by the writer's copy-on-write gate, and the iop-order list is our own copy.
1608 * dt_dev_write_history_item() reads h->module->op/params_size/version() through the item's
1609 * module pointer, which is an iop instance that outlives every history item naming it. */
1610static void _write_history_from_state(const _history_write_state_t *state, const int32_t imgid)
1611{
1612 dt_image_t *cache_img = dt_image_cache_get(imgid, 'w');
1613 if(IS_NULL_PTR(cache_img)) return;
1614
1615 dt_print(DT_DEBUG_HISTORY, "[dt_dev_write_history_ext] writing history for image %i...\n", imgid);
1616
1617 _cleanup_history(imgid);
1618
1619 // write history entries
1620 int i = 0;
1621 for(GList *history = g_list_first(state->snapshot.items); history; history = g_list_next(history))
1622 {
1623 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
1624 dt_dev_write_history_item(imgid, hist, i);
1625 i++;
1626 }
1627
1628 dt_history_repository_set_end(imgid, state->snapshot.history_end);
1629
1630 // write the iop-order-list this history was captured with
1631 dt_ioppr_write_iop_order_list(state->iop_order_list, imgid);
1632
1633 cache_img->history_hash = state->snapshot.history_hash;
1634
1636}
1637
1638/* Callers of this one already hold history_mutex (most as writers, mid-commit) and expect the
1639 * write to have happened when it returns, so the state is taken and written under their lock.
1640 * The two paths that took the lock ONLY to write -- the async job and the sync fallback in
1641 * dt_dev_write_history() -- do not come through here; they hold it just long enough to take the
1642 * state. */
1643void dt_dev_write_history_ext(dt_develop_t *dev, const int32_t imgid) REQUIRES_SHARED(dev->history_mutex)
1644{
1649}
1650
1651// Schedule history write as a background job to avoid blocking the GUI.
1652// If scheduling fails, fall back to synchronous write to preserve behaviour.
1654{
1656 if(IS_NULL_PTR(d)) return 1;
1657 const int32_t imgid = d->image_storage.id;
1658
1659 // This job used to hold history_mutex as reader for the whole rewrite -- every row of
1660 // history and masks_history deleted and re-inserted, tens to hundreds of ms -- and it runs
1661 // after every commit, so the GUI thread's NEXT commit (which needs the writer side) queued
1662 // behind it: the wait dt_dev_history_commit_item_now() logs as "blocked acquiring
1663 // history_mutex". Same disease as the pipe resync, same cure: freeze the state under the
1664 // lock, release, write from the frozen state.
1666 const double _lock_start = dt_get_wtime();
1667 dt_pthread_rwlock_rdlock(&d->history_mutex);
1669 // Clear the coalescing flag HERE, under the lock, at the moment the state is frozen -- not
1670 // after the write. dt_dev_write_history() skips queueing while this flag is set, on the
1671 // promise that the pending write will still pick the commit up. That promise now holds only
1672 // for commits landing BEFORE this point: anything committed after the unlock is not in our
1673 // snapshot, so it must find the flag clear and queue its own write. Clearing after the
1674 // rewrite would silently drop every commit made while the rewrite ran.
1675 dt_atomic_set_int(&d->history_write_pending, 0);
1676 dt_pthread_rwlock_unlock(&d->history_mutex);
1677 const double _lock_ms = (dt_get_wtime() - _lock_start) * 1000.0;
1678 if(_lock_ms > 1.0)
1680 "[_dt_dev_write_history_job_run] tid %lu held history_mutex for %.2f ms to snapshot image %i\n",
1681 (unsigned long)pthread_self(), _lock_ms, imgid);
1682
1683 const double _write_start = dt_get_wtime();
1685 const double _write_ms = (dt_get_wtime() - _write_start) * 1000.0;
1686 if(_write_ms > 1.0)
1688 "[_dt_dev_write_history_job_run] tid %lu full history+masks rewrite for image %i took %.2f ms, lock-free\n",
1689 (unsigned long)pthread_self(), imgid, _write_ms);
1691
1693 return 0;
1694}
1695
1696// Write TO XMP, so from the dev perspective, it's a read
1697void dt_dev_write_history(dt_develop_t *dev, gboolean async)
1698{
1699 if(!async)
1700 {
1701 // Same shape as the job: the lock is for freezing the state, not for writing it.
1709 return;
1710 }
1711
1712 // Coalesce: a write already QUEUED for this dev has not frozen its state yet, so it will
1713 // pick up this commit when it runs. A write already RUNNING froze its state when it took
1714 // the lock and cleared this flag at that same moment (see _dt_dev_write_history_job_run),
1715 // so a commit landing after that finds the flag clear and queues its own write here. Either
1716 // way nothing is lost; queueing while the flag is set would only repeat a rewrite of state
1717 // that is already on its way to disk.
1718 if(dt_atomic_exch_int(&dev->history_write_pending, 1) != 0) return;
1719
1721 dev->image_storage.id);
1722 dt_control_job_set_params(job, dev, NULL);
1723
1725 {
1726 // scheduling failed: dispose job, clear the flag we just set, and run synchronously
1730 }
1731}
1732
1740static gboolean _dev_auto_apply_presets(dt_develop_t *dev, int32_t imgid)
1741{
1742 dt_image_t *image = &dev->image_storage;
1743 const gboolean has_matrix = dt_image_is_matrix_correction_supported(image);
1744 const char *workflow_preset = has_matrix ? _("scene-referred default") : "\t\n";
1745
1746 int iformat = 0;
1747 if(dt_image_needs_rawprepare(image))
1748 iformat |= FOR_RAW;
1749 else
1750 iformat |= FOR_LDR;
1751
1752 if(dt_image_is_hdr(image))
1753 iformat |= FOR_HDR;
1754
1755 int excluded = 0;
1756 if(dt_image_monochrome_flags(image))
1757 excluded |= FOR_NOT_MONO;
1758 else
1759 excluded |= FOR_NOT_COLOR;
1760
1761 int legacy_params = 0;
1762 dt_dev_history_db_ctx_t ctx = { .dev = dev, .imgid = imgid, .legacy_params = &legacy_params, .presets = TRUE };
1763 dt_history_repository_foreach_auto_preset_row(imgid, image, workflow_preset, iformat, excluded, _dev_history_db_row_cb, &ctx);
1764
1765 // now we want to auto-apply the iop-order list if one corresponds and none are
1766 // still applied. Note that we can already have an iop-order list set when
1767 // copying an history or applying a style to a not yet developed image.
1768
1769 if(!dt_ioppr_has_iop_order_list(imgid))
1770 {
1771 void *params = NULL;
1772 int32_t params_len = 0;
1773 if(dt_history_repository_get_autoapply_ioporder_params(imgid, image, iformat, excluded, &params, &params_len))
1774 {
1775 GList *iop_list = dt_ioppr_deserialize_iop_order_list(params, params_len);
1776 dt_ioppr_write_iop_order_list(iop_list, imgid);
1777 g_list_free_full(iop_list, dt_free_gpointer);
1778 iop_list = NULL;
1780 dt_free(params);
1781 }
1782 else
1783 {
1784 // No auto-applied order exists for this image. Persist the built-in order
1785 // matching the input class; the non-RAW order is named ANSEL_JPG but also
1786 // covers rendered formats such as TIFF/PNG/HDR.
1787 const dt_iop_order_t default_order = dt_image_needs_rawprepare(image)
1790 GList *iop_list = dt_ioppr_get_iop_order_list_version(default_order);
1791 dt_ioppr_write_iop_order_list(iop_list, imgid);
1792 g_list_free_full(iop_list, dt_free_gpointer);
1793 iop_list = NULL;
1795 }
1796 }
1797
1798 // Notify our private image copy that auto-presets got applied
1800
1801 return TRUE;
1802}
1803
1814static void _insert_default_modules(dt_develop_t *dev, dt_iop_module_t *module, gboolean is_inited) REQUIRES(dev->history_mutex)
1815{
1816 // Module already in history: don't prepend extra entries.
1817 //
1818 // Two independent sources can already cover this module and both must be checked:
1819 // - dev->history in memory: dt_dev_replace_history_on_image() (image duplication, history
1820 // "replace" paste) loads dev->history from a *source* image, then points
1821 // dev->image_storage at a freshly created *destination* image whose DB history is still
1822 // empty (nothing written yet). The DB check alone always reports "missing" there, so every
1823 // default_enabled module (temperature, colorin, colorout, demosaic...) got a fresh
1824 // default-params entry silently appended after the correctly copied one, clobbering the
1825 // user's settings in the copy -- duplicating an image did not reproduce its development
1826 // identically.
1827 // - the DB row for dev->image_storage.id: dt_dev_read_history_ext() (normal darkroom/export
1828 // history load) calls this function on a freshly emptied dev->history, *before* the real
1829 // history rows are read from DB into memory. The in-memory check alone would always report
1830 // "missing" there too, causing every already-edited image to get spurious duplicate default
1831 // entries prepended ahead of its real history on every single load.
1832 if(!IS_NULL_PTR(dt_dev_history_get_first_item_by_module(dev->history, module))
1833 || dt_history_repository_module_exists(dev->image_storage.id, module->op))
1834 return;
1835
1836 // Module has no user params: no history: don't prepend either
1837 if((module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
1838 && (module->default_enabled || (module->force_enable && module->force_enable(module, FALSE))))
1839 {
1840 module->enabled = TRUE;
1841 return;
1842 }
1843
1844 dt_image_t *image = &dev->image_storage;
1845
1846 // Prior to Darktable 3.0, modules enabled by default which still had
1847 // default params (no user change) were not inserted into history/DB.
1848 // We need to insert them here with default params.
1849 // But defaults have changed since then for some modules, so we need to ensure
1850 // we insert them with OLD defaults.
1851 if(module->default_enabled || (module->force_enable && module->force_enable(module, FALSE)))
1852 {
1853 module->enabled = TRUE;
1854 const gboolean has_matrix = dt_image_is_matrix_correction_supported(image);
1855 const gboolean is_raw = dt_image_is_raw(image);
1856
1857 if(!strcmp(module->op, "temperature")
1858 && (image->change_timestamp == -1) // change_timestamp is not defined for old pics
1859 && is_raw && is_inited && has_matrix)
1860 {
1861 dt_print(DT_DEBUG_HISTORY, "[history] Image history seems older than Darktable 3.0, we will insert white balance.\n");
1862
1863 // Temp revert to legacy defaults
1864 dt_conf_set_string("plugins/darkroom/chromatic-adaptation", "legacy");
1865 dt_iop_reload_defaults(module);
1866
1867 dt_dev_add_history_item_ext(dev, module, TRUE, TRUE);
1868
1869 // Go back to current defaults
1870 dt_conf_set_string("plugins/darkroom/chromatic-adaptation", "modern");
1871 dt_iop_reload_defaults(module);
1872 }
1873 else
1874 {
1875 dt_dev_add_history_item_ext(dev, module, TRUE, TRUE);
1876 }
1877 }
1878 else if(module->workflow_enabled && !is_inited)
1879 {
1880 module->enabled = TRUE;
1881 dt_dev_add_history_item_ext(dev, module, TRUE, TRUE);
1882 }
1883
1884 if(module->enabled)
1885 dt_print(DT_DEBUG_HISTORY, "[history] %s was inserted into history by default (enabled %i)\n", module->op, module->enabled);
1886}
1887
1888// Returns TRUE if this is a freshly-inited history on which we just applied auto presets and defaults,
1889// FALSE if we had an earlier history
1890gboolean dt_dev_init_default_history(dt_develop_t *dev, const int32_t imgid, gboolean apply_auto_presets)
1891 REQUIRES(dev->history_mutex)
1892{
1893 const gboolean is_inited = (dev->image_storage.flags & DT_IMAGE_AUTO_PRESETS_APPLIED);
1894
1895 // Make sure this is set
1896 dt_conf_set_string("plugins/darkroom/chromatic-adaptation", "modern");
1897
1898 // make sure all modules default params are loaded to init history
1899 for(GList *iop = g_list_first(dev->iop); iop; iop = g_list_next(iop))
1900 {
1901 dt_iop_module_t *module = (dt_iop_module_t *)(iop->data);
1902 dt_iop_reload_defaults(module);
1903 _insert_default_modules(dev, module, is_inited);
1904 }
1905
1906 // On virgin history image, apply auto stuff (ours and user's)
1907 if(apply_auto_presets && !is_inited) _dev_auto_apply_presets(dev, imgid);
1908 dt_print(DT_DEBUG_HISTORY, "[history] temporary history initialised with default params and presets\n");
1909
1910 return !is_inited;
1911}
1912
1913int dt_dev_replace_history_on_image(dt_develop_t *dev_src, const int32_t dest_imgid,
1914 const gboolean reload_defaults, const char *msg)
1915{
1916 if(dest_imgid <= 0) return 1;
1917
1918 dt_dev_ensure_image_storage(dev_src, dest_imgid);
1919
1920 // Same shape as dt_dev_reload_history_items(): the whole rebuild runs under the write lock,
1921 // released before dt_dev_write_history(), which takes the read lock itself.
1923
1924 if(reload_defaults)
1925 {
1926 dt_dev_init_default_history(dev_src, dest_imgid, FALSE);
1927 dt_ioppr_resync_pipeline(dev_src, dest_imgid, msg, FALSE);
1928 }
1929
1932
1933 dt_dev_write_history(dev_src, FALSE);
1934
1935 return 0;
1936}
1937
1938// populate hist->module
1948{
1949 dt_iop_module_t *match = NULL;
1950
1951 for(GList *modules = g_list_first(dev->iop); modules; modules = g_list_next(modules))
1952 {
1953 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1954 if(!strcmp(module->op, hist->op_name))
1955 {
1956 if(module->multi_priority == hist->multi_priority)
1957 {
1958 // Found exact match at required priority: we are done
1959 hist->module = module;
1960 break;
1961 }
1962 else if(hist->multi_priority > 0)
1963 {
1964 // Found the right kind of module but the wrong instance.
1965 // Current history entry is targeting an instance that may exist later in the pipe, so keep looping/looking.
1966 match = module;
1967 }
1968 }
1969 }
1970
1971 if(!hist->module && match)
1972 {
1973 // We found a module having the required name but not the required instance number:
1974 // add a new instance of this module by using its ->so property
1975 dt_iop_module_t *new_module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
1976 if(!dt_iop_load_module(new_module, match->so, dev))
1977 {
1978 dev->iop = g_list_append(dev->iop, new_module);
1979 // Just init, it will get rewritten later by resync IOP order methods:
1980 new_module->instance = match->instance;
1981 hist->module = new_module;
1982 }
1983 }
1984 // else we found an already-existing instance and it's in hist->module already
1985
1986 if(hist->module) hist->module->enabled = (hist->enabled != 0);
1987}
1988
1989
2001static void _sync_blendop_params(dt_dev_history_item_t *hist, const void *blendop_params, const int bl_length,
2002 const int blendop_version, int *legacy_params)
2003{
2004 const gboolean is_valid_blendop_version = (blendop_version == dt_develop_blend_version());
2005 const gboolean is_valid_blendop_size = (bl_length == sizeof(dt_develop_blend_params_t));
2006
2007 hist->blend_params = malloc(sizeof(dt_develop_blend_params_t));
2010
2011 if(!IS_NULL_PTR(blendop_params) && is_valid_blendop_version && is_valid_blendop_size)
2012 {
2013 memcpy(hist->blend_params, blendop_params, sizeof(dt_develop_blend_params_t));
2014 }
2015 else if(blendop_params
2016 && dt_develop_blend_legacy_params(hist->module, blendop_params, blendop_version, hist->blend_params,
2017 dt_develop_blend_version(), bl_length)
2018 == 0)
2019 {
2021 }
2022 else
2023 {
2024 memcpy(hist->blend_params, hist->module->default_blendop_params, sizeof(dt_develop_blend_params_t));
2025 }
2026}
2027
2041static int _sync_params(dt_dev_history_item_t *hist, const void *module_params, const int param_length,
2042 const int modversion, int *legacy_params, const char *preset_name)
2043{
2044 hist->params_size = hist->module->params_size;
2045 hist->module_version = hist->module->version();
2046 const gboolean is_valid_module_version = (modversion == hist->module->version());
2047 const gboolean is_valid_params_size = (param_length == hist->module->params_size);
2048
2049 hist->params = malloc(hist->module->params_size);
2050 if(is_valid_module_version && is_valid_params_size)
2051 {
2052 memcpy(hist->params, module_params, hist->module->params_size);
2053 }
2054 else
2055 {
2056 if(!hist->module->legacy_params
2057 || hist->module->legacy_params(hist->module, module_params, labs(modversion),
2058 hist->params, labs(hist->module->version())))
2059 {
2060 gchar *preset = (preset_name) ? g_strdup_printf(_("from preset %s"), preset_name)
2061 : g_strdup("");
2062
2063 fprintf(stderr, "[dev_read_history] module `%s' %s version mismatch: history is %d, dt %d.\n", hist->module->op,
2064 preset, modversion, hist->module->version());
2065
2066 dt_history_message(_("module `%s' %s version mismatch: %d != %d"), hist->module->op,
2067 preset, hist->module->version(), modversion);
2068
2069 dt_free(preset);
2070 return 1;
2071 }
2072 else
2073 {
2074 // NOTE: spots version was bumped from 1 to 2 in 2013.
2075 // This handles edits made prior to Darktable 1.4.
2076 // Then spots was deprecated in 2021 in favour of retouch.
2077 // How many edits out there still need the legacy conversion in 2025 ?
2078 if(!strcmp(hist->module->op, "spots") && modversion == 1)
2079 {
2080 // quick and dirty hack to handle spot removal legacy_params
2081 memcpy(hist->blend_params, hist->module->blend_params, sizeof(dt_develop_blend_params_t));
2082 }
2084 }
2085
2086 /*
2087 * Fix for flip iop: previously it was not always needed, but it might be
2088 * in history stack as "orientation (off)", but now we always want it
2089 * by default, so if it is disabled, enable it, and replace params with
2090 * default_params. if user want to, he can disable it.
2091 * NOTE: Flip version was bumped from 1 to 2 in 2014.
2092 * This handles edits made prior to Darktable 1.6.
2093 * How many edits out there still need the legacy conversion in 2025 ?
2094 */
2095 if(!strcmp(hist->module->op, "flip") && hist->enabled == 0 && labs(modversion) == 1)
2096 {
2097 memcpy(hist->params, hist->module->default_params, hist->module->params_size);
2098 hist->enabled = TRUE;
2099 }
2100 }
2101
2102 return 0;
2103}
2104
2105// WARNING: this does not set hist->forms
2129static void _process_history_db_entry(dt_develop_t *dev, const int32_t imgid, const int id, const int num,
2130 const int modversion, const char *operation, const void *module_params,
2131 const int param_length, const gboolean enabled, const void *blendop_params,
2132 const int bl_length, const int blendop_version, const int multi_priority,
2133 const char *multi_name, const char *preset_name, int *legacy_params,
2134 const gboolean presets) REQUIRES(dev->history_mutex)
2135{
2136 // Sanity checks
2137 const gboolean is_valid_id = (id == imgid);
2138 const gboolean has_operation = (!IS_NULL_PTR(operation));
2139
2140 if(!(has_operation && is_valid_id))
2141 {
2142 fprintf(stderr, "[dev_read_history] database history for image `%s' seems to be corrupted!\n",
2143 dev->image_storage.filename);
2144 return;
2145 }
2146
2152 if(!dt_iop_get_module_from_list(dev->iop, operation)) return;
2153
2154 int iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, operation, multi_priority);
2155
2156 // Init a bare minimal history entry
2158 hist->module = NULL;
2159 hist->num = num;
2160 hist->iop_order = iop_order;
2161 hist->multi_priority = multi_priority;
2162 hist->enabled = (enabled != 0); // cast to gboolean
2163 g_strlcpy(hist->op_name, operation, sizeof(hist->op_name));
2164 g_strlcpy(hist->multi_name, multi_name ? multi_name : "", sizeof(hist->multi_name));
2165
2166 // Find a .so file that matches our history entry, aka a module to run the params stored in DB
2167 _find_so_for_history_entry(dev, hist);
2168
2169 if(!hist->module)
2170 {
2171 // Keep the serialized history entry even though no live module can consume it.
2172 hist->params_size = MAX(param_length, 0);
2173 hist->module_version = modversion;
2174 hist->blendop_params_size = MAX(bl_length, 0);
2175 hist->blendop_version = blendop_version;
2176 if(!IS_NULL_PTR(module_params) && param_length > 0)
2177 {
2178 hist->params = malloc(param_length);
2179 memcpy(hist->params, module_params, param_length);
2180 }
2181 if(!IS_NULL_PTR(blendop_params) && bl_length > 0)
2182 {
2183 hist->blend_params = malloc(bl_length);
2184 memcpy(hist->blend_params, blendop_params, bl_length);
2185 }
2186
2187 fprintf(
2188 stderr,
2189 "[dev_read_history] the module `%s' requested by image `%s' is not installed on this computer!\n",
2190 operation, dev->image_storage.filename);
2191 dev->history = g_list_append(dev->history, hist);
2192 return;
2193 }
2194
2195 // Update IOP order stuff, that applies to all modules regardless of their internals
2196 // Needed now to de-entangle multi-instances
2197 hist->module->iop_order = hist->iop_order;
2198 dt_iop_update_multi_priority(hist->module, hist->multi_priority);
2199
2200 // When the stored module_order uses a non-CUSTOM built-in (one entry per module),
2201 // extra instances (multi_priority > 0) are absent from iop_order_list and get
2202 // iop_order = INT_MAX above. Insert a placeholder so the module gets a valid
2203 // sequential position instead of drifting through the pipeline at INT_MAX.
2204 if(iop_order == INT_MAX && hist->multi_priority > 0)
2205 {
2206 dt_iop_order_entry_t *new_entry = (dt_iop_order_entry_t *)calloc(1, sizeof(dt_iop_order_entry_t));
2207 g_strlcpy(new_entry->operation, operation, sizeof(new_entry->operation));
2208 g_strlcpy(new_entry->name, hist->multi_name, sizeof(new_entry->name));
2209 new_entry->instance = hist->multi_priority;
2210 new_entry->o.iop_order = 0;
2211
2212 // Insert after the last existing entry for this operation so extra instances
2213 // sit after the base, matching the order from dt_dev_module_duplicate.
2214 GList *place = NULL;
2215 for(GList *l = dev->iop_order_list; l; l = g_list_next(l))
2216 {
2217 const dt_iop_order_entry_t *e = (dt_iop_order_entry_t *)l->data;
2218 if(!strcmp(e->operation, operation)) place = l;
2219 }
2220 dev->iop_order_list = g_list_insert_before(dev->iop_order_list,
2221 place ? g_list_next(place) : NULL, new_entry);
2222
2223 iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, operation, hist->multi_priority);
2224 hist->iop_order = iop_order;
2225 hist->module->iop_order = iop_order;
2226 }
2227
2228 // module has no user params and won't bother us in GUI - exit early, we are done
2229 if(hist->module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
2230 {
2231 // Since it's the last we hear from this module as far as history is concerned,
2232 // compute its hash here.
2233 dt_iop_compute_module_hash(hist->module, NULL);
2234
2235 // Done. We don't add to history
2236 dt_free(hist);
2237 return;
2238 }
2239
2240 // Copy module params if valid version, else try to convert legacy params
2241 if(_sync_params(hist, module_params, param_length, modversion, legacy_params, preset_name))
2242 {
2243 dt_free(hist);
2244 return;
2245 }
2246
2247 // So far, on error we haven't allocated any buffer, so we just freed the hist structure
2248
2249 // Last chance & desperate attempt at enabling/disabling critical modules
2250 // when history is garbled - This might prevent segfaults on invalid data
2251 if(hist->module->force_enable)
2252 hist->enabled = (hist->module->force_enable(hist->module, hist->enabled) != 0);
2253
2254 // make sure that always-on modules are always on. duh.
2255 if(hist->module->default_enabled == TRUE && hist->module->hide_enable_button == TRUE)
2256 hist->enabled = TRUE;
2257
2258 // Copy blending params if valid, else try to convert legacy params
2259 _sync_blendop_params(hist, blendop_params, bl_length, blendop_version, legacy_params);
2260
2261 if(presets && !IS_NULL_PTR(hist->blend_params) && !IS_NULL_PTR(hist->module)
2262 && !IS_NULL_PTR(hist->module->default_blendop_params))
2263 {
2264 dt_develop_blend_params_t preset_blend = *hist->blend_params;
2265 dt_develop_blend_params_t default_blend = *hist->module->default_blendop_params;
2266 preset_blend.mask_mode &= ~DEVELOP_MASK_ENABLED;
2267 default_blend.mask_mode &= ~DEVELOP_MASK_ENABLED;
2268 if(memcmp(&preset_blend, &default_blend, sizeof(dt_develop_blend_params_t)) == 0)
2269 hist->blend_params->mask_mode &= ~DEVELOP_MASK_ENABLED;
2270 }
2271
2272 dev->history = g_list_append(dev->history, hist);
2273
2274 dt_print(DT_DEBUG_HISTORY, "[history entry] read %s at pipe position %i (enabled %i) from %s %s\n", hist->op_name,
2275 hist->iop_order, hist->enabled, (presets) ? "preset" : "database", (presets) ? preset_name : "");
2276}
2277
2278
2279gboolean dt_dev_read_history_ext(dt_develop_t *dev, const int32_t imgid) REQUIRES(dev->history_mutex)
2280{
2281 if(imgid == UNKNOWN_IMAGE) return FALSE;
2282
2283 // This should be inited already when creating a pipeline or entering darkroom
2284 // but some pathes don't handle it, so make sure we have modules loaded.
2285 if(IS_NULL_PTR(dev->iop))
2286 dev->iop = dt_dev_load_modules(dev);
2287
2288 // Ensure raw metadata (WB coeffs, matrices, etc.) is available for modules that
2289 // query it while (re)loading defaults (e.g. temperature/colorin).
2290 // This is redundant with `_dt_dev_load_raw()` called from `dt_dev_load_image()`,
2291 // but some call sites reload history without guaranteeing a prior FULL open.
2292 if(dt_dev_ensure_image_storage(dev, imgid))
2293 return FALSE;
2294
2295 // Start fresh
2297 int legacy_params = 0;
2299
2300 // Find the new history end from DB now, if defined.
2301 // Note: dt_dev_set_history_end_ext sanitizes the value with the actual history size.
2302 // It needs to run after dev->history is fully populated.
2303 int32_t history_end = dt_history_repository_get_end(imgid);
2304
2305 // Find out if we already have an history, and how many items
2306 const int32_t db_items = dt_history_repository_get_next_num(imgid);
2307
2308 // Load all the modules that may be required by the image format,
2309 // plus auto-presets if it's a new edit
2310 gboolean first_run = dt_dev_init_default_history(dev, imgid, (db_items == 0));
2311
2312 // Protect history DB reads with a cache read lock.
2313 // Release it before applying history to modules to avoid deadlocks.
2314 dt_image_t *read_lock_img = dt_image_cache_get(imgid, 'r');
2315 if(IS_NULL_PTR(read_lock_img)) return FALSE;
2316
2317 // Load DB history into dev->history
2318 dt_dev_history_db_ctx_t ctx = { .dev = dev, .imgid = imgid, .legacy_params = &legacy_params, .presets = FALSE };
2320 const int32_t history_length = g_list_length(dev->history);
2321
2322 if(history_length > db_items)
2323 {
2324 // We have now more history entries than when reading DB,
2325 // meaning the sanitization step added required default modules.
2326 // We need to shift history end by the same amount to still point to the same entry.
2327 // This is valid whether history_end was 0 or not
2328 history_end += history_length - db_items;
2329 }
2330
2331 // Set a provisional history_end so mask history can resolve current forms.
2332 // We will recompute hashes once all history items are committed below.
2333 dt_dev_set_history_end_ext(dev, history_end);
2334
2335 // Sanitize and flatten module order
2336 dt_ioppr_resync_pipeline(dev, imgid, "dt_dev_read_history_no_image end", FALSE);
2337
2338 // After resync, iop_order_list was renumbered with correct sequential values.
2339 // Fix hist->iop_order for entries recovered from a missing order entry: they
2340 // got a placeholder value of 0 above; sync them from the now-correct module order.
2341 for(GList *h = dev->history; h; h = g_list_next(h))
2342 {
2344 if(hist->module && hist->iop_order < 1 && hist->module->iop_order > 0)
2345 hist->iop_order = hist->module->iop_order;
2346 }
2347
2348 // Update "masks history"
2349 // This design is stupid because `dt_dev_history_item_t *hist->forms` is not read
2350 // from the DB history items (`main.history`), but from the `main.masks_history`,
2351 // and later on, `dev->forms` is restored at the `history_end` index from dumping
2352 // the content of the `dt_dev_history_item_t *hist->forms` snapshot.
2353 // This only means that `hist->forms` doesn't belong to the `dt_dev_history_item_t *`
2354 // but should live in its own branch. See `dt_dev_pop_history_items_ext()`
2355 dt_masks_read_masks_history(dev, imgid);
2356
2357 dt_image_cache_read_release(read_lock_img);
2358 read_lock_img = NULL;
2359
2360 // Now we have fully-populated history items:
2361 // Commit params to modules and publish the masks on the raster stack for other modules to find
2362 GList *accumulated_forms = NULL;
2363 for(GList *history = g_list_first(dev->history); history; history = g_list_next(history))
2364 {
2365 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
2366 if(!hist)
2367 {
2368 fprintf(stderr, "[dt_dev_read_history_ext] we have no history item. This is not normal.\n");
2369 continue;
2370 }
2371 else if(!hist->module)
2372 {
2373 dt_print(DT_DEBUG_HISTORY, "[history] keeping archival history item %s (%s) without live module binding\n",
2374 hist->op_name, hist->multi_name);
2375 continue;
2376 }
2377
2378 // Same accumulation rule as dt_dev_pop_history_items_ext(): an item without its own forms
2379 // snapshot is hashed against the last snapshot recorded at or before its position.
2380 if(hist->forms) accumulated_forms = hist->forms;
2381
2382 dt_iop_module_t *module = hist->module;
2383 _history_to_module(hist, module, accumulated_forms);
2384 hist->hash = hist->module->hash;
2385
2386 // Register the freshly-read history item now (its hash is final), so the
2387 // node<->history resynchronization can resolve `params` to a real entry.
2389 dt_supervisor_history(DT_SV_CREATE, hist->hash, module->op, module->multi_priority,
2390 module->multi_name, module->iop_order, g_list_index(dev->history, hist),
2391 imgid, hist->enabled, module, hist->params, hist->blend_params, hist->forms);
2392
2393 dt_print(DT_DEBUG_HISTORY, "[history] successfully loaded module %s history (enabled: %i)\n", hist->module->op, hist->enabled);
2394 }
2395
2398 // The call above seeds dev->forms_hash from a freshly-zeroed dev, which always compares
2399 // as "changed" the first time -- that's establishing the baseline against what was just
2400 // loaded from disk, not a pending edit. Clear it so the first mask interaction (even a
2401 // no-op selection click) doesn't trigger a spurious history commit + full DB rewrite.
2402 dev->forms_changed = FALSE;
2403
2404 dt_dev_set_history_end_ext(dev, history_end);
2405 // Note: dt_dev_set_history_end already updates dev->history_hash
2406
2407 dt_print(DT_DEBUG_HISTORY, "[history] dt_dev_read_history_ext completed\n");
2408 return first_run;
2409}
2410
2411
2413{
2414 for(; list; list = g_list_next(list))
2415 {
2416 dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)list->data;
2417 if (hitem->module == module)
2418 {
2419 hitem->module = NULL;
2420 }
2421 }
2422}
2423
2428
2436{
2437 return (module->flags() & IOP_FLAGS_NO_HISTORY_STACK);
2438}
2439
2447{
2448 return module->default_enabled || (module->force_enable && module->force_enable(module, module->enabled));
2449}
2450
2458{
2459 return module->has_defaults ? module->has_defaults(module) : TRUE;
2460}
2461
2469{
2470 if(!module->blend_params || !module->default_blendop_params) return TRUE;
2471
2472 return memcmp(module->blend_params, module->default_blendop_params,
2473 sizeof(dt_develop_blend_params_t)) == 0;
2474}
2475
2486
2487typedef gboolean (*dt_iop_module_filter_t)(dt_iop_module_t *module);
2488
2495static void _dev_history_add_filtered(dt_develop_t *dev, dt_iop_module_filter_t filter) REQUIRES(dev->history_mutex)
2496{
2497 for(GList *item = g_list_first(dev->iop); item; item = g_list_next(item))
2498 {
2499 dt_iop_module_t *module = (dt_iop_module_t *)(item->data);
2500 if(!_module_leaves_no_history(module) && filter(module))
2501 dt_dev_add_history_item_ext(dev, module, FALSE, TRUE);
2502 }
2503}
2504
2509{
2510 return module->enabled && _module_is_default_or_forced_enabled(module);
2511}
2512
2517{
2518 return module->enabled && !_module_is_default_or_forced_enabled(module)
2519 && _module_params_are_default(module);
2520}
2521
2526{
2527 return module->enabled && !_module_is_default_or_forced_enabled(module)
2528 && !_module_params_are_default(module);
2529}
2530
2535{
2536 return !module->enabled
2537 && (module->default_enabled || module->workflow_enabled || _module_has_nondefault_internal_params(module));
2538}
2539
2548static void _dt_dev_history_compress_internal(dt_develop_t *dev, const gboolean write_history)
2549{
2550 // Rebuild the history list under lock, but run expensive cross-subsystem
2551 // operations (history->modules sync, optional DB write) after releasing it.
2552 // This keeps history_mutex hold time short and avoids lock-order contention
2553 // with GUI/pipeline users touching masks/pixelpipe.
2555
2556 // Cleanup old history
2558
2559 // Rebuild an history from current pipeline.
2560 // First: modules enabled by default or forced enabled for technical reasons
2562
2563 // Second: modules enabled by user
2564 // 2.1 : start with modules that still have default params,
2566
2567 // 2.2 : then modules that are set to non-default
2569
2570 // Third: disabled modules that have history because they were enabled by default or
2571 // because their internal params (including blendops) differ from defaults. Maybe users
2572 // want to re-enable them later, or it's modules enabled by default that were manually disabled.
2573 // Put them the end of the history, so user can truncate it after the last enabled item
2574 // to get rid of disabled history if needed.
2576
2577 dt_dev_set_history_end_ext(dev, g_list_length(dev->history));
2579
2586 if(write_history)
2587 {
2591 }
2592}
2593
2594void dt_dev_history_compress_ext(dt_develop_t *dev, gboolean write_history)
2595{
2596 _dt_dev_history_compress_internal(dev, write_history);
2597}
2598
2603
2604void dt_dev_history_truncate(dt_develop_t *dev, const int32_t imgid)
2605{
2607
2608 // Remove tail entries (num >= end).
2609 // history_end is a cursor expressed in "number of applied items" terms:
2610 // - keep items [0..history_end-1]
2611 // - remove items [history_end..]
2612 GList *link = g_list_nth(dev->history, dt_dev_get_history_end_ext(dev));
2613 while(link)
2614 {
2615 GList *next = g_list_next(link);
2616 const dt_dev_history_item_t *h = (const dt_dev_history_item_t *)link->data;
2617 if(dt_supervisor_active() && h && h->module)
2618 dt_supervisor_history(DT_SV_DELETE, h->hash, h->module->op, h->module->multi_priority,
2619 h->module->multi_name, h->module->iop_order, h->num,
2620 dev->image_storage.id, h->enabled, h->module, h->params, h->blend_params, h->forms);
2621 dt_dev_free_history_item(link->data);
2622 dev->history = g_list_delete_link(dev->history, link);
2623 link = next;
2624 }
2625
2627
2628 // Re-apply history and resync iop order from the truncated stack.
2636 dt_dev_write_history_ext(dev, imgid);
2638}
2639
2640void dt_dev_history_compress_or_truncate(dt_develop_t *dev) REQUIRES_SHARED(dev->history_mutex)
2641{
2642 if(dt_dev_get_history_end_ext(dev) == g_list_length(dev->history))
2644 else
2645 dt_dev_history_truncate(dev, dev->image_storage.id);
2646}
2647
2648
2657static int _check_deleted_instances(dt_develop_t *dev, GList **_iop_list, GList *history_list,
2658 GList **removed_gui_modules)
2659{
2660 GList *iop_list = *_iop_list;
2661 int deleted_module_found = 0;
2662
2663 // we will check on dev->iop if there's a module that is not in history
2664 GList *modules = iop_list;
2665 while(modules)
2666 {
2667 dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2668 if(IS_NULL_PTR(mod)) continue;
2669
2670 int delete_module = 0;
2671
2672 // base modules are a special case
2673 // most base modules won't be in history and must not be deleted
2674 // but the user may have deleted a base instance of a multi-instance module
2675 // and then undo and redo, so we will end up with two entries in dev->iop
2676 // with multi_priority == 0, this can't happen and the extra one must be deleted
2677 // dev->iop is sorted by (priority, multi_priority DESC), so if the next one is
2678 // a base instance too, one must be deleted
2679 if(mod->multi_priority == 0)
2680 {
2681 GList *modules_next = g_list_next(modules);
2682 if(modules_next)
2683 {
2684 dt_iop_module_t *mod_next = (dt_iop_module_t *)modules_next->data;
2685 if(strcmp(mod_next->op, mod->op) == 0 && mod_next->multi_priority == 0)
2686 {
2687 // is the same one, check which one must be deleted
2688 const int mod_in_history = (dt_dev_history_get_first_item_by_module(history_list, mod) != NULL);
2689 const int mod_next_in_history = (dt_dev_history_get_first_item_by_module(history_list, mod_next) != NULL);
2690
2691 // current is in history and next is not, delete next
2692 if(mod_in_history && !mod_next_in_history)
2693 {
2694 mod = mod_next;
2695 modules = modules_next;
2696 delete_module = 1;
2697 }
2698 // current is not in history and next is, delete current
2699 else if(!mod_in_history && mod_next_in_history)
2700 {
2701 delete_module = 1;
2702 }
2703 else
2704 {
2705 if(mod_in_history && mod_next_in_history)
2706 fprintf(
2707 stderr,
2708 "[_check_deleted_instances] found duplicate module %s %s (%i) and %s %s (%i) both in history\n",
2709 mod->op, mod->multi_name, mod->multi_priority, mod_next->op, mod_next->multi_name,
2710 mod_next->multi_priority);
2711 else
2712 fprintf(
2713 stderr,
2714 "[_check_deleted_instances] found duplicate module %s %s (%i) and %s %s (%i) none in history\n",
2715 mod->op, mod->multi_name, mod->multi_priority, mod_next->op, mod_next->multi_name,
2716 mod_next->multi_priority);
2717 }
2718 }
2719 }
2720 }
2721 // this is a regular multi-instance and must be in history
2722 else
2723 {
2724 delete_module = (dt_dev_history_get_first_item_by_module(history_list, mod) == NULL);
2725 }
2726
2727 // if module is not in history we delete it
2728 if(delete_module && mod)
2729 {
2730 deleted_module_found = 1;
2731
2732 iop_list = g_list_delete_link(iop_list, modules);
2733
2734 // remove the module reference from all snapshots
2736
2737 // don't delete the module, a pipe may still need it
2738 dev->alliop = g_list_append(dev->alliop, mod);
2739
2740 // Its widgets used to be destroyed RIGHT HERE, under history_mutex as writer --
2741 // GTK teardown inside the engine lock. The caller collects the removed instances
2742 // and destroys their widgets after the lock is released; the module is already
2743 // unlinked from the iop list and parked in alliop, so nothing re-touches it in
2744 // the meantime.
2745 if(removed_gui_modules)
2746 *removed_gui_modules = g_list_append(*removed_gui_modules, mod);
2747
2748 // and reset the list
2749 modules = iop_list;
2750 continue;
2751 }
2752
2753 modules = g_list_next(modules);
2754 }
2755 if(deleted_module_found) iop_list = g_list_sort(iop_list, dt_sort_iop_by_order);
2756
2757 *_iop_list = iop_list;
2758
2759 return deleted_module_found;
2760}
2761
2768static int _rebuild_multi_priority(GList *history_list)
2769{
2770 int changed = 0;
2771 for(const GList *history = history_list; history; history = g_list_next(history))
2772 {
2773 dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)history->data;
2774
2775 // if multi_priority is different in history and dev->iop
2776 // we keep the history version
2777 if(hitem->module && hitem->module->multi_priority != hitem->multi_priority)
2778 {
2779 dt_iop_update_multi_priority(hitem->module, hitem->multi_priority);
2780 changed = 1;
2781 }
2782 }
2783 return changed;
2784}
2785
2793static void _reset_module_instance(GList *hist, dt_iop_module_t *module, int multi_priority)
2794{
2795 for(; hist; hist = g_list_next(hist))
2796 {
2797 dt_dev_history_item_t *hit = (dt_dev_history_item_t *)hist->data;
2798
2799 if(IS_NULL_PTR(hit->module) && strcmp(hit->op_name, module->op) == 0 && hit->multi_priority == multi_priority)
2800 {
2801 hit->module = module;
2802 }
2803 }
2804}
2805
2813static void _undo_items_cb(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data)
2814{
2815 struct _cb_data *udata = (struct _cb_data *)user_data;
2816 dt_undo_history_t *hdata = (dt_undo_history_t *)data;
2817 _reset_module_instance(hdata->after_snapshot, udata->module, udata->multi_priority);
2818}
2819
2830static int _create_deleted_modules(GList **_iop_list, GList *history_list)
2831{
2832 GList *iop_list = *_iop_list;
2833 int changed = 0;
2834 gboolean done = FALSE;
2835
2836 GList *l = history_list;
2837 while(l)
2838 {
2839 GList *next = g_list_next(l);
2840 dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)l->data;
2841
2842 // this fixes the duplicate module when undo: hitem->multi_priority = 0;
2843 if(IS_NULL_PTR(hitem->module))
2844 {
2845 changed = 1;
2846
2847 const dt_iop_module_t *base_module = dt_iop_get_module_from_list(iop_list, hitem->op_name);
2848 if(IS_NULL_PTR(base_module))
2849 {
2850 fprintf(stderr, "[_create_deleted_modules] can't find base module for %s\n", hitem->op_name);
2851 return changed;
2852 }
2853
2854 // from there we create a new module for this base instance. The goal is to do a very minimal setup of the
2855 // new module to be able to write the history items. From there we reload the whole history back and this
2856 // will recreate the proper module instances.
2857 dt_iop_module_t *module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
2858 if(dt_iop_load_module(module, base_module->so, base_module->dev))
2859 {
2860 return changed;
2861 }
2862 module->instance = base_module->instance;
2863
2864 // adjust the multi_name of the new module
2865 g_strlcpy(module->multi_name, hitem->multi_name, sizeof(module->multi_name));
2867 module->iop_order = hitem->iop_order;
2868
2869 // we insert this module into dev->iop
2870 iop_list = g_list_insert_sorted(iop_list, module, dt_sort_iop_by_order);
2871
2872 // if not already done, set the module to all others same instance
2873 if(!done)
2874 {
2875 _reset_module_instance(history_list, module, hitem->multi_priority);
2876
2877 // and do that also in the undo/redo lists
2878 struct _cb_data udata = { module, hitem->multi_priority };
2880 done = TRUE;
2881 }
2882
2883 hitem->module = module;
2884 }
2885 l = next;
2886 }
2887
2888 *_iop_list = iop_list;
2889
2890 return changed;
2891}
2892
2893
2894// returns 1 if the topology of the pipe has changed, aka it needs a full rebuild
2895// 0 means only internal parameters of pipe nodes have change, so it's a mere resync
2896int dt_dev_history_refresh_nodes_ext(dt_develop_t *dev, GList **iop, GList *history,
2897 GList **removed_gui_modules)
2898{
2899 GList *iop_list = *iop;
2900
2901 // topology has changed?
2902 int pipe_remove = 0;
2903
2904 // we have to check if multi_priority has changed since history was saved
2905 // we will adjust it here
2906 if(_rebuild_multi_priority(history))
2907 {
2908 pipe_remove = 1;
2909 iop_list = g_list_sort(iop_list, dt_sort_iop_by_order);
2910 }
2911
2912 // check if this undo a delete module and re-create it
2913 if(_create_deleted_modules(&iop_list, history))
2914 pipe_remove = 1;
2915
2916 // check if this is a redo of a delete module or an undo of an add module
2917 if(_check_deleted_instances(dev, &iop_list, history, removed_gui_modules))
2918 pipe_remove = 1;
2919
2920 *iop = iop_list;
2921
2922 // if topology has changed, we need to reorder modules in GUI
2923 if(pipe_remove) dt_dev_signal_modules_moved(dev);
2924
2925 return pipe_remove;
2926}
2927
2928
2929/* ---------------------------------------------------------------------------------------------------
2930 * Out-of-history transient param channel (see dev_history.h).
2931 *
2932 * Thread-safety: the slot is guarded by `dev->transient_params_mutex`. Writers (GUI/worker thread)
2933 * `_set`/`_clear`; the pipeline reader (`_get`) copies out under the same lock and never dereferences
2934 * the publishing module. The published payload is a plain byte copy of the module params struct, so the
2935 * pipeline never touches GUI-owned `module->params`.
2936 * ------------------------------------------------------------------------------------------------- */
2937
2938void dt_dev_transient_params_set(dt_iop_module_t *module, const void *params, const size_t params_size,
2939 const void *blend_params, const size_t blend_size)
2940{
2941 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->dev) || IS_NULL_PTR(params) || params_size == 0) return;
2942 dt_develop_t *dev = module->dev;
2943
2945
2946 if(dev->transient_params.params_size != (int32_t)params_size)
2947 {
2949 dev->transient_params.params = g_malloc0(params_size);
2951 }
2953 memcpy(dev->transient_params.params, params, params_size);
2954
2955 if(!IS_NULL_PTR(blend_params) && blend_size > 0)
2956 {
2957 if(dev->transient_params.blend_size != (int32_t)blend_size)
2958 {
2960 dev->transient_params.blend_params = g_malloc0(blend_size);
2961 dev->transient_params.blend_size = (int32_t)blend_size;
2962 }
2964 memcpy(dev->transient_params.blend_params, blend_params, blend_size);
2965 }
2966 else
2967 {
2970 }
2971
2972 dev->transient_params.module = module;
2973 dev->transient_params.serial++;
2974
2976
2977 /* Intentionally does NOT trigger a pipe recompute. The caller flags the pipe the way that fits its
2978 * use case: a realtime module (drawlayer) raises DT_DEV_PIPE_TOP_CHANGED on the main pipe for a fast
2979 * focused-piece resync, while a geometry-changing edit (crop/ashift) drives a full
2980 * dt_dev_pixelpipe_resync_history_all() so every pipe (main, preview, virtual) replans ROI/formats.
2981 * Auto-triggering here too would route those edits through the partial focused-piece resync as well
2982 * and race the full one (garbled geometry on warm re-edits). */
2983}
2984
2986{
2987 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->dev)) return;
2988 dt_develop_t *dev = module->dev;
2989
2991 if(dev->transient_params.module == module)
2992 {
2997 dev->transient_params.module = NULL;
2998 dev->transient_params.serial++;
2999 }
3001
3002 /* As with _set, the caller is responsible for re-triggering the pipe (resync / history commit) so it
3003 * re-commits the focused module's piece from history instead of the dropped transient snapshot. */
3004}
3005
3007 void *out_params, const size_t out_params_size,
3008 void *out_blend, const size_t out_blend_size, gboolean *out_has_blend)
3009{
3010 if(!IS_NULL_PTR(out_has_blend)) *out_has_blend = FALSE;
3011 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module) || IS_NULL_PTR(out_params) || out_params_size == 0) return FALSE;
3012
3013 gboolean ok = FALSE;
3015 if(dev->transient_params.module == module && !IS_NULL_PTR(dev->transient_params.params)
3016 && dev->transient_params.params_size == (int32_t)out_params_size)
3017 {
3018 memcpy(out_params, dev->transient_params.params, out_params_size);
3019 ok = TRUE;
3020
3021 if(!IS_NULL_PTR(out_blend) && out_blend_size > 0 && !IS_NULL_PTR(dev->transient_params.blend_params)
3022 && dev->transient_params.blend_size == (int32_t)out_blend_size)
3023 {
3024 memcpy(out_blend, dev->transient_params.blend_params, out_blend_size);
3025 if(!IS_NULL_PTR(out_has_blend)) *out_has_blend = TRUE;
3026 }
3027 }
3029 return ok;
3030}
3031
3033{
3034 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module)) return FALSE;
3036 const gboolean active = (dev->transient_params.module == module);
3038 return active;
3039}
void reload_defaults(dt_iop_module_t *module)
Definition ashift.c:5828
#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)
int dt_atomic_get_int(dt_atomic_int *var)
int dt_atomic_sub_int(dt_atomic_int *var, int decr)
int dt_atomic_add_int(dt_atomic_int *var, int incr)
int dt_atomic_exch_int(dt_atomic_int *var, int value)
static void * dt_atomic_exch_ptr(dt_atomic_ptr *var, void *value)
Definition atomic.h:80
static void * dt_atomic_get_ptr(const dt_atomic_ptr *var)
Definition atomic.h:76
size_t params_size(dt_imageio_module_format_t *self)
Definition avif.c:571
#define m
Definition basecurve.c:283
int dt_develop_blend_version(void)
Definition blend.c:1714
int dt_develop_blend_legacy_params(dt_iop_module_t *module, const void *const old_params, const int old_version, void *new_params, const int new_version, const int length)
Definition blend.c:1778
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const float threshold
void dt_conf_set_string(const char *name, const char *val)
gboolean dt_image_is_matrix_correction_supported(const dt_image_t *img)
gboolean dt_image_is_raw(const dt_image_t *img)
int dt_image_monochrome_flags(const dt_image_t *img)
gboolean dt_image_is_hdr(const dt_image_t *img)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
void dt_image_history_changed(const int32_t imgid, const gboolean refresh_filmstrip)
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
static void _remove_history_leaks(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Remove history items past history_end when allowed.
void dt_apply_dev_history_update(dt_develop_t *dev)
Reload the current darkroom history and refresh every dependent GUI.
static gboolean _module_blend_params_are_default(dt_iop_module_t *module)
Check if blend params match defaults.
void dt_dev_history_set_commit_gui_handler(dt_dev_history_commit_gui_handler_t handler)
static int _rebuild_multi_priority(GList *history_list)
Resync module multi_priority values from history.
int dt_dev_write_history_item(const int32_t imgid, dt_dev_history_item_t *h, int32_t num)
void dt_dev_history_compress_or_truncate(dt_develop_t *dev) REQUIRES_SHARED(dev -> history_mutex)
Compress history if history_end is at top, otherwise truncate.
static void _history_to_module(const dt_dev_history_item_t *const hist, dt_iop_module_t *module, GList *current_forms)
Apply a history item to a module instance.
int dt_dev_next_multi_priority_for_op(dt_develop_t *dev, const char *op)
Return the next available multi_priority for an operation.
static void _dt_dev_modules_reload_defaults(dt_develop_t *dev)
Reset every module to its (already-computed) default params before history is overlaid.
static int _check_deleted_instances(dt_develop_t *dev, GList **_iop_list, GList *history_list, GList **removed_gui_modules)
Detect and handle module instances that exist in iop list but not in history.
static void _dev_history_add_filtered(dt_develop_t *dev, dt_iop_module_filter_t filter) REQUIRES(dev -> history_mutex)
Append history items for modules that pass a filter.
gboolean dt_dev_transient_params_get(dt_develop_t *dev, const dt_iop_module_t *module, void *out_params, const size_t out_params_size, void *out_blend, const size_t out_blend_size, gboolean *out_has_blend)
static void _undo_items_cb(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data)
Undo iterator callback to fix module pointers in snapshots.
void dt_dev_pop_history_items_ext(dt_develop_t *dev) REQUIRES_SHARED(dev -> history_mutex)
Apply history items to module params up to dev->history_end.
static void _history_invalidate_cb(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item)
Undo iterator callback to invalidate module pointers in snapshots.
static gboolean _compress_enabled_user_nondefault_params(dt_iop_module_t *module)
Filter: enabled modules with non-default params (user edits).
void dt_dev_history_snapshot_release(dt_dev_history_snapshot_t *snapshot)
Release every reference a snapshot holds and reset it. Needs no lock.
static dt_dev_history_item_t * _search_history_by_op(dt_develop_t *dev, const dt_iop_module_t *module) REQUIRES_SHARED(dev -> history_mutex)
Find the first history item matching a module operation name.
void dt_dev_history_set_undo_restore_gui_handler(dt_dev_history_undo_restore_gui_handler_t handler)
static void _dev_history_db_row_cb(void *user_data, const int32_t id, const int num, const int modversion, const char *operation, const void *module_params, const int param_length, const gboolean enabled, const void *blendop_params, const int bl_length, const int blendop_version, const int multi_priority, const char *multi_name, const char *preset_name)
Adapter callback for history DB rows.
static void _find_so_for_history_entry(dt_develop_t *dev, dt_dev_history_item_t *hist)
Bind a history entry to a module instance (.so) in dev->iop.
static gboolean _module_params_are_default(dt_iop_module_t *module)
Check if module params match defaults.
gboolean dt_dev_read_history_ext(dt_develop_t *dev, const int32_t imgid) REQUIRES(dev -> history_mutex)
Read history and masks from DB and populate dev->history.
static gboolean _module_is_default_or_forced_enabled(dt_iop_module_t *module)
Check if a module is enabled by default or force-enabled.
static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs) NO_THREAD_SAFETY_ANALYSIS
Apply an undo/redo history snapshot to a develop context.
static dt_iop_module_t * _history_merge_resolve_dest_instance(dt_develop_t *dev_dest, const dt_iop_module_t *mod_src, gboolean *created, gboolean *reused_base) REQUIRES_SHARED(dev_dest -> history_mutex)
Resolve or create the destination module instance for history merge.
static void _process_history_db_entry(dt_develop_t *dev, const int32_t imgid, const int id, const int num, const int modversion, const char *operation, const void *module_params, const int param_length, const gboolean enabled, const void *blendop_params, const int bl_length, const int blendop_version, const int multi_priority, const char *multi_name, const char *preset_name, int *legacy_params, const gboolean presets)
Build a history item from DB row data and append it to dev->history.
static int _sync_params(dt_dev_history_item_t *hist, const void *module_params, const int param_length, const int modversion, int *legacy_params, const char *preset_name)
Load or convert module params into a history item.
gboolean _module_leaves_no_history(dt_iop_module_t *module)
Return whether a module never writes history entries.
void dt_dev_history_undo_end_record_locked(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Finish an undo record with history_mutex already locked.
dt_dev_history_item_t * dt_dev_history_item_create(void)
Allocate a fresh, blank history item with refcount 1.
int dt_dev_replace_history_on_image(dt_develop_t *dev_src, const int32_t dest_imgid, const gboolean reload_defaults, const char *msg)
Replace an image history with the content of dev_src.
static int _create_deleted_modules(GList **_iop_list, GList *history_list)
Recreate missing module instances referenced by history.
static void _history_write_state_release(_history_write_state_t *state)
void dt_dev_transient_params_clear(dt_iop_module_t *module)
Drop the transient slot if it belongs to module.
void dt_dev_history_cleanup(void)
Cleanup cached statements or state used by history I/O.
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.
void dt_dev_transient_params_set(dt_iop_module_t *module, const void *params, const size_t params_size, const void *blend_params, const size_t blend_size)
Out-of-history transient param channel.
int dt_history_merge_module_into_history(dt_develop_t *dev_dest, dt_develop_t *dev_src, dt_iop_module_t *mod_src) REQUIRES(dev_dest -> history_mutex)
Merge a single module instance into a destination history.
void dt_dev_free_history_item(gpointer data)
Release a reference to a history item (used as GList free callback).
gboolean dt_dev_history_item_update_from_params(dt_develop_t *dev, dt_dev_history_item_t *hist, dt_iop_module_t *module, const gboolean enabled, const void *params, const int32_t params_size, const dt_develop_blend_params_t *blend_params, GList *forms)
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.
GList * dt_history_duplicate(GList *hist)
Deep-copy a history list.
static dt_dev_history_item_t * _dt_dev_history_item_duplicate_one(const dt_dev_history_item_t *old)
int dt_dev_copy_module_contents(dt_develop_t *dev_dest, dt_develop_t *dev_src, dt_iop_module_t *mod_dest, const dt_iop_module_t *mod_src)
int dt_dev_history_refresh_nodes_ext(dt_develop_t *dev, GList **iop, GList *history, GList **removed_gui_modules)
Refresh GUI module nodes to match history state.
void dt_dev_history_free_history(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Free the whole history list attached to dev->history.
static void _history_undo_data_free(gpointer data)
Free an undo history snapshot structure.
void dt_dev_history_compress_ext(dt_develop_t *dev, gboolean write_history)
Variant of history compression that optionally skips DB writeback.
gboolean dt_dev_add_history_item_ext(dt_develop_t *dev, struct dt_iop_module_t *module, gboolean enable, gboolean force_new_item) REQUIRES(dev -> history_mutex)
Append or update a history item for a module.
gboolean(* dt_iop_module_filter_t)(dt_iop_module_t *module)
void dt_dev_history_undo_invalidate_module(dt_iop_module_t *module)
Invalidate a module pointer inside undo snapshots.
void dt_dev_history_compress(dt_develop_t *dev)
Compress an history from a loaded pipeline, aka simply take a snapshot of all modules parameters....
uint64_t dt_dev_history_compute_hash(dt_develop_t *dev) REQUIRES_SHARED(dev -> history_mutex)
Get the integrity checksum of the whole history stack. This should be done ONLY when history is chang...
dt_dev_history_item_t * dt_dev_history_item_ref(dt_dev_history_item_t *item)
Take a reference on a history item. Pair with dt_dev_free_history_item().
gboolean dt_dev_history_is_image_in_dev(GList *imgs)
Check whether any image of the list is the one currently loaded in the darkroom.
static void _insert_default_modules(dt_develop_t *dev, dt_iop_module_t *module, gboolean is_inited) REQUIRES(dev -> history_mutex)
Insert default modules into history when needed.
void dt_dev_history_snapshot_take(dt_develop_t *dev, dt_dev_history_snapshot_t *snapshot) REQUIRES_SHARED(dev -> history_mutex)
Take a snapshot of dev->history. Caller holds history_mutex (reader suffices).
int dt_dev_history_item_from_source_history_item(dt_develop_t *dev_dest, dt_develop_t *dev_src, const dt_dev_history_item_t *hist_src, dt_iop_module_t *mod_dest, dt_dev_history_item_t **out_hist)
int dt_dev_merge_history_into_image(dt_develop_t *dev_src, int32_t dest_imgid, const GList *mod_list, gboolean merge_iop_order, const dt_history_merge_strategy_t mode, const gboolean paste_instances, const char *source_label, dt_hm_batch_state_t *batch)
Merge a list of modules into a destination image history via dt_history_merge().
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_write_history(dt_develop_t *dev, gboolean async)
Thread-safe wrapper around dt_dev_write_history_ext() for dev->image_storage.id.
void dt_dev_history_truncate(dt_develop_t *dev, const int32_t imgid)
static gboolean _compress_enabled_default_or_forced(dt_iop_module_t *module)
Filter: enabled modules that are default/forced enabled.
static void _reset_module_instance(GList *hist, dt_iop_module_t *module, int multi_priority)
Rebind history items to a module instance after recreation.
static dt_dev_history_undo_restore_gui_handler_t _undo_restore_gui_handler
dt_dev_history_item_t * dt_dev_history_get_last_item_by_module(GList *history_list, dt_iop_module_t *module, int history_end)
Find the last history item referencing a module up to history_end.
gboolean dt_dev_transient_params_active(dt_develop_t *dev, const dt_iop_module_t *module)
static void _dt_dev_history_compress_internal(dt_develop_t *dev, const gboolean write_history)
Rebuild history from current pipeline state.
static void _cleanup_history(const int32_t imgid)
Delete all history entries for an image from the DB.
void dt_dev_history_undo_end_record(dt_develop_t *dev)
Finish an undo record for history changes.
gboolean dt_dev_init_default_history(dt_develop_t *dev, const int32_t imgid, gboolean apply_auto_presets)
Initialize module defaults and insert required default modules.
gboolean dt_history_module_skip_copy(const int flags)
Determine whether a module should be skipped during history copy.
guint dt_dev_mask_history_overload(GList *dev_history, guint threshold)
static int _dt_dev_write_history_job_run(dt_job_t *job)
void dt_dev_history_pixelpipe_update(dt_develop_t *dev, gboolean rebuild)
Rebuild or resync pixelpipes after backend history changes.
void dt_dev_history_undo_start_record(dt_develop_t *dev)
Start an undo record for history changes.
static dt_dev_history_commit_gui_handler_t _commit_gui_handler
void dt_dev_history_commit_item_now(dt_develop_t *dev, dt_iop_module_t *module, gboolean enable)
Commit one history item immediately – record undo, take history_mutex as writer, write through,...
static void _sync_blendop_params(dt_dev_history_item_t *hist, const void *blendop_params, const int bl_length, const int blendop_version, int *legacy_params)
Load or convert blendop params into a history item.
void dt_dev_history_undo_start_record_locked(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Start an undo record with history_mutex already locked.
static void _write_history_from_state(const _history_write_state_t *state, const int32_t imgid)
void dt_dev_invalidate_history_module(GList *list, dt_iop_module_t *module)
Remove a module pointer from a history list.
void dt_dev_history_notify_change(dt_develop_t *dev, const int32_t imgid)
Notify the rest of the app that history changes were written.
gboolean dt_dev_reload_history_items(dt_develop_t *dev, const int32_t imgid)
Reload history from DB and rebuild pipelines/GUI state.
static gboolean _compress_disabled_with_history(dt_iop_module_t *module)
Filter: disabled modules that still need history entries.
static void _history_write_state_take(dt_develop_t *dev, _history_write_state_t *state) REQUIRES_SHARED(dev -> history_mutex)
static gboolean _compress_enabled_user_default_params(dt_iop_module_t *module)
Filter: enabled modules with default params (user enabled).
static gboolean _dev_auto_apply_presets(dt_develop_t *dev, int32_t imgid)
Apply auto-presets and default iop order for a fresh history.
static gboolean _module_has_nondefault_internal_params(dt_iop_module_t *module)
Check if any module params (including blend params) are non-default.
dt_dev_history_item_t * dt_dev_history_get_first_item_by_module(GList *history_list, dt_iop_module_t *module)
Find the first history item referencing a module.
dt_dev_history_item_t * dt_dev_history_cow_touch(dt_develop_t *dev, dt_dev_history_item_t *hist)
Copy-on-write gate for mutating a history item in place.
void dt_dev_write_history_ext(dt_develop_t *dev, const int32_t imgid) REQUIRES_SHARED(dev -> history_mutex)
Write dev->history to DB and XMP for a given image id.
void(* dt_dev_history_commit_gui_handler_t)(struct dt_develop_t *dev, struct dt_iop_module_t *module)
Post-commit presentation work: viewport-size refresh for geometry-changing modules,...
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:1905
void dt_dev_history_gui_update(struct dt_develop_t *dev)
Apply history-loaded params to module GUIs.
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1899
void(* dt_dev_history_undo_restore_gui_handler_t)(struct dt_develop_t *dev, int mask_edit_mode, int request_mask_display)
Restores the presentation half of an undo record: the blending panel's mask toggles for the focused m...
#define dt_dev_pixelpipe_resync_history_all(dev)
#define dt_dev_pixelpipe_rebuild_all(dev)
#define dt_dev_pixelpipe_update_history_all(dev)
void dt_dev_signal_modules_moved(dt_develop_t *dev)
Definition develop.c:1848
void dt_dev_masks_list_change(dt_develop_t *dev)
Definition develop.c:1340
int dt_dev_get_thumbnail_size(dt_develop_t *dev)
Definition develop.c:353
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:237
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:143
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1854
GList * dt_dev_load_modules(dt_develop_t *dev)
Definition develop.c:98
void dt_dev_masks_update_hash(dt_develop_t *dev)
Definition develop.c:1921
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1865
dt_dev_image_storage_t dt_dev_ensure_image_storage(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:956
void dt_dev_masks_list_update(dt_develop_t *dev)
Definition develop.c:1345
static uint64_t dt_dev_get_history_hash(const dt_develop_t *dev)
Definition develop.h:494
dt_dev_pixelpipe_display_mask_t
Definition develop.h:121
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:499
gboolean enabled
–doc was passed on the command line
static int rwlock NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:340
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
void * legacy_params(dt_imageio_module_format_t *self, const void *const old_params, const size_t old_params_size, const int old_version, const int new_version, size_t *new_size)
Definition exr.cc:293
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
static gboolean enable(const dt_image_t *image)
Definition highlights.c:879
void dt_history_toast(const char *format,...)
void dt_history_message(const char *format,...)
void dt_history_changed(const dt_history_change_t what)
Raise it. Internal to this code.
Everything the history, styles and presets code says to the outside world: messages for the user,...
@ DT_HISTORY_CHANGE_DEVELOP
@ FOR_RAW
@ FOR_NOT_MONO
@ FOR_LDR
@ FOR_HDR
@ FOR_NOT_COLOR
int dt_history_merge(dt_develop_t *dev_dest, dt_develop_t *dev_src, const int32_t dest_imgid, const GList *mod_list, const gboolean merge_iop_order, const dt_history_merge_strategy_t strategy, const gboolean force_new_modules, const char *source_label, dt_hm_batch_state_t *batch)
Merge a list of modules into a destination image, solving pipeline topologies for proper insertion of...
dt_history_merge_strategy_t
void dt_history_repository_foreach_auto_preset_row(const int32_t imgid, const dt_image_t *image, const char *workflow_preset, const int iformat, const int excluded, dt_history_repository_row_cb cb, void *user_data)
gboolean dt_history_repository_delete_dev_history(const int32_t imgid)
gboolean dt_history_repository_write_item(const int32_t imgid, const int num, const char *operation, const void *op_params, const int op_params_size, const int module_version, const gboolean enabled, const void *blendop_params, const int blendop_params_size, const int blendop_version, const int multi_priority, const char *multi_name)
gboolean dt_history_repository_module_exists(const int32_t imgid, const char *operation)
gboolean dt_history_repository_get_autoapply_ioporder_params(const int32_t imgid, const dt_image_t *image, const int iformat, const int excluded, void **params, int32_t *params_len)
gboolean dt_history_repository_set_end(const int32_t imgid, const int32_t history_end)
int32_t dt_history_repository_get_end(const int32_t imgid)
int32_t dt_history_repository_get_next_num(const int32_t imgid)
void dt_history_repository_foreach_row(const int32_t imgid, dt_history_repository_row_cb cb, void *user_data)
#define UNKNOWN_IMAGE
Definition image.h:78
@ DT_IMAGE_NO_LEGACY_PRESETS
Definition image.h:132
@ DT_IMAGE_AUTO_PRESETS_APPLIED
Definition image.h:130
void dt_image_cache_write_release(dt_image_t *img, dt_image_cache_write_mode_t mode)
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_IMAGE_CACHE_RELAXED
Definition image_cache.h:50
@ DT_IMAGE_CACHE_SAFE
Definition image_cache.h:48
dt_iop_module_t * dt_iop_get_module_from_list(GList *iop_list, const char *op)
Definition imageop.c:1697
gboolean dt_iop_module_needs_mask_history_ext(const dt_iop_module_t *module, gboolean *raster, gboolean *drawn, gboolean *parametric)
Definition imageop.c:1205
void dt_iop_load_default_params(dt_iop_module_t *module)
Definition imageop.c:145
void dt_iop_compute_module_hash(dt_iop_module_t *module, GList *masks)
Definition imageop.c:1461
void dt_iop_reload_defaults(dt_iop_module_t *module)
Definition imageop.c:792
void dt_iop_commit_blend_params(dt_iop_module_t *module, const dt_develop_blend_params_t *blendop_params)
Definition imageop.c:1234
int dt_iop_load_module(dt_iop_module_t *module, dt_iop_module_so_t *module_so, dt_develop_t *dev)
Definition imageop.c:1102
gboolean dt_iop_module_needs_mask_history(const dt_iop_module_t *module)
Definition imageop.c:1228
gboolean dt_iop_check_modules_equal(dt_iop_module_t *mod_1, dt_iop_module_t *mod_2)
Definition imageop.c:1382
dt_iop_module_t * dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
Definition imageop.c:1839
void dt_iop_update_multi_priority(dt_iop_module_t *module, int new_priority)
Definition imageop.c:1801
@ IOP_FLAGS_HIDDEN
Definition imageop.h:189
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_UNSAFE_COPY
Definition imageop.h:196
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_FLAGS_NO_HISTORY_STACK
Definition imageop.h:193
struct dt_iop_tonecurve_params_t preset
gboolean dt_ioppr_has_iop_order_list(int32_t imgid)
Check whether the image has an explicit order list stored in DB.
Definition iop_order.c:1095
GList * dt_ioppr_get_iop_order_list_version(dt_iop_order_t version)
Return the built-in order list for a given version.
Definition iop_order.c:1066
gint dt_sort_iop_by_order(gconstpointer a, gconstpointer b)
Compare two module instances by iop_order for sorting.
Definition iop_order.c:1976
GList * dt_ioppr_iop_order_copy_deep(GList *iop_order_list)
Deep-copy an order list.
Definition iop_order.c:1970
gboolean dt_ioppr_write_iop_order_list(GList *iop_order_list, const int32_t imgid)
Persist an order list to the DB for a given image.
Definition iop_order.c:1035
int dt_ioppr_get_iop_order(GList *iop_order_list, const char *op_name, const int multi_priority)
Return the iop_order for a given operation/instance pair.
Definition iop_order.c:913
void dt_ioppr_set_default_iop_order(dt_develop_t *dev, const int32_t imgid)
Set dev->iop_order_list to the default order for a given image.
Definition iop_order.c:1309
void dt_ioppr_resync_pipeline(dt_develop_t *dev, const int32_t imgid, const char *msg, gboolean check_duplicates)
Resynchronize pipeline order and related structures.
Definition iop_order.c:1241
GList * dt_ioppr_deserialize_iop_order_list(const char *buf, size_t size)
Deserialize an order list from a binary blob.
Definition iop_order.c:2606
dt_iop_order_t
Definition iop_order.h:149
@ DT_IOP_ORDER_ANSEL_RAW
Definition iop_order.h:154
@ DT_IOP_ORDER_ANSEL_JPG
Definition iop_order.h:155
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:137
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:407
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:131
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:114
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:155
@ DT_JOB_QUEUE_USER_BG
Definition jobs.h:56
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_HISTORY
Definition logging.h:75
@ DT_DEBUG_VERBOSE
Definition logging.h:78
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
void dt_masks_read_masks_history(dt_develop_t *dev, const int32_t imgid)
Definition masks.c:1097
void dt_masks_write_masks_history_item(const int32_t imgid, const int num, dt_masks_form_t *form)
Definition masks.c:1110
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
dt_masks_edit_mode_t dt_masks_get_edit_mode(struct dt_iop_module_t *module)
Definition masks.c:1148
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
int dt_masks_copy_used_forms_for_module(dt_develop_t *dev_dest, dt_develop_t *dev_src, const struct dt_iop_module_t *mod_src)
GList * dt_masks_snapshot_current_forms(dt_develop_t *dev, gboolean reset_changed)
void dt_masks_replace_current_forms(dt_develop_t *dev, GList *forms)
dt_masks_form_t * dt_masks_form_ref(dt_masks_form_t *form)
void dt_masks_form_unref(dt_masks_form_t *form)
dt_masks_edit_mode_t
@ DT_MASKS_EDIT_OFF
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
const char * name
Definition pdf.h:90
#define DT_PIXELPIPE_CACHE_HASH_INVALID
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
dt_iop_module_t *int multi_priority
dt_dev_history_snapshot_t snapshot
dt_iop_params_t * params
Definition dev_history.h:54
dt_atomic_int refcount
Definition dev_history.h:73
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:57
struct dt_iop_module_t *gboolean enabled
Definition dev_history.h:52
A reference-sharing snapshot of dev->history, for readers that outlive a lock.
dt_atomic_int shutdown
int32_t gui_attached
Definition develop.h:167
GList * iop_order_list
Definition develop.h:275
dt_image_t image_storage
Definition develop.h:225
GList * iop
Definition develop.h:269
struct dt_develop_t::@12 transient_params
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
uint64_t serial
Definition develop.h:299
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
GList * history
Definition develop.h:259
struct dt_iop_module_t *void * params
Definition develop.h:294
GList * alliop
Definition develop.h:271
dt_atomic_int history_write_pending
Definition develop.h:265
dt_pthread_mutex_t transient_params_mutex
Definition develop.h:301
int32_t blend_size
Definition develop.h:298
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
GList * forms
Definition develop.h:304
void * blend_params
Definition develop.h:297
int32_t params_size
Definition develop.h:296
uint64_t history_hash
Definition image.h:404
int32_t flags
Definition image.h:401
GTimeSpan change_timestamp
Definition image.h:415
int32_t id
Definition image.h:401
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
char multi_name[128]
Definition imageop.h:387
struct dt_develop_t * dev
Definition imageop.h:311
int32_t instance
Definition imageop.h:266
gboolean enabled
Definition imageop.h:313
dt_iop_module_so_t * so
Definition imageop.h:376
int request_mask_display
Definition imageop.h:276
struct dt_develop_blend_params_t * default_blendop_params
Definition imageop.h:349
int32_t params_size
Definition imageop.h:335
dt_dev_operation_t op
Definition imageop.h:259
dt_iop_params_t * params
Definition imageop.h:333
Definition iop_order.h:160
char name[25]
Definition iop_order.h:168
int iop_order
Definition iop_order.h:163
char operation[20]
Definition iop_order.h:166
union dt_iop_order_entry_t::@26 o
int32_t instance
Definition iop_order.h:167
GList * points
Definition masks.h:253
GList * before_snapshot
dt_dev_pixelpipe_display_mask_t request_mask_display
GList * before_iop_order_list
GList * after_iop_order_list
dt_masks_edit_mode_t mask_edit_mode
void dt_supervisor_rekey(const uint64_t old_hash, const uint64_t new_hash)
void dt_supervisor_history(const dt_sv_op_t op, const uint64_t param_hash, const char *op_name, const int multi_priority, const char *multi_name, const int iop_order, const int history_index, const int32_t imgid, const gboolean enabled, const dt_iop_module_t *module, const void *params, const dt_develop_blend_params_t *blend_params, GList *forms)
Definition supervisor.c:804
@ DT_SV_UPDATE
Definition supervisor.h:79
@ DT_SV_CREATE
Definition supervisor.h:78
@ DT_SV_DELETE
Definition supervisor.h:81
static gboolean dt_supervisor_active(void)
Definition supervisor.h:95
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43
void dt_undo_iterate_internal(dt_undo_t *self, uint32_t filter, gpointer user_data, void(*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
Definition undo.c:394
void dt_undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, void(*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs), void(*free_data)(gpointer data))
Definition undo.c:163
dt_undo_type_t
Definition undo.h:40
@ DT_UNDO_HISTORY
Definition undo.h:43
dt_undo_t * dt_undo_get_global(void)
Definition darktable.c:611
void * dt_undo_data_t
Definition undo.h:70
dt_undo_action_t
Definition undo.h:65
@ DT_ACTION_UNDO
Definition undo.h:66
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()