Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dev_snapshot.c
Go to the documentation of this file.
1/*
2 This file is part of ansel,
3 Copyright (C) 2025-2026 Guillaume STUTIN.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20
21#include "develop/iop_order.h"
22#include "caches/mipmap_cache.h"
23#include "control/control.h"
24#include "control/jobs.h"
25#include "develop/dev_history.h"
26#include "develop/develop.h"
29#include "views/dev_backbuf.h"
30
31#include <math.h>
32#include "gui/application.h"
33
34// Real state behind a dt_dev_snapshot_t handle. Heap-allocated and refcounted so that copying a
35// dt_dev_snapshot_t (e.g. libs/snapshots.c shuffling its fixed-size slot array) only ever copies a
36// stable pointer -- never this struct itself, which a background recompute job can still be
37// touching. `pipe`'s accurate ("main") reprocess runs on that job (control/jobs.h, mirroring
38// dtgtk/thumbnail.c's own refcounted-job pattern for the exact same problem); `preview_pipe` is
39// cheap enough to always run inline on the GUI thread.
40//
41// A new job for `pipe` is requested every time dt_dev_snapshot_draw() sees dev's
42// viewport has moved, exactly like dev->pipe's own darkroom worker loop, which replans and
43// reprocesses on every zoom/pan tick with no artificial delay either. Responsiveness is instead
44// naturally paced by "at most one job in flight at a time" (see _schedule_main_recompute()): while
45// one is running, newer requests are simply dropped and re-derived from dev's then-current
46// viewport once that job's completion triggers the next redraw -- the same effect a delay would
47// have bought, without adding latency once a job slot is actually free.
48//
49// `job`, `pending_roi`, `last_roi`, `roi_valid` are shared between the GUI thread and the job's
50// worker thread and guarded by `lock`. Everything else here is only ever touched from the GUI
51// thread. `pipe`/`preview_pipe` themselves need no extra locking beyond that: dt_dev_lock_pipe_surface()
52// (views/dev_backbuf.c) and the pixelpipe cache are already built for exactly one writer
53// (whichever thread calls dt_dev_pixelpipe_process()) concurrent with the GUI thread reading the
54// published backbuf -- the same guarantee that already makes dev->pipe safe between the darkroom
55// worker thread and the GUI thread.
83
85{
88
89 if(!IS_NULL_PTR(engine->pipe))
90 {
92 dt_free(engine->pipe);
93 }
94 if(!IS_NULL_PTR(engine->preview_pipe))
95 {
97 dt_free(engine->preview_pipe);
98 }
99 if(!IS_NULL_PTR(engine->frozen))
100 {
101 dt_dev_cleanup(engine->frozen);
102 dt_free(engine->frozen);
103 }
104
106 dt_free(engine);
107}
108
109// Drops one reference; the last one frees the engine. May run on the GUI thread (dt_dev_snapshot_clear())
110// or on the recompute job's own worker thread (its params-destroy callback) -- whichever happens
111// last, exactly like dtgtk/thumbnail.c's _thumbnail_release()/_thumbnail_free().
113{
114 if(IS_NULL_PTR(engine)) return;
115 if(dt_atomic_sub_int(&engine->ref_count, 1) == 1) _engine_free(engine);
116}
117
118static void _recompute_job_cleanup(void *params)
119{
121}
122
124{
125 if(IS_NULL_PTR(snap) || IS_NULL_PTR(snap->engine)) return;
126
127 dt_dev_snapshot_engine_t *engine = snap->engine;
128 snap->engine = NULL;
129
131
132 dt_pthread_mutex_lock(&engine->lock);
133 dt_job_t *job = engine->job;
135 // Best-effort: a queued-but-not-yet-started job is skipped outright; one already running is
136 // never preempted mid-process() (exactly like dev->pipe is never preempted mid-process()), it
137 // just finds `destroying` set and skips publishing/redrawing once it does finish.
138 if(!IS_NULL_PTR(job)) dt_control_job_cancel(job);
139
140 _engine_unref(engine); // drop our own reference; the job (if any) still holds its own.
141}
142
144{
145 return !IS_NULL_PTR(snap) && !IS_NULL_PTR(snap->engine) && snap->engine->captured;
146}
147
148// Mirrors _update_darkroom_roi()'s main-pipe branch (develop/develop.c), substituting `pipe`'s
149// own processed size for dt_dev_geometry_processed_width(dev)/height -- the snapshot's own image may have
150// different dimensions than the one currently open in darkroom. Deliberately ignores the caller's
151// clip rect: only dev's pan/zoom (dev->roi) drives what gets processed, so resizing/dragging a
152// compare split line never triggers a reprocess.
153static gboolean _compute_main_roi(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, dt_iop_roi_t *roi)
154{
155 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(roi)) return FALSE;
157 if(pipe->processed_width <= 0 || pipe->processed_height <= 0) return FALSE;
158
159 const float scale = dt_dev_roi_request_natural_scale(dev) * dt_dev_viewport_scaling(dev);
160 const int roi_width = (int)roundf(scale * pipe->processed_width);
161 const int roi_height = (int)roundf(scale * pipe->processed_height);
162
163 roi->width = MAX(1, MIN(roi_width, dt_dev_viewport_box_width(dev)));
164 roi->height = MAX(1, MIN(roi_height, dt_dev_viewport_box_height(dev)));
165 roi->x = (int)roundf(dt_dev_viewport_center_x(dev) * roi_width - roi->width * .5f);
166 roi->y = (int)roundf(dt_dev_viewport_center_y(dev) * roi_height - roi->height * .5f);
167 roi->scale = scale;
168 return TRUE;
169}
170
171// Mirrors _update_darkroom_roi()'s preview-pipe branch: the whole image, fit to the widget, no
172// user zoom factor and no pan offset.
173static gboolean _compute_preview_roi(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, dt_iop_roi_t *roi)
174{
175 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(roi)) return FALSE;
176 if(!dt_dev_roi_request_valid(dev)) return FALSE;
177 if(pipe->processed_width <= 0 || pipe->processed_height <= 0) return FALSE;
178
179 const float scale = dt_dev_roi_request_natural_scale(dev);
180 roi->width = MAX(1, (int)roundf(scale * pipe->processed_width));
181 roi->height = MAX(1, (int)roundf(scale * pipe->processed_height));
182 roi->x = 0;
183 roi->y = 0;
184 roi->scale = scale;
185 return TRUE;
186}
187
188static gboolean _roi_equal(const dt_iop_roi_t *a, const dt_iop_roi_t *b)
189{
190 return a->x == b->x
191 && a->y == b->y
192 && a->width == b->width
193 && a->height == b->height
194 && fabs(a->scale - b->scale) < 1e-6;
195}
196
197// Runs `pipe` at `roi` and publishes a new backbuf. Mirrors what the darkroom worker loop does
198// for dev->pipe on every zoom/pan tick (_resync_pipe_with_history() -> dt_dev_pixelpipe_change()
199// with DT_DEV_PIPE_ZOOMED): re-committing no-history-stack modules (finalscale self-enables/
200// disables depending on scale) and re-settling piece->buf_in/out and processed_width/height
201// through dt_dev_pixelpipe_change() before every process() call at a *different* roi/scale than
202// the previous one. Skipping this leaves finalscale's state stale from whichever roi was last
203// committed, and the next process() at a different size reads/writes with the wrong geometry --
204// each row starting from the wrong offset (a diagonal shear), because the piece never learned its
205// target size actually changed.
207{
208 dt_dev_pixelpipe_set_input(pipe, engine->frozen->image_storage.id, engine->raw_width, engine->raw_height,
209 engine->raw_iscale, DT_MIPMAP_FULL);
212 return dt_dev_pixelpipe_process(pipe, *roi) == 0;
213}
214
215// Immediate GUI-thread only: (re)runs the cheap preview tier if its target roi
216// changed. Called on every draw -- cheap because it only changes when the widget is resized.
218{
219 dt_iop_roi_t roi = { 0 };
220 if(!_compute_preview_roi(dev, engine->preview_pipe, &roi)) return;
221 if(engine->preview_roi_valid && _roi_equal(&engine->preview_last_roi, &roi)) return;
222
223 engine->preview_roi_valid = _process_at_roi(engine, engine->preview_pipe, &roi);
224 if(engine->preview_roi_valid) engine->preview_last_roi = roi;
225}
226
227// Runs the accurate main tier at `roi` right now and publishes the result under `lock`. Used
228// directly (no job) for the capture-time smoke test, and by the recompute job otherwise.
229static gboolean _sync_main_now(dt_dev_snapshot_engine_t *engine, const dt_iop_roi_t *roi)
230{
231 const gboolean ok = _process_at_roi(engine, engine->pipe, roi);
232 dt_pthread_mutex_lock(&engine->lock);
233 engine->roi_valid = ok;
234 if(ok) engine->last_roi = *roi;
236 return ok;
237}
238
239// Runs on a control/jobs.h worker thread (DT_JOB_QUEUE_USER_FG), never on the GUI thread.
240static int32_t _recompute_job_run(dt_job_t *job)
241{
243 if(IS_NULL_PTR(engine)) return 1;
244 if(dt_atomic_get_int(&engine->destroying)) return 1;
245
246 dt_pthread_mutex_lock(&engine->lock);
247 // A cancelled or superseded job (dt_dev_snapshot_clear() ran, or a newer job replaced this one
248 // in the narrow window between scheduling and running) must not touch the pipe or publish a
249 // result -- same guard as dtgtk/thumbnail.c's _get_image_buffer().
250 const gboolean stale = engine->job != job || dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED;
251 const dt_iop_roi_t roi = engine->pending_roi;
253 if(stale) return 1;
254
255 const gboolean ok = _process_at_roi(engine, engine->pipe, &roi);
256
257 dt_pthread_mutex_lock(&engine->lock);
258 if(engine->job == job)
259 {
260 engine->roi_valid = ok;
261 if(ok) engine->last_roi = roi;
262 engine->job = NULL;
263 }
265
266 // dt_control_queue_redraw_center() is already called from worker threads elsewhere in the
267 // pixelpipe itself (develop/pixelpipe_hb.c's tiling progress messages, run from the darkroom
268 // worker thread), so this is a proven-safe cross-thread call, no marshaling needed.
270
271 return 0;
272}
273
274// Requests a main-tier reprocess at `roi` right now, matching dev->pipe's own
275// darkroom worker loop, which replans and reprocesses on every zoom/pan tick with no artificial
276// delay either. The only throttle is "never run two jobs concurrently on the same `pipe`" (unlike
277// the GUI-reads/job-writes pair, which is safe by construction -- dt_dev_lock_pipe_surface()'s own
278// contract -- two writers are not): if one is already in flight, this just records the latest
279// target and returns; the running job's completion triggers a redraw, dt_dev_snapshot_draw() sees
280// the still-stale roi on that next call, and calls back in here to start a fresh job for whatever
281// dev's viewport has become by then -- so responsiveness is bounded by "how fast one job finishes",
282// not by a fixed delay.
284{
285 dt_pthread_mutex_lock(&engine->lock);
286 const gboolean job_active = !IS_NULL_PTR(engine->job);
287 if(!job_active) engine->pending_roi = *roi;
289 if(job_active) return;
290
291 dt_job_t *job = dt_control_job_create(&_recompute_job_run, "snapshot recompute");
292 if(IS_NULL_PTR(job)) return;
293
294 dt_atomic_add_int(&engine->ref_count, 1); // the job's own reference
296
297 dt_pthread_mutex_lock(&engine->lock);
298 engine->job = job;
300
302 {
303 dt_pthread_mutex_lock(&engine->lock);
304 if(engine->job == job) engine->job = NULL;
306 dt_control_job_dispose(job); // triggers _recompute_job_cleanup(), dropping the ref taken above
307 }
308}
309
310// Approximates the current viewport from the cheap, fit-scale preview tier: cairo-translate/scale
311// the already-rendered fit image to roughly match dev's current pan/zoom, same technique as
312// darkroom.c's own _build_preview_fallback_surface() for dev->preview_pipe.
313static void _draw_preview_fallback(dt_dev_snapshot_engine_t *engine, dt_develop_t *dev, cairo_t *cr, int width,
314 int height)
315{
316 if(!engine->preview_roi_valid) return;
317 if(!dt_dev_lock_pipe_surface(dev, engine->preview_pipe, &engine->preview_locked, &engine->preview_wait,
318 "snapshot-preview", TRUE))
319 return;
320 if(IS_NULL_PTR(engine->preview_locked.surface) || IS_NULL_PTR(engine->preview_locked.entry)) return;
321
322 const float ppd = dt_gui_get_global()->ppd;
323 const float preview_wd = engine->preview_locked.width / ppd;
324 const float preview_ht = engine->preview_locked.height / ppd;
325 const float preview_scale = dt_dev_viewport_scaling(dev);
326 const float tx = 0.5f * width - dt_dev_viewport_center_x(dev) * preview_wd * preview_scale;
327 const float ty = 0.5f * height - dt_dev_viewport_center_y(dev) * preview_ht * preview_scale;
328
330 cairo_surface_set_device_scale(engine->preview_locked.surface, ppd, ppd);
331 cairo_save(cr);
332 cairo_translate(cr, tx, ty);
333 cairo_scale(cr, preview_scale, preview_scale);
334 cairo_rectangle(cr, 0, 0, preview_wd, preview_ht);
335 cairo_set_source_surface(cr, engine->preview_locked.surface, 0, 0);
336 cairo_fill(cr);
337 cairo_restore(cr);
339}
340
341gboolean dt_dev_snapshot_capture(dt_dev_snapshot_t *snap, dt_develop_t *dev, int32_t imgid,
342 GList *history_override, GList *iop_order_override,
343 int32_t history_end_override)
344{
345 dt_develop_t *frozen = NULL;
346 dt_dev_snapshot_engine_t *engine = NULL;
347 dt_mipmap_buffer_t buf = { 0 };
348 const dt_dev_pixelpipe_t *live_preview = NULL;
349
351 if(IS_NULL_PTR(snap) || IS_NULL_PTR(dev) || imgid <= 0) goto fail;
352
353 frozen = (dt_develop_t *)calloc(1, sizeof(dt_develop_t));
354 if(IS_NULL_PTR(frozen)) goto fail;
355 dt_dev_init(frozen, 0);
356
357 if(dt_dev_load_image(frozen, imgid))
358 {
359 dt_print(DT_DEBUG_DEV, "[dev_snapshot] capture failed: dt_dev_load_image failed for imgid=%d\n", imgid);
360 dt_dev_cleanup(frozen);
361 dt_free(frozen);
362 goto fail;
363 }
364
365 if(history_override)
366 {
368 frozen->history = history_override;
369 history_override = NULL; // ownership transferred to frozen; do not free again below
370 g_list_free_full(frozen->iop_order_list, dt_free_gpointer);
371 frozen->iop_order_list = iop_order_override;
372 iop_order_override = NULL;
373
374 for(GList *history = g_list_first(frozen->history); history; history = g_list_next(history))
375 {
376 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
377 if(IS_NULL_PTR(hist)) continue;
378 hist->module = dt_dev_get_module_instance(frozen, hist->op_name, hist->multi_name, hist->multi_priority);
379 if(IS_NULL_PTR(hist->module))
380 hist->module = dt_dev_create_module_instance(frozen, hist->op_name, hist->multi_name, hist->multi_priority, FALSE);
381 if(IS_NULL_PTR(hist->module))
382 hist->module = dt_iop_get_module_by_op_priority(frozen->iop, hist->op_name, -1);
383 if(IS_NULL_PTR(hist->module))
384 {
386 "[dev_snapshot] capture failed: unresolved module op=%s multi=%s priority=%d for imgid=%d\n",
387 hist->op_name, hist->multi_name, hist->multi_priority, imgid);
388 dt_dev_cleanup(frozen);
389 dt_free(frozen);
390 goto fail;
391 }
392 }
393
394 // Forms (mask geometry) aren't part of a history item's own params blob -- they're
395 // snapshotted per-commit as hist->forms (see dt_dev_pop_history_items_ext() and
396 // doc/masks_history_dedup.md). Replacing frozen->history above left frozen->forms
397 // untouched, still holding whatever dt_dev_load_image() read from this image's *saved*
398 // main.masks_history a few lines up -- not this override's live, possibly-uncommitted
399 // shapes. Re-derive it with the same accumulation rule dt_dev_pop_history_items_ext()
400 // uses: walk up to history_end_override, keep the last non-NULL hist->forms. Without
401 // this, a module needing mask history (retouch, drawn-mask blending) resolves its
402 // blend_params->mask_id against a group that pipe->forms doesn't contain, and its
403 // shapes silently fail to render in the snapshot.
404 GList *forms = NULL;
405 int hist_pos = 0;
406 for(GList *history = g_list_first(frozen->history); history && hist_pos < history_end_override;
407 history = g_list_next(history), hist_pos++)
408 {
409 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
410 if(hist->forms) forms = hist->forms;
411 }
412 dt_masks_replace_current_forms(frozen, forms);
413
414 dt_dev_set_history_end_ext(frozen, history_end_override);
416 }
417
419 DT_MIPMAP_BLOCKING, 'r');
420 if(IS_NULL_PTR(buf.buf) || buf.width <= 0 || buf.height <= 0)
421 {
422 dt_print(DT_DEBUG_DEV, "[dev_snapshot] capture failed: mipmap full unavailable for imgid=%d\n", imgid);
424 dt_dev_cleanup(frozen);
425 dt_free(frozen);
426 goto fail;
427 }
428
429 engine = (dt_dev_snapshot_engine_t *)calloc(1, sizeof(dt_dev_snapshot_engine_t));
430 if(IS_NULL_PTR(engine))
431 {
433 dt_dev_cleanup(frozen);
434 dt_free(frozen);
435 goto fail;
436 }
437 dt_pthread_mutex_init(&engine->lock, NULL);
438 dt_atomic_set_int(&engine->ref_count, 1);
440
441 engine->pipe = (dt_dev_pixelpipe_t *)calloc(1, sizeof(dt_dev_pixelpipe_t));
442 engine->preview_pipe = (dt_dev_pixelpipe_t *)calloc(1, sizeof(dt_dev_pixelpipe_t));
443 if(IS_NULL_PTR(engine->pipe) || IS_NULL_PTR(engine->preview_pipe))
444 {
446 if(engine->pipe) dt_free(engine->pipe);
447 if(engine->preview_pipe) dt_free(engine->preview_pipe);
449 dt_free(engine);
450 dt_dev_cleanup(frozen);
451 dt_free(frozen);
452 goto fail;
453 }
454
455 // Not combined with the calloc check above via `||`: both init calls must run unconditionally
456 // (short-circuiting would leave the second pipe raw calloc'd memory that never went through
457 // dt_dev_pixelpipe_init_cached(), which dt_dev_pixelpipe_cleanup() cannot safely be called on
458 // below -- its mutex would never have been initialized).
459 const gboolean pipe_inited = dt_dev_pixelpipe_init(engine->pipe, frozen);
460 const gboolean preview_inited = dt_dev_pixelpipe_init(engine->preview_pipe, frozen);
461 if(!pipe_inited || !preview_inited)
462 {
463 dt_print(DT_DEBUG_DEV, "[dev_snapshot] capture failed: pixelpipe init failed for imgid=%d\n", imgid);
465 if(pipe_inited) dt_dev_pixelpipe_cleanup(engine->pipe);
466 dt_free(engine->pipe);
467 if(preview_inited) dt_dev_pixelpipe_cleanup(engine->preview_pipe);
468 dt_free(engine->preview_pipe);
470 dt_free(engine);
471 dt_dev_cleanup(frozen);
472 dt_free(frozen);
473 goto fail;
474 }
475
476 engine->raw_width = buf.width;
477 engine->raw_height = buf.height;
478 engine->raw_iscale = buf.iscale;
479
480 // Reuse the live darkroom's ICC settings if any image is currently open in darkroom, so a
481 // captured snapshot/preview soft-proofs the same way the live pipe does. Harmless when frozen
482 // is the same image dev->preview_pipe already reflects; still correct when it's a different
483 // one, since ICC intent/profile are a display-wide GUI setting, not per-image state.
484 dt_develop_t *const live_dev = dt_dev_get_global();
485 live_preview = live_dev ? live_dev->preview_pipe : NULL;
486
487 for(int i = 0; i < 2; i++)
488 {
489 dt_dev_pixelpipe_t *p = i == 0 ? engine->pipe : engine->preview_pipe;
492 if(!IS_NULL_PTR(live_preview))
493 dt_dev_pixelpipe_set_icc(p, live_preview->icc_type, live_preview->icc_filename, live_preview->icc_intent);
496 dt_dev_pixelpipe_get_roi_out(p, p->iwidth, p->iheight, &p->processed_width, &p->processed_height);
497 }
498
500
501 engine->frozen = frozen; // ownership transferred: pipe nodes reference frozen->iop instances.
502
503 // Smoke-test render at dev's current viewport, synchronously and inline (no job -- there is
504 // nothing else that could hold a reference to `engine` yet): validates the capture the same way
505 // a full-resolution render used to (a failure here aborts the capture, same contract callers
506 // already rely on), and primes the first draw so it is never a blank frame.
507 dt_iop_roi_t roi = { 0 };
508 gboolean ok = FALSE;
509 if(_compute_main_roi(dev, engine->pipe, &roi))
510 ok = _sync_main_now(engine, &roi);
511 _sync_preview_now(engine, dev);
512
513 engine->captured = ok;
514 if(!ok)
515 {
516 _engine_unref(engine);
517 goto fail;
518 }
519
520 snap->engine = engine;
521 return TRUE;
522
523fail:
524 if(history_override) g_list_free_full(history_override, dt_free_gpointer);
525 if(iop_order_override) g_list_free_full(iop_order_override, dt_free_gpointer);
526 return FALSE;
527}
528
529void dt_dev_snapshot_draw(dt_dev_snapshot_t *snap, cairo_t *cri, struct dt_develop_t *dev,
530 int32_t width, int32_t height,
531 double clip_x, double clip_y, double clip_w, double clip_h)
532{
533 if(IS_NULL_PTR(snap) || IS_NULL_PTR(snap->engine) || IS_NULL_PTR(dev) || IS_NULL_PTR(cri)) return;
534 if(clip_w <= 0.0 || clip_h <= 0.0) return;
535
536 dt_dev_snapshot_engine_t *engine = snap->engine;
537
538 dt_iop_roi_t want_roi = { 0 };
539 const gboolean want_ok = _compute_main_roi(dev, engine->pipe, &want_roi);
540 _sync_preview_now(engine, dev);
541
542 dt_pthread_mutex_lock(&engine->lock);
543 const gboolean roi_valid = engine->roi_valid;
544 const dt_iop_roi_t last_roi = engine->last_roi;
546
547 const gboolean main_ready = want_ok && roi_valid && _roi_equal(&last_roi, &want_roi);
548 if(want_ok && !main_ready) _schedule_main_recompute(engine, &want_roi);
549
550 if(!main_ready && !engine->preview_roi_valid) return;
551
552 dt_aligned_pixel_t bg_color = { 0.0f };
553 dt_dev_get_background_color(dev, bg_color);
554
555 cairo_save(cri);
556 cairo_rectangle(cri, clip_x, clip_y, clip_w, clip_h);
557 cairo_clip(cri);
558
559 if(main_ready)
560 {
561 if(dt_dev_lock_pipe_surface(dev, engine->pipe, &engine->locked, &engine->wait, "snapshot", FALSE)
562 && !IS_NULL_PTR(engine->locked.surface))
563 dt_dev_render_locked_surface(cri, dev, &engine->locked, width, height, dt_dev_viewport_border_size(dev), bg_color);
564 }
565 else
566 {
567 cairo_set_source_rgb(cri, bg_color[0], bg_color[1], bg_color[2]);
568 cairo_paint(cri);
569 _draw_preview_fallback(engine, dev, cri, width, height);
570 }
571
572 cairo_restore(cri);
573}
574
575// clang-format off
576// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
577// vim: shiftwidth=2 expandtab tabstop=2 cindent
578// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
579// clang-format on
#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)
atomic_int dt_atomic_int
Definition atomic.h:68
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
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
gboolean dt_dev_lock_pipe_surface(dt_develop_t *dev, dt_dev_pixelpipe_t *pipe, dt_dev_locked_surface_t *locked, dt_dev_pixelpipe_cache_wait_t *wait, const char *wait_owner_tag, gboolean keep_previous_on_fail)
gboolean dt_dev_render_locked_surface(cairo_t *cr, const dt_develop_t *dev, dt_dev_locked_surface_t *locked, const int width, const int height, const int border, const dt_aligned_pixel_t bg_color)
void dt_dev_get_background_color(const dt_develop_t *dev, dt_aligned_pixel_t bg_color)
Definition dev_backbuf.c:47
void dt_dev_release_locked_surface(dt_dev_locked_surface_t *locked)
Definition dev_backbuf.c:99
dt_iop_module_t * dt_dev_get_module_instance(dt_develop_t *dev, const char *op, const char *multi_name, const int multi_priority)
Find a module instance by op name and instance metadata.
dt_iop_module_t * dt_dev_create_module_instance(dt_develop_t *dev, const char *op, const char *multi_name, const int multi_priority, gboolean use_next_priority)
Create a new module instance from an existing base .so.
void dt_dev_history_free_history(dt_develop_t *dev) REQUIRES(dev -> history_mutex)
Free the whole history list attached to dev->history.
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...
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_pixelpipe_propagate_formats(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_get_roi_out(dt_dev_pixelpipe_t *pipe, const int width_in, const int height_in, int *width, int *height)
void dt_dev_pixelpipe_change(dt_dev_pixelpipe_t *pipe)
float dt_dev_roi_request_natural_scale(const dt_develop_t *dev)
gboolean dt_dev_roi_request_valid(const dt_develop_t *dev)
static void _engine_unref(dt_dev_snapshot_engine_t *engine)
static void _draw_preview_fallback(dt_dev_snapshot_engine_t *engine, dt_develop_t *dev, cairo_t *cr, int width, int height)
void dt_dev_snapshot_draw(dt_dev_snapshot_t *snap, cairo_t *cri, struct dt_develop_t *dev, int32_t width, int32_t height, double clip_x, double clip_y, double clip_w, double clip_h)
static gboolean _compute_main_roi(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, dt_iop_roi_t *roi)
static void _engine_free(dt_dev_snapshot_engine_t *engine)
static int32_t _recompute_job_run(dt_job_t *job)
void dt_dev_snapshot_clear(dt_dev_snapshot_t *snap)
static void _schedule_main_recompute(dt_dev_snapshot_engine_t *engine, const dt_iop_roi_t *roi)
static void _recompute_job_cleanup(void *params)
gboolean dt_dev_snapshot_is_valid(const dt_dev_snapshot_t *snap)
static gboolean _compute_preview_roi(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, dt_iop_roi_t *roi)
static void _sync_preview_now(dt_dev_snapshot_engine_t *engine, dt_develop_t *dev)
static gboolean _sync_main_now(dt_dev_snapshot_engine_t *engine, const dt_iop_roi_t *roi)
gboolean dt_dev_snapshot_capture(dt_dev_snapshot_t *snap, dt_develop_t *dev, int32_t imgid, GList *history_override, GList *iop_order_override, int32_t history_end_override)
static gboolean _roi_equal(const dt_iop_roi_t *a, const dt_iop_roi_t *b)
static gboolean _process_at_roi(dt_dev_snapshot_engine_t *engine, dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
float dt_dev_viewport_center_y(const dt_develop_t *dev)
float dt_dev_viewport_center_x(const dt_develop_t *dev)
int32_t dt_dev_viewport_border_size(const dt_develop_t *dev)
int32_t dt_dev_viewport_box_height(const dt_develop_t *dev)
float dt_dev_viewport_scaling(const dt_develop_t *dev)
int32_t dt_dev_viewport_box_width(const dt_develop_t *dev)
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:237
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:1000
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:143
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:499
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_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
dt_iop_module_t * dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
Definition imageop.c:1839
dt_job_state_t dt_control_job_get_state(_dt_job_t *job)
Definition jobs.c:105
void dt_control_job_cancel(_dt_job_t *job)
Definition jobs.c:182
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_FG
Definition jobs.h:54
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:47
@ DT_DEBUG_DEV
Definition logging.h:53
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_replace_current_forms(dt_develop_t *dev, GList *forms)
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
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
@ DT_MIPMAP_BLOCKING
@ DT_MIPMAP_FULL
#define dt_mipmap_cache_get(B, C, D, E, F)
#define dt_mipmap_cache_release(B)
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
void dt_dev_pixelpipe_set_input(dt_dev_pixelpipe_t *pipe, int32_t imgid, int width, int height, float iscale, dt_mipmap_size_t size)
int dt_dev_pixelpipe_init(dt_dev_pixelpipe_t *pipe, dt_develop_t *dev)
void dt_dev_pixelpipe_set_icc(dt_dev_pixelpipe_t *pipe, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent)
void dt_dev_pixelpipe_create_nodes(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_cleanup(dt_dev_pixelpipe_t *pipe)
int dt_dev_pixelpipe_process(dt_dev_pixelpipe_t *pipe, dt_iop_roi_t roi)
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
@ DT_DEV_PIPE_ZOOMED
#define dt_dev_pixelpipe_synch_all(pipe)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
struct dt_pixel_cache_entry_t * entry
Definition dev_backbuf.h:45
cairo_surface_t * surface
Definition dev_backbuf.h:46
dt_colorspaces_color_profile_type_t icc_type
dt_iop_color_intent_t icc_intent
dt_dev_pixelpipe_t * preview_pipe
dt_iop_roi_t preview_last_roi
dt_dev_pixelpipe_t * pipe
dt_dev_locked_surface_t preview_locked
dt_dev_pixelpipe_cache_wait_t wait
dt_dev_pixelpipe_cache_wait_t preview_wait
dt_pthread_mutex_t lock
dt_dev_locked_surface_t locked
struct dt_dev_snapshot_engine_t * engine
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_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
GList * history
Definition develop.h:259
int32_t id
Definition image.h:401
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
One consumer's outstanding request, owned by that consumer, not by the queue.
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29