Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
drawlayer.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
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
19#ifdef HAVE_CONFIG_H
20#include "system/macros.h"
22#include "common/logging.h"
23#include "system/mem_alloc.h"
24#include "system/openmp.h"
26#include "common/paths.h"
27#include "common/hash.h"
28#include "config.h"
29#endif
30#include "develop/masks_gui.h"
31
32#include "widgets/bauhaus.h"
34#include "system/dtpthread.h"
35#include "common/imagebuf.h"
37#include "develop/iop_profile.h"
38#include "common/opencl.h"
39#include "common/conf.h"
40#include "control/control.h"
41#include "control/jobs.h"
42#include "develop/dev_history.h"
43#include "develop/develop.h"
44#include "develop/imageop.h"
45#include "develop/imageop_gui.h"
47#include "develop/masks.h"
49#include "pixel/interpolation.h"
51#include "gui/application.h"
53#include "iop/drawlayer/brush.h"
54#include "iop/drawlayer/cache.h"
57#include "iop/drawlayer/io.h"
59#include "iop/drawlayer/paint.h"
63#include "iop/iop_api.h"
64
65#include <glib/gstdio.h>
66#include <inttypes.h>
67#include <limits.h>
68#include <math.h>
69#include <sched.h>
70#include <stdlib.h>
71#include <string.h>
72#include "widgets/notebook.h"
74
76
77
81/*
82 * drawlayer architecture summary
83 * ------------------------------
84 *
85 * This module stores a painted premultiplied RGBA layer in a half-float TIFF sidecar.
86 * The persistent layer lives in full image coordinates (raw-sized canvas, but in the
87 * module's current pipeline geometry, not in raw sensor geometry). The GUI keeps:
88 *
89 * 1. a full-resolution half-float cache of the selected TIFF layer (`base_patch`),
90 * 2. (legacy) a widget-sized ARGB preview overlay (`live_surface`) for immediate feedback,
91 * 3. a worker thread that consumes stroke samples while the module has focus.
92 *
93 * The current stroke model is intentionally conservative:
94 * - `distance` defines a spatial metronome for resampling the path independently of
95 * `mouse_moved` dispatch cadence.
96 * - smoothing is applied only to the incoming raw cursor point before that resampling.
97 * - the kept implementation uses a simple linear extrapolation from two anchor samples
98 * separated by one brush radius. Several "cleverer" variants were tried here
99 * (kernel caching, fixed-N smoothing windows, higher-order extrapolation, inertial
100 * direction filters), but they either benchmarked slower, introduced sampling
101 * dependence again, or created visible cusps/overshoot. Those attempts were removed
102 * and are documented locally near the code they affected.
103 *
104 * The guiding rule throughout this file is: keep the authoritative geometry in layer
105 * space, and derive widget-space feedback from it, so GUI preview and pipeline output
106 * cannot drift apart due to duplicated math.
107 */
108
109#ifdef HAVE_OPENCL
114#endif
115
121
124
125#define _commit_dabs dt_drawlayer_commit_dabs
126#define _flush_layer_cache dt_drawlayer_flush_layer_cache
127#define _sync_widget_cache dt_drawlayer_sync_widget_cache
128#define _set_drawlayer_pipeline_realtime_mode dt_drawlayer_set_pipeline_realtime_mode
129#define _ensure_layer_cache dt_drawlayer_ensure_layer_cache
130#define _release_all_base_patch_extra_refs dt_drawlayer_release_all_base_patch_extra_refs
131#define _drawlayer_wait_for_rasterization_modal dt_drawlayer_wait_for_rasterization_modal
132#define _current_live_padding dt_drawlayer_current_live_padding
133#define _layer_to_widget_coords dt_drawlayer_layer_to_widget_coords
134#define _touch_stroke_commit_hash dt_drawlayer_touch_stroke_commit_hash
135#define _drawlayer_runtime_collect_inputs NULL
136#define _drawlayer_runtime_perform_action NULL
137
138gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, gboolean record_history);
141static void _refresh_layer_widgets(dt_iop_module_t *self);
142static void _sync_layer_controls(dt_iop_module_t *self);
143static void _sanitize_requested_layer_name(const char *requested, char *name, size_t name_size);
144static gboolean _prompt_layer_name_dialog(const char *title, const char *message, const char *initial_name,
145 char *name, size_t name_size);
148static gboolean _background_layer_job_done_idle(gpointer user_data);
152 gboolean flush_pending);
153static void _sync_cached_brush_colors(dt_iop_module_t *self, const float display_rgb[3]);
154
159
160#include "drawlayer/conf.c"
162#include "control/signal.h"
163
165static void _brush_pipeline_color_from_display(dt_iop_module_t *self, const float display_rgb[3], float pipeline_rgb[3])
166{
167 if(!display_rgb || IS_NULL_PTR(pipeline_rgb)) return;
168
169 pipeline_rgb[0] = _clamp01(display_rgb[0]);
170 pipeline_rgb[1] = _clamp01(display_rgb[1]);
171 pipeline_rgb[2] = _clamp01(display_rgb[2]);
172
173 if(!IS_NULL_PTR(self) && self->dev && self->dev->pipe)
174 {
175 const dt_iop_order_iccprofile_info_t *const display_profile
177 const dt_iop_order_iccprofile_info_t *const work_profile
179 if(!IS_NULL_PTR(display_profile) && !IS_NULL_PTR(work_profile))
180 {
181 float in[4] = { pipeline_rgb[0], pipeline_rgb[1], pipeline_rgb[2], 0.0f };
182 float out[4] = { pipeline_rgb[0], pipeline_rgb[1], pipeline_rgb[2], 0.0f };
183 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, display_profile, work_profile,
184 "drawlayer brush color");
185 pipeline_rgb[0] = out[0];
186 pipeline_rgb[1] = out[1];
187 pipeline_rgb[2] = out[2];
188 }
189 }
190
191 const float gain = exp2f(_conf_hdr_exposure());
192 pipeline_rgb[0] *= gain;
193 pipeline_rgb[1] *= gain;
194 pipeline_rgb[2] *= gain;
195}
196
198static void _sync_cached_brush_colors(dt_iop_module_t *self, const float display_rgb[3])
199{
201 if(IS_NULL_PTR(g) || !display_rgb) return;
202
203 g->ui.brush_display_color[0] = _clamp01(display_rgb[0]);
204 g->ui.brush_display_color[1] = _clamp01(display_rgb[1]);
205 g->ui.brush_display_color[2] = _clamp01(display_rgb[2]);
206 _brush_pipeline_color_from_display(self, g->ui.brush_display_color, g->ui.brush_pipeline_color);
207 g->ui.brush_color_valid = TRUE;
208}
209
211{
212 if(IS_NULL_PTR(self) || IS_NULL_PTR(input)) return;
214
215 uint32_t map_flags = 0u;
228
229 float display_rgb[3] = { 0.0f };
230 float pipeline_rgb[3] = { 0.0f };
231 if(!IS_NULL_PTR(g) && g->ui.brush_color_valid)
232 {
233 memcpy(display_rgb, g->ui.brush_display_color, sizeof(display_rgb));
234 memcpy(pipeline_rgb, g->ui.brush_pipeline_color, sizeof(pipeline_rgb));
235 }
236 else
237 {
238 _conf_display_color(display_rgb);
239 if(!IS_NULL_PTR(g)) _sync_cached_brush_colors(self, display_rgb);
240 if(!IS_NULL_PTR(g) && g->ui.brush_color_valid)
241 {
242 memcpy(display_rgb, g->ui.brush_display_color, sizeof(display_rgb));
243 memcpy(pipeline_rgb, g->ui.brush_pipeline_color, sizeof(pipeline_rgb));
244 }
245 else
246 {
247 _brush_pipeline_color_from_display(self, display_rgb, pipeline_rgb);
248 display_rgb[0] = _clamp01(display_rgb[0]);
249 display_rgb[1] = _clamp01(display_rgb[1]);
250 display_rgb[2] = _clamp01(display_rgb[2]);
251 }
252 }
253
254 input->map_flags = map_flags;
258 input->distance_percent = _conf_distance() / 100.0f;
259 input->smoothing_percent = _conf_smoothing() / 100.0f;
260 input->brush_radius = _conf_size();
261 input->brush_opacity = _conf_opacity() / 100.0f;
262 input->brush_flow = _conf_flow() / 100.0f;
264 input->brush_sprinkles = _conf_sprinkles() / 100.0f;
268 input->brush_mode = _conf_brush_mode();
269 input->color[0] = pipeline_rgb[0];
270 input->color[1] = pipeline_rgb[1];
271 input->color[2] = pipeline_rgb[2];
272 input->display_color[0] = display_rgb[0];
273 input->display_color[1] = display_rgb[1];
274 input->display_color[2] = display_rgb[2];
275}
276
278{
279 if(IS_NULL_PTR(input)) return;
280 input->have_layer_coords = FALSE;
281 input->lx = 0.0f;
282 input->ly = 0.0f;
283 if(IS_NULL_PTR(self)) return;
284
285 float lx = 0.0f;
286 float ly = 0.0f;
287 if(dt_drawlayer_widget_to_layer_coords(self, input->wx, input->wy, &lx, &ly))
288 {
289 input->lx = lx;
290 input->ly = ly;
291 input->have_layer_coords = TRUE;
292 }
293
296 "[drawlayer] raw-input batch=%u event=%u pos=%u widget=(%.3f,%.3f) raster=(%.3f,%.3f) ok=%d\n",
297 input->stroke_batch, input->event_index, input->stroke_pos, input->wx, input->wy, input->lx, input->ly,
298 input->have_layer_coords ? 1 : 0);
299}
300
301static gboolean _layer_name_non_empty(const char *name)
302{
303 if(IS_NULL_PTR(name)) return FALSE;
304 char tmp[DRAWLAYER_NAME_SIZE] = { 0 };
305 g_strlcpy(tmp, name, sizeof(tmp));
306 g_strstrip(tmp);
307 return tmp[0] != '\0';
308}
309
310static gboolean _get_current_work_profile_key(dt_iop_module_t *self, GList *iop_list, dt_dev_pixelpipe_t *pipe,
311 char *key, const size_t key_size)
312{
313 if(IS_NULL_PTR(key) || key_size == 0) return FALSE;
314 key[0] = '\0';
315 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe) || !iop_list) return FALSE;
316
317 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_iop_work_profile_info(self, iop_list);
318 if(IS_NULL_PTR(work_profile)) return FALSE;
319
320 g_snprintf(key, key_size, "%d|%d|%s", (int)work_profile->type, (int)work_profile->intent,
321 work_profile->filename);
322 return key[0] != '\0';
323}
324
334
335static void _destroy_process_scratch(gpointer data)
336{
338 if(IS_NULL_PTR(scratch)) return;
339 dt_drawlayer_cache_free_temp_buffer((void **)&scratch->layerbuf, "drawlayer process scratch");
340 dt_drawlayer_cache_free_temp_buffer((void **)&scratch->cl_background_rgba, "drawlayer process scratch");
341 dt_drawlayer_cache_free_temp_buffer((void **)&scratch->flush_update_rgba, "drawlayer process update scratch");
342 dt_free(scratch);
343}
344
346
348{
351 if(!IS_NULL_PTR(scratch)) return scratch;
352
353 scratch = g_malloc0(sizeof(*scratch));
354 if(IS_NULL_PTR(scratch)) return NULL;
355 g_private_set(&_drawlayer_process_scratch_key, scratch);
356 return scratch;
357}
358
359static inline __attribute__((always_inline)) gboolean _resolve_layer_geometry(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
360 const dt_dev_pixelpipe_iop_t *piece, int *layer_width,
361 int *layer_height, int *origin_x, int *origin_y)
362{
363 if(!IS_NULL_PTR(layer_width)) *layer_width = 0;
364 if(!IS_NULL_PTR(layer_height)) *layer_height = 0;
365 if(!IS_NULL_PTR(origin_x)) *origin_x = 0;
366 if(!IS_NULL_PTR(origin_y)) *origin_y = 0;
367 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return FALSE;
368 /* The canvas has the RAW image's dimensions, in this image's own orientation, and is centred
369 * on the module's frame -- the same for every pipe, every zoom and every crop, which is the
370 * point of anchoring to it: a crop changes which window of the canvas a render reads, not
371 * where the paint sits. See iop/drawlayer/coordinates.h.
372 *
373 * It used to be the module's own stage frame, lifted back through roi_out.scale. That size
374 * moves with every crop, and the authored raster was then fitted onto the new frame -- the
375 * paint moved and stretched with the crop.
376 *
377 * Thumbnail and export pipes may start from a downscaled mipmap; that shrinks the module's
378 * frame, not the canvas, and the placement's scale absorbs it. */
379 int resolved_width = 0;
380 int resolved_height = 0;
381 if(!dt_drawlayer_layer_canvas_for_pipe(self, pipe, &resolved_width, &resolved_height)) return FALSE;
382
383 (void)piece;
384
385 if(!IS_NULL_PTR(layer_width)) *layer_width = resolved_width;
386 if(!IS_NULL_PTR(layer_height)) *layer_height = resolved_height;
387 return resolved_width > 0 && resolved_height > 0;
388}
389
390static inline __attribute__((always_inline)) uint64_t _drawlayer_params_cache_hash(const int32_t imgid, const dt_iop_drawlayer_params_t *params,
391 const int layer_width, const int layer_height)
392{
393 /* Internal drawlayer base-cache identity must stay stable across transient
394 * stroke/hash updates. Key only by image + layer identity + working profile,
395 * not by volatile fields (stroke hash, sidecar timestamp...).
396 *
397 * This keeps the shared base patch line hot across interactive drawing ticks
398 * and avoids expensive rekey conflicts/republishing during realtime updates. */
399 uint64_t hash = 5381u;
400 hash = dt_hash(hash, (const char *)&imgid, sizeof(imgid));
401 hash = dt_hash(hash, (const char *)&layer_width, sizeof(layer_width));
402 hash = dt_hash(hash, (const char *)&layer_height, sizeof(layer_height));
403 if(!IS_NULL_PTR(params))
404 {
405 hash = dt_hash(hash, params->layer_name, sizeof(params->layer_name));
406 hash = dt_hash(hash, (const char *)&params->layer_order, sizeof(params->layer_order));
407 hash = dt_hash(hash, params->work_profile, sizeof(params->work_profile));
408 }
409 return hash ? hash : 1u;
410}
411
412static gboolean _rekey_shared_base_patch(drawlayer_patch_t *patch, const int32_t imgid,
413 const dt_iop_drawlayer_params_t *params)
414{
415 /* Rekeying lets the same pixelpipe cache line keep its allocated storage and
416 * current in-memory pixels while the serialized module hash advances to a new
417 * history snapshot. This is the central piece that lets other pipelines find
418 * the newest base patch through the cache instead of through GUI internals. */
419 if(IS_NULL_PTR(patch) || IS_NULL_PTR(patch->cache_entry) || IS_NULL_PTR(params)) return FALSE;
420 const uint64_t new_hash = _drawlayer_params_cache_hash(imgid, params, patch->width, patch->height);
421 if(new_hash == patch->cache_hash) return TRUE;
422 if(dt_dev_pixelpipe_cache_rekey(patch->cache_hash, new_hash, patch->cache_entry) == 0)
423 {
424 patch->cache_hash = new_hash;
425 return TRUE;
426 }
427
428 /* Fallback path: another cache line already owns `new_hash` (or rekeying
429 * failed for any other reason). Publish the current authoritative pixels into
430 * that target key explicitly so parallel/headless pipelines resolving by the
431 * latest params hash still see up-to-date content.
432 *
433 * We intentionally keep `patch` bound to its original cache entry/hash here,
434 * because this module may hold additional explicit refs on that entry
435 * (`base_patch_loaded_ref`, stroke refs). Rebinding `patch` would desynchronize
436 * those ref counters. */
437 if(IS_NULL_PTR(patch->pixels) || patch->width <= 0 || patch->height <= 0) return FALSE;
438
439 drawlayer_patch_t published = { 0 };
440 int created = 0;
441 if(!dt_drawlayer_cache_patch_alloc_shared(&published, new_hash, (size_t)patch->width * patch->height,
442 patch->width, patch->height, "drawlayer sidecar cache", &created))
443 return FALSE;
444
446 memcpy(published.pixels, patch->pixels, (size_t)patch->width * patch->height * 4 * sizeof(float));
448#ifdef HAVE_OPENCL
450 published.cache_entry, -1);
451#endif
452 dt_drawlayer_cache_patch_clear(&published, "drawlayer patch");
455 "[drawlayer] cache rekey conflict old=%" PRIu64 " new=%" PRIu64 " -> published snapshot instead\n",
456 patch->cache_hash, new_hash);
457 return TRUE;
458}
459
461{
462 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->process.base_patch.cache_entry) || g->process.base_patch_loaded_ref) return;
463 dt_dev_pixelpipe_cache_ref_count_entry(TRUE, g->process.base_patch.cache_entry);
464 g->process.base_patch_loaded_ref = TRUE;
465}
466
468{
469 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->process.base_patch.cache_entry)) return;
470 dt_dev_pixelpipe_cache_ref_count_entry(TRUE, g->process.base_patch.cache_entry);
471 g->process.base_patch_stroke_refs++;
472}
473
475{
476 if(IS_NULL_PTR(g)) return;
477 if(IS_NULL_PTR(g->process.base_patch.cache_entry))
478 {
479 /* Keep refcount bookkeeping state coherent even if the cache entry has
480 * already been detached/cleared. This prevents stale counters from being
481 * applied to a future reused `g->process.base_patch` entry. */
482 g->process.base_patch_loaded_ref = FALSE;
483 g->process.base_patch_stroke_refs = 0;
484 return;
485 }
486
487 if(g->process.base_patch_loaded_ref)
488 {
489 dt_dev_pixelpipe_cache_ref_count_entry(FALSE, g->process.base_patch.cache_entry);
490 g->process.base_patch_loaded_ref = FALSE;
491 }
492
493 while(g->process.base_patch_stroke_refs > 0)
494 {
495 dt_dev_pixelpipe_cache_ref_count_entry(FALSE, g->process.base_patch.cache_entry);
496 g->process.base_patch_stroke_refs--;
497 }
498}
499
500static inline __attribute__((always_inline)) gboolean _refresh_piece_base_cache(dt_iop_module_t *self, dt_iop_drawlayer_data_t *data,
503{
504 if(IS_NULL_PTR(self) || IS_NULL_PTR(data) || IS_NULL_PTR(params) || IS_NULL_PTR(piece)) return FALSE;
505 if(params->layer_name[0] == '\0')
506 {
507 dt_drawlayer_cache_patch_clear(&data->process.base_patch, "drawlayer patch");
508 data->process.cache_valid = FALSE;
509 data->process.cache_dirty = FALSE;
511 data->process.cache_imgid = -1;
512 data->process.cache_layer_name[0] = '\0';
513 data->process.cache_layer_order = -1;
515 return TRUE;
516 }
517
518 /* Display heartbeats hit this path for every pipeline pass. When the current
519 * in-memory patch already matches the selected layer identity and geometry,
520 * stay entirely in RAM and skip sidecar probing altogether. Layer names are
521 * the stable identity here; page order is only a lookup hint and may drift
522 * after sidecar rewrites without invalidating the pixels already cached. */
524 && data->process.cache_imgid == pipe->dev->image_storage.id
525 && !g_strcmp0(data->process.cache_layer_name, params->layer_name)
526 && data->process.base_patch.width > 0 && data->process.base_patch.height > 0)
527 {
528 const uint64_t cached_hash = _drawlayer_params_cache_hash(pipe->dev->image_storage.id, params,
531 if(data->process.base_patch.cache_hash == cached_hash)
532 return TRUE;
533 }
534
535 char path[DT_PATH_MAX] = { 0 };
536 const gboolean have_sidecar_path = dt_drawlayer_io_sidecar_path(pipe->dev->image_storage.id, path, sizeof(path));
537 const gboolean have_sidecar = have_sidecar_path && g_file_test(path, G_FILE_TEST_EXISTS);
538 dt_drawlayer_io_layer_info_t info = { 0 };
539 if(have_sidecar)
540 dt_drawlayer_io_find_layer(path, params->layer_name, params->layer_order, &info);
541
542 int layer_width = 0;
543 int layer_height = 0;
544 const gboolean have_pipe_geometry = _resolve_layer_geometry(self, pipe, piece, &layer_width, &layer_height, NULL, NULL);
545
546 /* The canvas is the raw frame in every pipe, so the stored page's own dimensions are no longer
547 * consulted to decide it -- they used to be, because a thumbnail or export pipe resolved a
548 * SMALLER canvas than the darkroom did (both followed the module's stage frame, and those pipes
549 * render downscaled), and reinterpreting the page at that size recentred it. Anchoring removes
550 * the divergence rather than papering over it: one canvas size for every pipe, and the pipe's
551 * own scale is absorbed by the placement.
552 *
553 * A page authored before this change was written in the post-crop frame and will be read as
554 * though it were raw, so it appears shifted until repainted. That was the agreed trade; the
555 * alternative was a versioned sidecar and a migration pass. */
556 if(!have_pipe_geometry)
557 {
558 if(info.found && info.width > 0 && info.height > 0)
559 {
560 layer_width = (int)info.width;
561 layer_height = (int)info.height;
562 }
563 else
564 return FALSE;
565 }
566
567 const uint64_t base_hash = _drawlayer_params_cache_hash(pipe->dev->image_storage.id, params, layer_width, layer_height);
569 && data->process.base_patch.cache_hash == base_hash && data->process.cache_imgid == pipe->dev->image_storage.id
570 && !g_strcmp0(data->process.cache_layer_name, params->layer_name))
571 return TRUE;
572
573 dt_drawlayer_cache_patch_clear(&data->process.base_patch, "drawlayer patch");
574 data->process.cache_valid = FALSE;
575 data->process.cache_dirty = FALSE;
577 data->process.cache_imgid = -1;
578 data->process.cache_layer_name[0] = '\0';
579 data->process.cache_layer_order = -1;
581
582 if(layer_width > 0 && layer_height > 0)
583 {
584 int created = 0;
586 _drawlayer_params_cache_hash(pipe->dev->image_storage.id, params, layer_width,
587 layer_height),
588 (size_t)layer_width * layer_height, layer_width, layer_height,
589 "drawlayer sidecar cache", &created))
590 return FALSE;
591
592 if(!created)
593 {
594 data->process.cache_valid = TRUE;
595 data->process.cache_imgid = pipe->dev->image_storage.id;
596 g_strlcpy(data->process.cache_layer_name, params->layer_name, sizeof(data->process.cache_layer_name));
597 data->process.cache_layer_order = params->layer_order;
598 return TRUE;
599 }
600
601 gboolean warm_loaded = FALSE;
602 if(have_sidecar && info.found)
603 {
604 dt_drawlayer_io_patch_t warm_patch = { 0 };
606 warm_patch.x = data->process.base_patch.x;
607 warm_patch.y = data->process.base_patch.y;
608 warm_patch.width = data->process.base_patch.width;
609 warm_patch.height = data->process.base_patch.height;
610 warm_patch.pixels = data->process.base_patch.pixels;
611 warm_loaded = dt_drawlayer_io_load_layer(path, params->layer_name, params->layer_order, layer_width,
612 layer_height, &warm_patch);
614 }
615
616 if(warm_loaded)
617 {
618 data->process.cache_valid = TRUE;
619 data->process.cache_imgid = pipe->dev->image_storage.id;
620 g_strlcpy(data->process.cache_layer_name, params->layer_name, sizeof(data->process.cache_layer_name));
621 data->process.cache_layer_order = info.found ? info.index : params->layer_order;
623 return TRUE;
624 }
625
626 data->process.cache_valid = TRUE;
627 data->process.cache_dirty = FALSE;
629 data->process.cache_imgid = pipe->dev->image_storage.id;
630 g_strlcpy(data->process.cache_layer_name, params->layer_name, sizeof(data->process.cache_layer_name));
631 data->process.cache_layer_order = info.found ? info.index : params->layer_order;
632 return TRUE;
633 }
634 return FALSE;
635}
636
638static void _blend_layer_over_input(float *output, const float *input, const float *layerbuf, const size_t pixels,
639 const gboolean use_preview_bg, const float preview_bg)
640{
641 if(IS_NULL_PTR(output) || IS_NULL_PTR(input) || IS_NULL_PTR(layerbuf) || pixels == 0) return;
642
643 const dt_aligned_pixel_simd_t preview_base = { preview_bg, preview_bg, preview_bg, 1.0f };
645 for(size_t kk = 0; kk < pixels; kk++)
646 {
647 const float *base = input + 4 * kk;
648 const float *layer = layerbuf + 4 * kk;
649 float *pixel = output + 4 * kk;
650 const float src_alpha = _clamp01(layer[3]);
651 if(src_alpha > 1e-8f)
652 {
653 const dt_aligned_pixel_simd_t base_v = use_preview_bg ? preview_base : dt_load_simd_aligned(base);
654 dt_aligned_pixel_simd_t src_v = dt_load_simd_aligned(layer);
655 const float inv_alpha = 1.0f - src_alpha;
656 const dt_aligned_pixel_simd_t inv_alpha_v = { inv_alpha, inv_alpha, inv_alpha, inv_alpha };
657 src_v[3] = src_alpha;
658 dt_store_simd_aligned(pixel, src_v + base_v * inv_alpha_v);
659 }
660 else
661 {
662 const dt_aligned_pixel_simd_t base_v = use_preview_bg ? preview_base : dt_load_simd_aligned(base);
663 dt_store_simd_aligned(pixel, base_v);
664 }
665 }
666}
667
668#ifdef HAVE_OPENCL
675
676static gboolean _drawlayer_sync_host_image_to_device(const int devid, cl_mem device_image, void *host_pixels,
677 const int width, const int height, const int bpp,
678 const dt_drawlayer_damaged_rect_t *dirty_rect)
679{
680 if(IS_NULL_PTR(device_image) || IS_NULL_PTR(host_pixels) || width <= 0 || height <= 0 || bpp <= 0) return FALSE;
681
682 if(dirty_rect && dirty_rect->valid)
683 {
684 const int dirty_x0 = CLAMP(dirty_rect->nw[0], 0, width);
685 const int dirty_y0 = CLAMP(dirty_rect->nw[1], 0, height);
686 const int dirty_x1 = CLAMP(dirty_rect->se[0], 0, width);
687 const int dirty_y1 = CLAMP(dirty_rect->se[1], 0, height);
688 const int dirty_w = dirty_x1 - dirty_x0;
689 const int dirty_h = dirty_y1 - dirty_y0;
690 if(dirty_w > 0 && dirty_h > 0 && (dirty_w < width || dirty_h < height))
691 {
692 const size_t origin[] = { (size_t)dirty_x0, (size_t)dirty_y0, 0 };
693 const size_t region[] = { (size_t)dirty_w, (size_t)dirty_h, 1 };
694 char *host_origin = (char *)host_pixels + ((size_t)dirty_y0 * width + dirty_x0) * (size_t)bpp;
695 if(dt_opencl_write_host_to_device_raw(devid, host_origin, device_image, origin, region, width * bpp,
696 CL_TRUE) == CL_SUCCESS)
697 return TRUE;
698 }
699 }
700
701 if(dt_opencl_is_pinned_memory(device_image))
702 {
703 void *mapped = dt_opencl_map_image(devid, device_image, TRUE, CL_MAP_WRITE, width, height, bpp);
704 if(dt_opencl_unmap_mem_object(devid, device_image, mapped) == CL_SUCCESS)
705 return TRUE;
706 }
707
708 return dt_opencl_write_host_to_device(devid, host_pixels, device_image, width, height, bpp) == CL_SUCCESS;
709}
710
711static gboolean _drawlayer_acquire_source_image(const int devid, const float *layer_pixels,
712 dt_pixel_cache_entry_t *resolved_entry,
713 const gboolean force_device_copy, const gboolean realtime_reuse,
714 const int source_w, const int source_h,
717{
718 if(IS_NULL_PTR(source) || IS_NULL_PTR(layer_pixels) || source_w <= 0 || source_h <= 0) return FALSE;
719 *source = (drawlayer_cl_image_handle_t){ 0 };
720
721 if(force_device_copy)
722 {
723 source->mem
724 = dt_opencl_copy_host_to_device(devid, (void *)layer_pixels, source_w, source_h, 4 * sizeof(float));
725 return !IS_NULL_PTR(source->mem);
726 }
727
728 /* Realtime redraws keep revisiting the same host-backed layer cache. Prefer a
729 * reusable device buffer first so the blend path can stay asynchronous instead
730 * of forcing a full `dt_opencl_finish()` before the base patch lock is released. */
731 if(realtime_reuse && resolved_entry)
732 {
733 const dt_iop_roi_t source_roi = { .width = source_w, .height = source_h };
734 gboolean reused_from_cache = FALSE;
735 source->mem = dt_dev_pixelpipe_cache_get_cl_buffer(devid, NULL, &source_roi, 4 * sizeof(float), NULL,
736 "drawlayer source", resolved_entry,
737 &reused_from_cache, NULL);
738 if(source->mem)
739 {
740 const dt_drawlayer_damaged_rect_t *dirty_rect
741 = (reused_from_cache && process) ? &process->cache_dirty_rect : NULL;
742 if(_drawlayer_sync_host_image_to_device(devid, source->mem, (void *)layer_pixels, source_w, source_h,
743 4 * sizeof(float), dirty_rect))
744 {
745 source->is_cached_device = TRUE;
747 return TRUE;
748 }
749
750 dt_dev_pixelpipe_cache_release_cl_buffer((void **)&source->mem, resolved_entry, NULL, FALSE);
751 }
752 }
753
755 (void *)layer_pixels, resolved_entry, devid, source_w, source_h,
756 4 * sizeof(float), CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, NULL);
757 if(source->mem)
758 {
759 source->is_pinned = TRUE;
760 return TRUE;
761 }
762
764 devid, source_w, source_h, 4 * sizeof(float), (void *)layer_pixels, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR);
765 if(source->mem)
766 {
767 if(_drawlayer_sync_host_image_to_device(devid, source->mem, (void *)layer_pixels, source_w, source_h,
768 4 * sizeof(float), NULL))
769 {
770 source->is_pinned = TRUE;
771 return TRUE;
772 }
773
775 source->mem = NULL;
776 }
777
778 source->mem = dt_opencl_copy_host_to_device(devid, (void *)layer_pixels, source_w, source_h, 4 * sizeof(float));
779 return !IS_NULL_PTR(source->mem);
780}
781
782static int _drawlayer_copy_or_resample_layer_roi(const int devid, cl_mem dev_source_rgba, cl_mem dev_layer_rgba,
783 const int source_w, const int source_h,
784 const dt_iop_roi_t *const target_roi,
785 const dt_iop_roi_t *const source_roi)
786{
787 const gboolean can_copy_crop
788 = (fabs(target_roi->scale - 1.0) <= 1e-6 && target_roi->x >= 0
789 && target_roi->y >= 0 && target_roi->x + target_roi->width <= source_w
790 && target_roi->y + target_roi->height <= source_h);
791 if(can_copy_crop)
792 {
793 size_t src_origin[3] = { (size_t)target_roi->x, (size_t)target_roi->y, 0 };
794 size_t dst_origin[3] = { 0, 0, 0 };
795 size_t region[3] = { (size_t)target_roi->width, (size_t)target_roi->height, 1 };
796 const int copy_err
797 = dt_opencl_enqueue_copy_image(devid, dev_source_rgba, dev_layer_rgba, src_origin, dst_origin, region);
798 if(copy_err == CL_SUCCESS) return CL_SUCCESS;
799 }
800
801 /* Force bilinear for the layer matte. The premultiplied-alpha brush stroke is
802 * stamped at full canvas resolution, so this resample only ever previews it at
803 * display scale (mostly downscaling). Bilinear is the only kernel here with no
804 * negative lobes: it cannot overshoot, so it never rings/halos at stroke edges
805 * nor pushes alpha out of [0,1] — unlike the user-pref default (Lanczos) or the
806 * Catmull-Rom "bicubic". On minification dt_interpolation_resample widens the
807 * tap support, so bilinear acts as a clean area filter (no aliasing). */
809 return dt_interpolation_resample_cl(itor, devid, dev_layer_rgba, target_roi, dev_source_rgba, source_roi);
810}
811
812static gboolean _drawlayer_acquire_layer_image(const int devid, dt_pixel_cache_entry_t *resolved_entry,
813 const gboolean realtime_reuse, const gboolean direct_copy,
814 cl_mem dev_source_rgba, const int source_w, const int source_h,
815 const dt_iop_roi_t *const target_roi,
816 const dt_iop_roi_t *const source_roi,
817 drawlayer_cl_image_handle_t *layer, int *err)
818{
819 if(IS_NULL_PTR(layer) || !err || IS_NULL_PTR(target_roi) || IS_NULL_PTR(source_roi)) return FALSE;
820 *layer = (drawlayer_cl_image_handle_t){ 0 };
821
822 if(direct_copy)
823 {
824 layer->mem = dev_source_rgba;
825 return TRUE;
826 }
827
828 if(realtime_reuse && resolved_entry)
829 {
830 layer->mem = dt_dev_pixelpipe_cache_get_cl_buffer(devid, NULL, target_roi, 4 * sizeof(float), NULL,
831 "drawlayer layer", resolved_entry, NULL,
832 dev_source_rgba);
833 layer->is_cached_device = (!IS_NULL_PTR(layer->mem));
834 }
835
836 if(IS_NULL_PTR(layer->mem))
837 {
838 layer->mem = dt_opencl_alloc_device(devid, target_roi->width, target_roi->height, 4 * sizeof(float));
839 }
840
841 if(IS_NULL_PTR(layer->mem))
842 {
843 *err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
844 return FALSE;
845 }
846
847 *err = _drawlayer_copy_or_resample_layer_roi(devid, dev_source_rgba, layer->mem, source_w, source_h, target_roi,
848 source_roi);
849 return *err == CL_SUCCESS;
850}
851
852static int _drawlayer_run_premult_over_kernel(const int devid, const int kernel_premult_over,
853 cl_mem dev_background, cl_mem dev_layer_rgba, cl_mem dev_out,
854 const int width, const int height, const int background_offset_x,
855 const int background_offset_y)
856{
857 const int offs[2] = { background_offset_x, background_offset_y };
858 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
859
860 int err = dt_opencl_set_kernel_arg(devid, kernel_premult_over, 0, sizeof(cl_mem), &dev_background);
861 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 1, sizeof(cl_mem), &dev_layer_rgba);
862 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 2, sizeof(cl_mem), &dev_out);
863 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 3, sizeof(int), &width);
864 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 4, sizeof(int), &height);
865 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 5, sizeof(offs), offs);
866 if(err != CL_SUCCESS) return err;
867
868 return dt_opencl_enqueue_kernel_2d(devid, kernel_premult_over, sizes);
869}
870
871/* Influence radius (in target/display pixels) of a changed source pixel through
872 * the resampling interpolation. interpolation.c maps a target output pixel to a
873 * source window whose half-width is ~support/scale source pixels; transformed
874 * back to target space that footprint is ~support pixels regardless of scale, so
875 * a fixed, generous target-space margin keeps partial-resample edges seamless. */
876#define DRAWLAYER_RESAMPLE_DAMAGE_MARGIN 8
877
878/* Map a layer-space (source) damage rectangle to the output (target) display
879 * window it affects, padded by the interpolation support so resampled edges stay
880 * seamless. Returns TRUE and fills *out (nw inclusive / se exclusive, clamped to
881 * the target ROI, in target-local coordinates) only when the mapped window is a
882 * strict sub-rect of the target worth a partial composite. */
884 const dt_iop_roi_t *const target_roi,
885 const dt_iop_roi_t *const source_roi,
887{
888 if(IS_NULL_PTR(src_damage) || !src_damage->valid || IS_NULL_PTR(target_roi) || IS_NULL_PTR(source_roi)
889 || IS_NULL_PTR(out))
890 return FALSE;
891 if(target_roi->width <= 0 || target_roi->height <= 0 || target_roi->scale <= 0.0f) return FALSE;
892
893 const double scale = target_roi->scale;
894 const int margin = DRAWLAYER_RESAMPLE_DAMAGE_MARGIN;
895 /* out = (source + source_roi.x) * scale - target_roi.x (inverse of the
896 * interpolation source mapping source = (target_roi.x + out)/scale - source_roi.x). */
897 const double sx0 = (double)src_damage->nw[0] + source_roi->x;
898 const double sy0 = (double)src_damage->nw[1] + source_roi->y;
899 const double sx1 = (double)src_damage->se[0] + source_roi->x;
900 const double sy1 = (double)src_damage->se[1] + source_roi->y;
901
902 int tx0 = (int)floor(sx0 * scale - target_roi->x) - margin;
903 int ty0 = (int)floor(sy0 * scale - target_roi->y) - margin;
904 int tx1 = (int)ceil(sx1 * scale - target_roi->x) + margin;
905 int ty1 = (int)ceil(sy1 * scale - target_roi->y) + margin;
906
907 tx0 = CLAMP(tx0, 0, target_roi->width);
908 ty0 = CLAMP(ty0, 0, target_roi->height);
909 tx1 = CLAMP(tx1, 0, target_roi->width);
910 ty1 = CLAMP(ty1, 0, target_roi->height);
911
912 if(tx1 <= tx0 || ty1 <= ty0) return FALSE;
913 /* Whole-frame damage is not worth the partial path's bookkeeping. */
914 if(tx0 == 0 && ty0 == 0 && tx1 == target_roi->width && ty1 == target_roi->height) return FALSE;
915
916 *out = (dt_drawlayer_damaged_rect_t){ .valid = TRUE, .nw = { tx0, ty0 }, .se = { tx1, ty1 } };
917 return TRUE;
918}
919
920static int _blend_layer_over_input_cl(const int devid, const int kernel_premult_over, cl_mem dev_out,
921 cl_mem dev_in, drawlayer_process_scratch_t *scratch,
922 const float *layer_pixels, dt_pixel_cache_entry_t *source_entry,
923 cl_mem source_mem_override, const int source_w, const int source_h,
925 const dt_iop_roi_t *const target_roi, const dt_iop_roi_t *const source_roi,
926 const gboolean direct_copy, const gboolean use_preview_bg,
927 const float preview_bg, const gboolean realtime_reuse,
928 const gboolean force_device_copy, const gboolean allow_partial)
929{
930 if(devid < 0 || IS_NULL_PTR(dev_out) || IS_NULL_PTR(dev_in) || IS_NULL_PTR(scratch) || (IS_NULL_PTR(layer_pixels) && !source_mem_override) || source_w <= 0
931 || source_h <= 0 || !target_roi || target_roi->width <= 0 || target_roi->height <= 0)
932 return FALSE;
933 if(kernel_premult_over < 0) return FALSE;
934
935 dt_pixel_cache_entry_t *resolved_entry = source_entry;
936 gboolean resolved_entry_ref = FALSE;
937 if(realtime_reuse && !resolved_entry)
938 {
939 resolved_entry
940 = dt_dev_pixelpipe_cache_ref_entry_for_host_ptr((void *)layer_pixels);
941 resolved_entry_ref = (!IS_NULL_PTR(resolved_entry));
942 }
943
944 /* Realtime partial composite: when the caller validated that dev_out still
945 * holds this node's previous full composite (same buffer, hash and geometry)
946 * and only the painted sub-rect of the layer changed, refresh just that window
947 * instead of re-resampling the whole display. The source-damage rectangle must
948 * be snapshotted here because _drawlayer_acquire_source_image() consumes (and
949 * resets) process->cache_dirty_rect while uploading the dirty source region. */
950 dt_drawlayer_damaged_rect_t target_damage = { 0 };
951 const gboolean partial = allow_partial && !use_preview_bg && !direct_copy && !source_mem_override && process
952 && _drawlayer_map_source_damage_to_target(&process->cache_dirty_rect, target_roi,
953 source_roi, &target_damage);
954
955 drawlayer_cl_image_handle_t source = { 0 };
956 drawlayer_cl_image_handle_t layer = { 0 };
957 cl_mem dev_layer_partial = NULL;
958 cl_mem dev_bg_partial = NULL;
959 cl_mem dev_out_partial = NULL;
960 cl_mem dev_background = NULL;
961 int err = CL_SUCCESS;
962 int result = FALSE;
963 if(source_mem_override)
964 source.mem = source_mem_override;
965 else if(!_drawlayer_acquire_source_image(devid, layer_pixels, resolved_entry, force_device_copy, realtime_reuse,
966 source_w, source_h, process, &source))
967 goto cleanup;
968
969 if(partial)
970 {
971 const int dw = target_damage.se[0] - target_damage.nw[0];
972 const int dh = target_damage.se[1] - target_damage.nw[1];
973 size_t win_origin[3] = { (size_t)target_damage.nw[0], (size_t)target_damage.nw[1], 0 };
974 size_t zero_origin[3] = { 0, 0, 0 };
975 size_t win_region[3] = { (size_t)dw, (size_t)dh, 1 };
976
977 /* All three windows are damage-sized device scratch buffers. We keep the
978 * unmodified full-frame `blendop_premult_over` kernel (local 0,0 coords) and
979 * stage the sub-window through copies so the GPU kernel ABI never changes:
980 * layer_partial = resample(source, damaged display window)
981 * bg_partial = dev_in[window] (background slice)
982 * out_partial = over(layer_partial, bg_partial) (full-window kernel)
983 * dev_out[window] = out_partial (refresh in place)
984 * The rest of dev_out keeps the previous full composite. */
985 dev_layer_partial = dt_opencl_alloc_device(devid, dw, dh, 4 * sizeof(float));
986 dev_bg_partial = dt_opencl_alloc_device(devid, dw, dh, 4 * sizeof(float));
987 dev_out_partial = dt_opencl_alloc_device(devid, dw, dh, 4 * sizeof(float));
988 if(IS_NULL_PTR(dev_layer_partial) || IS_NULL_PTR(dev_bg_partial) || IS_NULL_PTR(dev_out_partial))
989 {
990 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
991 goto cleanup;
992 }
993 /* Resample only the damaged display window from the full-res source. The
994 * sub-window's global target origin maps back to the correct source region
995 * through the unchanged interpolation source mapping. */
996 const dt_iop_roi_t sub_target_roi = {
997 .x = target_roi->x + target_damage.nw[0],
998 .y = target_roi->y + target_damage.nw[1],
999 .width = dw,
1000 .height = dh,
1001 .scale = target_roi->scale,
1002 };
1003 err = _drawlayer_copy_or_resample_layer_roi(devid, source.mem, dev_layer_partial, source_w, source_h,
1004 &sub_target_roi, source_roi);
1005 if(err != CL_SUCCESS) goto cleanup;
1006
1007 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_bg_partial, win_origin, zero_origin, win_region);
1008 if(err != CL_SUCCESS) goto cleanup;
1009
1010 err = _drawlayer_run_premult_over_kernel(devid, kernel_premult_over, dev_bg_partial, dev_layer_partial,
1011 dev_out_partial, dw, dh, 0, 0);
1012 if(err != CL_SUCCESS) goto cleanup;
1013
1014 err = dt_opencl_enqueue_copy_image(devid, dev_out_partial, dev_out, zero_origin, win_origin, win_region);
1015 if(err != CL_SUCCESS) goto cleanup;
1016
1018 dt_print(DT_DEBUG_PERF, "[drawlayer] partial composite window=%dx%d at (%d,%d) of %dx%d\n", dw, dh,
1019 target_damage.nw[0], target_damage.nw[1], target_roi->width, target_roi->height);
1020
1021 if(source.is_pinned)
1022 {
1023 if(!dt_opencl_finish(devid))
1024 {
1025 err = -1;
1026 goto cleanup;
1027 }
1028 }
1029 result = TRUE;
1030 goto cleanup;
1031 }
1032
1033 if(!_drawlayer_acquire_layer_image(devid, resolved_entry, realtime_reuse, direct_copy, source.mem, source_w,
1034 source_h, target_roi, source_roi, &layer, &err))
1035 goto cleanup;
1036
1037 if(use_preview_bg)
1038 {
1039 const size_t out_pixels = (size_t)target_roi->width * target_roi->height;
1041 &scratch->cl_background_rgba_pixels, out_pixels,
1042 "drawlayer process scratch");
1043 if(IS_NULL_PTR(background)) goto cleanup;
1044 __OMP_PARALLEL_FOR__(if(out_pixels > 4096))
1045 for(size_t kk = 0; kk < out_pixels; kk++)
1046 {
1047 float *pixel = background + 4 * kk;
1048 pixel[0] = preview_bg;
1049 pixel[1] = preview_bg;
1050 pixel[2] = preview_bg;
1051 pixel[3] = 1.0f;
1052 }
1054 background, NULL, devid, target_roi->width, target_roi->height,
1055 4 * sizeof(float), CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, NULL);
1056 if(IS_NULL_PTR(dev_background)) goto cleanup;
1057 }
1058 else
1059 dev_background = dev_in;
1060
1061 err = _drawlayer_run_premult_over_kernel(devid, kernel_premult_over, dev_background, layer.mem, dev_out,
1062 target_roi->width, target_roi->height, 0, 0);
1063 if(err != CL_SUCCESS) goto cleanup;
1064
1065 /* The realtime display source is the host-backed full-resolution cache.
1066 * When imported as CL_MEM_USE_HOST_PTR, queued GPU reads may still touch that
1067 * host memory after process_cl() returns. Finish the queue before releasing
1068 * the cache lock whenever the layer source is host-backed. */
1069 if(source.is_pinned)
1070 {
1071 if(!dt_opencl_finish(devid))
1072 {
1073 err = -1;
1074 goto cleanup;
1075 }
1076 }
1077
1078 result = TRUE;
1079
1080cleanup:
1081 if(dev_layer_partial) dt_opencl_release_mem_object(dev_layer_partial);
1082 if(dev_bg_partial) dt_opencl_release_mem_object(dev_bg_partial);
1083 if(dev_out_partial) dt_opencl_release_mem_object(dev_out_partial);
1084 if(use_preview_bg)
1086 (void **)&dev_background);
1087 if(layer.mem && layer.mem != source.mem)
1088 {
1089 if(layer.is_cached_device && resolved_entry)
1090 dt_dev_pixelpipe_cache_release_cl_buffer((void **)&layer.mem, resolved_entry, NULL, TRUE);
1091 else
1093 }
1094 if(!source_mem_override && source.is_pinned)
1095 dt_dev_pixelpipe_cache_put_pinned_image((void *)layer_pixels, resolved_entry,
1096 (void **)&source.mem);
1097 else if(!source_mem_override && source.is_cached_device && resolved_entry)
1098 dt_dev_pixelpipe_cache_release_cl_buffer((void **)&source.mem, resolved_entry, NULL, TRUE);
1099 else if(!source_mem_override && source.mem)
1101 if(resolved_entry_ref)
1103
1104 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[drawlayer] process_cl blend path failed: %d\n\n", err);
1105
1106 return result;
1107}
1108#endif
1109
1110static gboolean _profile_key_is_sane(const char *value)
1111{
1112 if(IS_NULL_PTR(value) || value[0] == '\0') return FALSE;
1113
1114 int separators = 0;
1115 for(const unsigned char *c = (const unsigned char *)value; *c; c++)
1116 {
1117 if(*c == '|')
1118 separators++;
1119 else if(!g_ascii_isprint(*c))
1120 return FALSE;
1121 }
1122
1123 return separators >= 2;
1124}
1125
1126static int64_t _sidecar_timestamp_from_path(const char *path)
1127{
1128 if(IS_NULL_PTR(path) || path[0] == '\0' || !g_file_test(path, G_FILE_TEST_EXISTS)) return 0;
1129
1130 GStatBuf st = { 0 };
1131 if(g_stat(path, &st) != 0) return 0;
1132 return (int64_t)st.st_mtime;
1133}
1134
1135static void _ensure_cursor_stamp_surface(dt_iop_module_t *self, const float widget_radius, const float opacity,
1136 const float hardness)
1137{
1139 if(IS_NULL_PTR(g) || widget_radius <= 0.0f) return;
1140
1141 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1142 float display_rgb[3] = { 0.0f };
1143 _conf_display_color(display_rgb);
1144 const int shape = _conf_brush_shape();
1145 const int size_px = MAX(2, (int)ceil((2.0f * widget_radius + 2.0f) * ppd));
1146
1147 const gboolean needs_rebuild
1148 = !g->ui.cursor_surface || g->ui.cursor_surface_size != size_px || fabs(g->ui.cursor_surface_ppd - ppd) > 1e-9
1149 || fabsf(g->ui.cursor_radius - widget_radius) > 1e-3f || fabsf(g->ui.cursor_opacity - opacity) > 1e-6f
1150 || fabsf(g->ui.cursor_hardness - hardness) > 1e-6f || g->ui.cursor_shape != shape
1151 || fabsf(g->ui.cursor_color[0] - display_rgb[0]) > 1e-6f || fabsf(g->ui.cursor_color[1] - display_rgb[1]) > 1e-6f
1152 || fabsf(g->ui.cursor_color[2] - display_rgb[2]) > 1e-6f;
1153 if(!needs_rebuild) return;
1154
1156 g->ui.cursor_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size_px, size_px);
1157 if(cairo_surface_status(g->ui.cursor_surface) != CAIRO_STATUS_SUCCESS)
1158 {
1160 return;
1161 }
1162 cairo_surface_set_device_scale(g->ui.cursor_surface, ppd, ppd);
1163
1164 unsigned char *data = cairo_image_surface_get_data(g->ui.cursor_surface);
1165 const int stride = cairo_image_surface_get_stride(g->ui.cursor_surface);
1166 memset(data, 0, (size_t)stride * size_px);
1167
1169 .radius = fmaxf(widget_radius * (float)ppd, 0.5f),
1170 .shape = shape,
1171 .hardness = hardness,
1172 .opacity = opacity,
1173 .display_color = { display_rgb[0], display_rgb[1], display_rgb[2] },
1174 };
1175
1176 const float half = 0.5f * (float)size_px;
1177 dt_drawlayer_brush_rasterize_dab_argb8(&dab, data, size_px, size_px, stride, half, half, 1.0f);
1178 cairo_surface_mark_dirty(g->ui.cursor_surface);
1179
1180 g->ui.cursor_surface_size = size_px;
1181 g->ui.cursor_surface_ppd = ppd;
1182 g->ui.cursor_radius = widget_radius;
1183 g->ui.cursor_opacity = opacity;
1184 g->ui.cursor_hardness = hardness;
1185 g->ui.cursor_shape = shape;
1186 g->ui.cursor_color[0] = display_rgb[0];
1187 g->ui.cursor_color[1] = display_rgb[1];
1188 g->ui.cursor_color[2] = display_rgb[2];
1189}
1190
1191static drawlayer_wait_dialog_t _show_drawlayer_wait_dialog(const char *title, const char *message)
1192{
1193 drawlayer_wait_dialog_t wait = { 0 };
1194 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui()) || IS_NULL_PTR(title) || !title[0] || IS_NULL_PTR(message) || !message[0]) return wait;
1195
1196 GtkWidget *dialog = gtk_dialog_new();
1198 if(main) gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main));
1199 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1200 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
1201 gtk_window_set_deletable(GTK_WINDOW(dialog), FALSE);
1202 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1203 gtk_window_set_title(GTK_WINDOW(dialog), title);
1204 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
1205
1206 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1207 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1208 GtkWidget *spinner = gtk_spinner_new();
1209 GtkWidget *label = gtk_label_new(message);
1210 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
1211 gtk_box_pack_start(GTK_BOX(box), spinner, FALSE, FALSE, 0);
1212 gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
1213 gtk_container_set_border_width(GTK_CONTAINER(box), DT_PIXEL_APPLY_DPI(12));
1214 gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
1215 gtk_spinner_start(GTK_SPINNER(spinner));
1216 gtk_widget_show_all(dialog);
1217 gtk_widget_show_now(dialog);
1218 gtk_window_present(GTK_WINDOW(dialog));
1219 GdkDisplay *display = gtk_widget_get_display(dialog);
1220 if(display) gdk_display_flush(display);
1221 /* `gui_focus()` / explicit sidecar save show this dialog and then immediately
1222 * enter synchronous worker-drain / TIFF-write code on the UI thread. Give GTK
1223 * a couple of non-blocking iterations so the modal maps and paints before that
1224 * blocking section starts, otherwise it may never become visible at all. */
1225 for(int k = 0; k < 2; k++)
1226 gtk_main_iteration_do(FALSE);
1227 wait.dialog = dialog;
1228 return wait;
1229}
1230
1236
1237static gboolean _drawlayer_modal_wait_tick(gpointer user_data)
1238{
1240 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->loop)) return G_SOURCE_REMOVE;
1241 if(!(state->g && dt_drawlayer_worker_any_active(state->g->stroke.worker)))
1242 {
1243 g_main_loop_quit(state->loop);
1244 return G_SOURCE_REMOVE;
1245 }
1246
1247 return G_SOURCE_CONTINUE;
1248}
1249
1251 const char *title, const char *message)
1252{
1253 if(!(g && dt_drawlayer_worker_any_active(g->stroke.worker))) return;
1254
1256 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
1258 .loop = loop,
1259 .g = g,
1260 };
1261 const guint source_id = g_timeout_add(16, _drawlayer_modal_wait_tick, &state);
1262
1263 if(g && dt_drawlayer_worker_any_active(g->stroke.worker))
1264 g_main_loop_run(loop);
1265
1266 if(source_id) g_source_remove(source_id);
1267 if(wait.dialog)
1268 {
1269 gtk_widget_destroy(wait.dialog);
1270 wait.dialog = NULL;
1271 }
1272 g_main_loop_unref(loop);
1273}
1274
1275static void _show_drawlayer_modal_message(const GtkMessageType type, const char *primary, const char *secondary)
1276{
1277 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui()) || IS_NULL_PTR(primary) || primary[0] == '\0') return;
1278
1279 GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(dt_gui_main_window()),
1280 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1281 type, GTK_BUTTONS_OK, "%s", primary);
1282 if(secondary && secondary[0] != '\0')
1283 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
1284 gtk_dialog_run(GTK_DIALOG(dialog));
1285 gtk_widget_destroy(dialog);
1286}
1287
1288static gboolean _prompt_layer_name_dialog(const char *title, const char *message, const char *initial_name,
1289 char *name, const size_t name_size)
1290{
1291 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui()) || IS_NULL_PTR(title) || title[0] == '\0' || IS_NULL_PTR(name) || name_size == 0) return FALSE;
1292
1293 GtkWidget *dialog = gtk_dialog_new_with_buttons(
1294 title, GTK_WINDOW(dt_gui_main_window()),
1295 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, _("Cancel"), GTK_RESPONSE_CANCEL,
1296 _("Confirm"), GTK_RESPONSE_ACCEPT, NULL);
1297 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1298 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1299 GtkWidget *entry = gtk_entry_new();
1300 if(!IS_NULL_PTR(message) && message[0] != '\0')
1301 {
1302 GtkWidget *label = gtk_label_new(message);
1303 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
1304 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1305 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
1306 }
1307 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
1308 if(initial_name && initial_name[0] != '\0') gtk_entry_set_text(GTK_ENTRY(entry), initial_name);
1309 gtk_box_pack_start(GTK_BOX(box), entry, FALSE, FALSE, 0);
1310 gtk_container_set_border_width(GTK_CONTAINER(box), DT_PIXEL_APPLY_DPI(12));
1311 gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
1312 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1313 gtk_widget_show_all(dialog);
1314
1315 gboolean accepted = FALSE;
1316 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1317 {
1318 _sanitize_requested_layer_name(gtk_entry_get_text(GTK_ENTRY(entry)), name, name_size);
1319 accepted = _layer_name_non_empty(name);
1320 }
1321
1322 gtk_widget_destroy(dialog);
1323 return accepted;
1324}
1325
1326static gboolean _color_picker_set_from_position(dt_iop_module_t *self, const float x, const float y)
1327{
1329 if(IS_NULL_PTR(g) || !g->ui.widgets || IS_NULL_PTR(g->controls.color)) return FALSE;
1330
1331 float display_rgb[3] = { 0.0f };
1332 if(!dt_drawlayer_widgets_update_from_picker_position(g->ui.widgets, g->controls.color, x, y, display_rgb)) return FALSE;
1333 _apply_display_brush_color(self, display_rgb, FALSE);
1334 return TRUE;
1335}
1336
1337static gboolean _color_picker_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1338{
1339 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1341 if(IS_NULL_PTR(g) || !g->ui.widgets) return FALSE;
1342 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1343 return dt_drawlayer_widgets_draw_picker(g->ui.widgets, widget, cr, ppd);
1344}
1345
1346static gboolean _color_swatch_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1347{
1348 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1350 if(IS_NULL_PTR(g) || !g->ui.widgets) return FALSE;
1351 return dt_drawlayer_widgets_draw_swatch(g->ui.widgets, widget, cr);
1352}
1353
1354static gboolean _color_swatch_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1355{
1356 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1358 if(IS_NULL_PTR(g) || !g->ui.widgets || !widget || IS_NULL_PTR(event) || event->button != 1) return FALSE;
1359
1360 float display_rgb[3] = { 0.0f };
1361 if(!dt_drawlayer_widgets_pick_history_color(g->ui.widgets, widget, event->x, event->y, display_rgb)) return FALSE;
1362 _apply_display_brush_color(self, display_rgb, FALSE);
1363 return TRUE;
1364}
1365
1367{
1369 if(IS_NULL_PTR(g) || !g->ui.widgets || IS_NULL_PTR(g->controls.brush_shape)) return;
1370
1374 gtk_widget_queue_draw(g->controls.brush_shape);
1375}
1376
1377static gboolean _brush_profile_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1378{
1379 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1381 if(IS_NULL_PTR(g) || !g->ui.widgets) return FALSE;
1382 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1383 return dt_drawlayer_widgets_draw_brush_profiles(g->ui.widgets, widget, cr, ppd);
1384}
1385
1386static gboolean _brush_profile_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1387{
1388 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1390 if(IS_NULL_PTR(g) || !g->ui.widgets || !widget || IS_NULL_PTR(event) || event->button != 1) return FALSE;
1391
1393 if(!dt_drawlayer_widgets_pick_brush_profile(g->ui.widgets, widget, event->x, event->y, &shape)) return FALSE;
1394
1399 gtk_widget_queue_draw(widget);
1400 return TRUE;
1401}
1402
1404 const float working_rgb[3], float display_rgb[3])
1405{
1406 if(IS_NULL_PTR(working_rgb) || !display_rgb) return FALSE;
1407
1408 const float gain = exp2f(_conf_hdr_exposure());
1409 const float inv_gain = (gain > 0.0f) ? 1.0f / gain : 1.0f;
1410 const float preview_rgb[3] = {
1411 working_rgb[0] * inv_gain,
1412 working_rgb[1] * inv_gain,
1413 working_rgb[2] * inv_gain,
1414 };
1415
1416 display_rgb[0] = _clamp01(preview_rgb[0]);
1417 display_rgb[1] = _clamp01(preview_rgb[1]);
1418 display_rgb[2] = _clamp01(preview_rgb[2]);
1419
1420 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe)) return TRUE;
1421
1422 const dt_iop_order_iccprofile_info_t *const work_profile
1423 = self->dev ? dt_ioppr_get_iop_work_profile_info(self, self->dev->iop) : NULL;
1424 const dt_iop_order_iccprofile_info_t *const display_profile = dt_ioppr_get_pipe_output_profile_info(pipe);
1425 if(IS_NULL_PTR(work_profile) || !display_profile) return TRUE;
1426
1427 float in[4] = { preview_rgb[0], preview_rgb[1], preview_rgb[2], 0.0f };
1428 float out[4] = { preview_rgb[0], preview_rgb[1], preview_rgb[2], 0.0f };
1429 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, work_profile, display_profile, "drawlayer picked color");
1430 display_rgb[0] = _clamp01(out[0]);
1431 display_rgb[1] = _clamp01(out[1]);
1432 display_rgb[2] = _clamp01(out[2]);
1433 return TRUE;
1434}
1435
1438{
1439 (void)picker;
1440 (void)piece;
1441 if(IS_NULL_PTR(self) || dt_gui_widgets_suppressed()) return;
1443 const float *picked = (source == DRAWLAYER_PICK_SOURCE_OUTPUT) ? self->picked_output_color : self->picked_color;
1444 const float *picked_min
1446 const float *picked_max
1448 if(picked_max[0] < picked_min[0]) return;
1449
1450 float display_rgb[3] = { 0.0f };
1451 if(!_working_rgb_to_display_rgb(self, pipe, picked, display_rgb)) return;
1452 _apply_display_brush_color(self, display_rgb, TRUE);
1453}
1454
1455static gboolean _color_picker_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1456{
1457 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1458 if(IS_NULL_PTR(event) || event->button != 1) return FALSE;
1459 (void)widget;
1460 return _color_picker_set_from_position(self, event->x, event->y);
1461}
1462
1463static gboolean _color_picker_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1464{
1465 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1467 (void)widget;
1468 (void)event;
1469 if(!IS_NULL_PTR(g) && g->ui.widgets)
1470 {
1471 float display_rgb[3] = { 0.0f };
1472 if(dt_drawlayer_widgets_finish_picker_drag(g->ui.widgets, display_rgb))
1473 _remember_display_color(self, display_rgb);
1474 }
1475 return FALSE;
1476}
1477
1478static gboolean _color_picker_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1479{
1480 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1482 (void)widget;
1483 if(IS_NULL_PTR(g) || !g->ui.widgets || IS_NULL_PTR(event) || !dt_drawlayer_widgets_is_picker_dragging(g->ui.widgets)) return FALSE;
1484 return _color_picker_set_from_position(self, event->x, event->y);
1485}
1486
1488{
1489 if(IS_NULL_PTR(params)) return;
1490
1491 if(memchr(params->layer_name, '\0', sizeof(params->layer_name)) == NULL)
1492 memset(params->layer_name, 0, sizeof(params->layer_name));
1493 else
1494 params->layer_name[sizeof(params->layer_name) - 1] = '\0';
1495
1496 char sanitized_layer_name[DRAWLAYER_NAME_SIZE] = { 0 };
1497 _sanitize_requested_layer_name(params->layer_name, sanitized_layer_name, sizeof(sanitized_layer_name));
1498 if(g_strcmp0(params->layer_name, sanitized_layer_name))
1499 g_strlcpy(params->layer_name, sanitized_layer_name, sizeof(params->layer_name));
1500
1501 if(memchr(params->work_profile, '\0', sizeof(params->work_profile)) == NULL)
1502 memset(params->work_profile, 0, sizeof(params->work_profile));
1503 else
1504 params->work_profile[sizeof(params->work_profile) - 1] = '\0';
1505
1506 if(!_layer_name_non_empty(params->layer_name))
1507 {
1508 params->layer_name[0] = '\0';
1509 params->layer_order = -1;
1510 params->sidecar_timestamp = 0;
1511 }
1512 if(params->layer_order < -1) params->layer_order = -1;
1513 if(params->work_profile[0] != '\0' && !_profile_key_is_sane(params->work_profile))
1514 memset(params->work_profile, 0, sizeof(params->work_profile));
1515
1516 // Freshly enabled or migrated instances without a concrete sidecar page should adopt the current profile.
1517 if(params->layer_order < 0 && params->stroke_commit_hash == 0u)
1518 memset(params->work_profile, 0, sizeof(params->work_profile));
1519
1520 if(params->work_profile[0] == '\0' && !IS_NULL_PTR(self) && self->dev)
1521 {
1522 char current_profile[DRAWLAYER_PROFILE_SIZE] = { 0 };
1523 if(_get_current_work_profile_key(self, self->dev->iop, self->dev->pipe, current_profile,
1524 sizeof(current_profile)))
1525 g_strlcpy(params->work_profile, current_profile, sizeof(params->work_profile));
1526 }
1527}
1528
1529static void _sanitize_requested_layer_name(const char *requested, char *name, const size_t name_size)
1530{
1531 if(IS_NULL_PTR(name) || name_size == 0) return;
1532 name[0] = '\0';
1533 if(!(!IS_NULL_PTR(requested) && requested[0])) return;
1534
1535 gboolean last_was_space = FALSE;
1536 size_t out = 0;
1537 for(size_t in = 0; requested[in] != '\0' && out + 1 < name_size; in++)
1538 {
1539 const unsigned char ch = (unsigned char)requested[in];
1540 if(g_ascii_isspace(ch))
1541 {
1542 if(out > 0 && !last_was_space)
1543 {
1544 name[out++] = ' ';
1545 last_was_space = TRUE;
1546 }
1547 continue;
1548 }
1549
1550 name[out++] = (char)ch;
1551 last_was_space = FALSE;
1552 }
1553 name[out] = '\0';
1554 g_strstrip(name);
1555}
1556
1557/* Realtime worker/queue implementation lives in its own implementation include. */
1558#include "drawlayer/worker.c"
1559
1560/* Layer cache and sidecar synchronization stay in a private implementation
1561 * include so drawlayer.c keeps the orchestration flow readable without
1562 * introducing a second public API boundary. */
1563#include "drawlayer/layers.c"
1564
1565// Find the cached working layer if any
1566static inline __attribute__((always_inline)) gboolean _update_runtime_state(const drawlayer_runtime_request_t *request,
1568{
1569 if(source) *source = (dt_drawlayer_runtime_source_t){ 0 };
1570 if(IS_NULL_PTR(request) || IS_NULL_PTR(request->self) || IS_NULL_PTR(request->piece) || IS_NULL_PTR(request->pipe) || IS_NULL_PTR(request->roi_out)
1571 || !request->runtime_params || !source)
1572 return FALSE;
1573
1574 const dt_dev_pixelpipe_t *const pipe = request->pipe;
1575 dt_drawlayer_runtime_manager_t *const manager = request->manager;
1577
1578 if(process && process->cache_valid && process->base_patch.pixels && process->cache_imgid == pipe->dev->image_storage.id)
1579 {
1580 dt_iop_roi_t process_roi = request->roi_out ? *request->roi_out : *request->roi_in;
1581 const dt_iop_roi_t source_full_roi = {
1582 .x = 0,
1583 .y = 0,
1584 .width = process->base_patch.width,
1585 .height = process->base_patch.height,
1586 .scale = 1.0f,
1587 };
1588
1589 /* The canvas has the raw image's dimensions and this module's frame is a window into it, at
1590 * the position the size fold gives that window (piece->buf_in.x/y -- a crop records itself
1591 * there). So the window this render reads is the requested output window shifted by that
1592 * origin -- and by nothing else. No scale: canvas and frame share the pixel pitch. No
1593 * rotation: the flip is already in the canvas's orientation.
1594 *
1595 * dt_interpolation_resample() reads source pixel ((x + i) / scale) for output column i, and
1596 * target_roi->x is in those same scaled units, which is why the offset is multiplied by the
1597 * render's own scale here and by nothing else.
1598 *
1599 * Before this the canvas WAS the module's frame and target_roi indexed it directly -- so the
1600 * canvas changed size with every crop and the authored raster was re-fitted onto the new
1601 * frame. The paint moved and stretched with the crop; that is the bug this fixes.
1602 *
1603 * KNOWN LIMIT: when the frame is LARGER than the canvas (a perspective correction expands it)
1604 * the offset goes negative and the resampler clamps to the canvas edge instead of reading
1605 * transparency, so paint touching the very edge of the canvas smears outward into the added
1606 * margin. Paint that does not reach the edge is unaffected. */
1608 if(dt_drawlayer_raw_placement_for_frame(request->self, pipe, &request->piece->buf_in, &placement))
1609 {
1610 process_roi.x += (int)lroundf((float)placement.offset_x * process_roi.scale);
1611 process_roi.y += (int)lroundf((float)placement.offset_y * process_roi.scale);
1612 }
1614 source->pixels = process->base_patch.pixels;
1615 source->cache_entry = process->base_patch.cache_entry;
1616 source->width = process->base_patch.width;
1617 source->height = process->base_patch.height;
1618 source->direct_copy = FALSE;
1619 source->source_roi = source_full_roi;
1620 source->target_roi = process_roi;
1622 if(manager)
1623 {
1627 source->tracked_read_lock = TRUE;
1629 TRUE);
1630 }
1631 return TRUE;
1632 }
1633
1634 return FALSE;
1635}
1636
1637static inline __attribute__((always_inline)) drawlayer_preview_background_t _resolve_preview_background(const dt_iop_module_t *self,
1638 const dt_iop_drawlayer_gui_data_t *gui)
1639{
1640 const int preview_mode = (gui && self && self->dev && self->dev->gui_module == self)
1644 .enabled = (preview_mode != DRAWLAYER_PREVIEW_BG_IMAGE),
1645 .value = (preview_mode == DRAWLAYER_PREVIEW_BG_WHITE) ? 1.0f
1646 : (preview_mode == DRAWLAYER_PREVIEW_BG_GREY) ? 0.5f
1647 : 0.0f,
1648 };
1649}
1650
1652 const gboolean have_last_dab, const float last_dab_x,
1653 const float last_dab_y, const uint32_t publish_serial)
1654{
1655 if(IS_NULL_PTR(params)) return;
1656
1657 uint32_t x_bits = 0u;
1658 uint32_t y_bits = 0u;
1659 if(have_last_dab)
1660 {
1661 memcpy(&x_bits, &last_dab_x, sizeof(x_bits));
1662 memcpy(&y_bits, &last_dab_y, sizeof(y_bits));
1663 }
1664
1665 const uint32_t seed[5] = { (uint32_t)dab_count, have_last_dab ? 1u : 0u, x_bits, y_bits, publish_serial };
1666
1667 uint64_t hash = params->stroke_commit_hash ? params->stroke_commit_hash : 5381u;
1668 hash = dt_hash(hash, (const char *)seed, sizeof(seed));
1669
1670 /* Keep the serialized field non-zero so "uninitialized" remains distinguishable
1671 * from "updated at least once" in legacy parameter blobs. */
1672 params->stroke_commit_hash = (uint32_t)(hash ? hash : 1u);
1673}
1674
1676{
1678 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
1679 GMainContext *const ui_ctx = g_main_context_default();
1680 if(IS_NULL_PTR(g) || IS_NULL_PTR(params) || !(ui_ctx && g_main_context_is_owner(ui_ctx))) return;
1681
1682 g->manager.background_job_running = g->session.background_job_running;
1683
1684 if(g->controls.layer_select) _populate_layer_list(self);
1686}
1687
1689{
1691 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
1692 if(IS_NULL_PTR(g) || IS_NULL_PTR(params)) return;
1693
1694 const gboolean attached = _layer_name_non_empty(params->layer_name);
1695 const gboolean missing = (g->session.missing_layer_error[0] != '\0');
1696 const gboolean have_existing_layers = g->controls.layer_select && dt_bauhaus_combobox_length(g->controls.layer_select) > 0;
1697
1698 if(g->controls.notebook)
1699 {
1700 if(g->controls.brush_tab) gtk_widget_set_visible(g->controls.brush_tab, attached);
1701 if(g->controls.input_tab) gtk_widget_set_visible(g->controls.input_tab, attached);
1702 if(g->controls.layer_tab) gtk_widget_set_visible(g->controls.layer_tab, TRUE);
1703 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(g->controls.notebook), attached);
1704 gtk_notebook_set_show_border(GTK_NOTEBOOK(g->controls.notebook), attached);
1705 if(!attached && g->controls.layer_tab)
1706 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->controls.notebook),
1707 gtk_notebook_page_num(GTK_NOTEBOOK(g->controls.notebook), g->controls.layer_tab));
1708 }
1709
1710 if(g->controls.preview_title) gtk_widget_set_visible(g->controls.preview_title, attached);
1711 if(g->controls.preview_box) gtk_widget_set_visible(g->controls.preview_box, attached);
1712 if(g->controls.layer_action_row) gtk_widget_set_visible(g->controls.layer_action_row, TRUE);
1713 if(g->controls.layer_status)
1714 {
1715 gtk_label_set_text(GTK_LABEL(g->controls.layer_status), missing ? g->session.missing_layer_error : "");
1716 gtk_widget_set_visible(g->controls.layer_status, missing);
1717 }
1718 if(g->controls.delete_layer) gtk_widget_set_visible(g->controls.delete_layer, attached);
1719 if(g->controls.rename_layer) gtk_widget_set_visible(g->controls.rename_layer, attached);
1720 if(g->controls.layer_fill_title) gtk_widget_set_visible(g->controls.layer_fill_title, attached);
1721 if(g->controls.layer_fill_row) gtk_widget_set_visible(g->controls.layer_fill_row, attached);
1722 if(g->controls.create_background) gtk_widget_set_visible(g->controls.create_background, attached);
1723 if(g->controls.save_layer) gtk_widget_set_visible(g->controls.save_layer, attached);
1724 if(g->controls.attach_layer) gtk_widget_set_visible(g->controls.attach_layer, !attached && have_existing_layers);
1725
1726 if(g->controls.layer_select) gtk_widget_set_visible(g->controls.layer_select, attached || have_existing_layers);
1727 if(g->controls.create_layer) gtk_widget_set_sensitive(g->controls.create_layer, TRUE);
1728 if(g->controls.rename_layer) gtk_widget_set_sensitive(g->controls.rename_layer, attached);
1729 if(g->controls.attach_layer) gtk_widget_set_sensitive(g->controls.attach_layer, have_existing_layers);
1730 if(g->controls.create_background)
1731 gtk_widget_set_sensitive(g->controls.create_background, attached && !g->session.background_job_running);
1732 if(g->controls.save_layer) gtk_widget_set_sensitive(g->controls.save_layer, attached);
1733}
1734
1736{
1738 if(IS_NULL_PTR(g)) return;
1739
1740 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_image))
1741 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_image),
1742 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_IMAGE);
1743 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_white))
1744 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_white),
1745 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_WHITE);
1746 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_grey))
1747 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_grey),
1748 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_GREY);
1749 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_black))
1750 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_black),
1751 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_BLACK);
1752}
1753
1754gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, const gboolean record_history)
1755{
1758 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return TRUE;
1759 /* A stroke is still physically in progress (button held down). Never finalize or
1760 * reset it here — that would truncate the live path. This must guard BOTH commit
1761 * kinds: a quiet flush (record_history == FALSE, scheduled by GUI_SCROLL /
1762 * GUI_SYNC_TEMP_BUFFERS while pending stroke work exists) would otherwise run
1763 * _wait_worker_idle + finalize + _reset_stroke_session mid-stroke without ever
1764 * clearing painting_active, silently cutting the stroke short. The realtime worker
1765 * already keeps base_patch updated incrementally, so there is nothing to flush
1766 * mid-stroke; defer a history commit to the worker and no-op a quiet flush. The
1767 * real commit runs on STROKE_END / focus-loss / image-change, where
1768 * _apply_runtime_event() has already cleared painting_active first. */
1769 if(g->manager.painting_active)
1770 {
1771 if(record_history) dt_drawlayer_worker_request_commit(g->stroke.worker);
1772 dt_print(DT_DEBUG_PERF, "[drawlayer] commit_dabs deferred: stroke in progress (record_history=%d)\n",
1773 record_history);
1774 return TRUE;
1775 }
1776
1777 _cancel_async_commit(g->stroke.worker);
1778
1779 if(!record_history)
1780 dt_drawlayer_worker_seal_for_commit(g->stroke.worker);
1781
1782 /* Commit ordering is strict:
1783 * - the backend worker must be idle,
1784 * - the full-resolution layer cache must already contain the stroke,
1785 * - only then do we mutate params/history so the pipeline invalidation sees a coherent state. */
1786 _wait_worker_idle(self, g->stroke.worker);
1787 /* Damage-rectangle ownership stays in drawlayer:
1788 * paint accumulates per-dab bounds into a stroke rectangle, and on commit the
1789 * module consumes that rectangle to update cache dirty state. */
1791
1792 int sample_count = 0;
1793 gboolean had_stroke = FALSE;
1794 gboolean had_last_dab = FALSE;
1795 float last_dab_x = 0.0f;
1796 float last_dab_y = 0.0f;
1798 g->stroke.finish_commit_pending = FALSE;
1799 sample_count = (int)g->stroke.stroke_sample_count;
1800 had_stroke = (sample_count > 0);
1801 had_last_dab = g->stroke.last_dab_valid;
1802 last_dab_x = g->stroke.last_dab_x;
1803 last_dab_y = g->stroke.last_dab_y;
1806 dt_drawlayer_worker_reset_stroke(g->stroke.worker);
1807
1808 if(had_stroke)
1809 {
1810 /* Keep stroke commit lightweight for interactive rendering:
1811 * - do not flush process tile back to base patch here,
1812 * - do not invalidate the process tile here.
1813 *
1814 * Base-patch synchronization is handled at explicit persistence points
1815 * (save/focus-out/mouse-leave/geometry rebuild) so recomputes can keep
1816 * blending the already-updated process tile directly. */
1817 _touch_stroke_commit_hash(params, sample_count, had_last_dab, last_dab_x, last_dab_y, 0u);
1819 if(record_history && self->dev)
1820 {
1827 if(self->post_history_commit) self->post_history_commit(self);
1828
1829 g->manager.realtime_active = FALSE;
1834 }
1835
1836 /* The final stroke-end raster batches may land after the button-release
1837 * event already requested one redraw. Queue one more redraw now that the
1838 * worker is idle and the last published process snapshot is coherent, so
1839 * late-finishing strokes become visible even without further UI activity. */
1840 if(self->dev && self->dev->gui_attached) dt_control_queue_redraw_center();
1841 }
1842
1843 if(!(had_stroke && record_history))
1844 {
1845 g->manager.realtime_active = FALSE;
1847 }
1848
1849 return TRUE;
1850}
1851
1852static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
1853{
1854 (void)instance;
1855 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1857 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return;
1858 drawlayer_runtime_host_context_t runtime_host = {
1859 .runtime = {
1860 .self = self,
1861 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
1862 .gui = g,
1863 .manager = &g->manager,
1864 .process_state = &g->process,
1865 },
1866 };
1867 const dt_drawlayer_runtime_host_t runtime_manager = {
1868 .user_data = &runtime_host,
1869 .collect_inputs = _drawlayer_runtime_collect_inputs,
1870 .perform_action = _drawlayer_runtime_perform_action,
1871 };
1873 &g->manager,
1875 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_PIPE_FINISHED,
1876 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
1877 },
1878 &runtime_manager);
1879}
1880
1882{
1884 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->controls.color) || IS_NULL_PTR(g->controls.softness)) return;
1885
1886 const gboolean paint_mode = (_conf_brush_mode() == DT_DRAWLAYER_BRUSH_MODE_PAINT);
1887 const gboolean show_hardness = (_conf_brush_shape() != DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN);
1888 gtk_widget_set_visible(GTK_WIDGET(g->controls.color), paint_mode);
1889 if(g->controls.color_row) gtk_widget_set_visible(g->controls.color_row, paint_mode);
1890 if(g->controls.color_swatch) gtk_widget_set_visible(g->controls.color_swatch, paint_mode);
1891 if(g->controls.image_colorpicker) gtk_widget_set_visible(g->controls.image_colorpicker, paint_mode);
1892 if(g->controls.image_colorpicker_source) gtk_widget_set_visible(g->controls.image_colorpicker_source, paint_mode);
1893 gtk_widget_set_visible(g->controls.softness, show_hardness);
1894}
1895
1897{
1898 if(IS_NULL_PTR(self->dev)) return FALSE;
1899
1902 if(!_layer_name_non_empty(params->layer_name)) return FALSE;
1903
1904 GString *errors = g_string_new(NULL);
1905 gboolean deleted = FALSE;
1906 int layer_width = 0;
1907 int layer_height = 0;
1908 if(!_resolve_layer_geometry(self, NULL, NULL, &layer_width, &layer_height, NULL, NULL))
1909 {
1911 layer_width = geometry.raw_width;
1912 layer_height = geometry.raw_height;
1913 }
1914
1915 char path[DT_PATH_MAX] = { 0 };
1916 if(!dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, path, sizeof(path)))
1917 {
1918 _layerio_append_error(errors, _("failed to resolve drawlayer sidecar path"));
1919 }
1920 else if(!g_file_test(path, G_FILE_TEST_EXISTS))
1921 {
1922 _layerio_append_error(errors, _("drawlayer sidecar TIFF is missing"));
1923 }
1924 else
1925 {
1926 if(!dt_drawlayer_io_delete_layer(path, params->layer_name, layer_width, layer_height))
1927 _layerio_append_error(errors, _("failed to delete drawing layer from sidecar"));
1928 else
1929 {
1930 deleted = TRUE;
1931 params->layer_name[0] = '\0';
1932 params->layer_order = -1;
1933 params->sidecar_timestamp = 0;
1934 memset(params->work_profile, 0, sizeof(params->work_profile));
1935 if(g) g->session.missing_layer_error[0] = '\0';
1936 if(!IS_NULL_PTR(g))
1937 {
1939 dt_drawlayer_cache_patch_clear(&g->process.base_patch, "drawlayer patch");
1940 g->process.cache_valid = FALSE;
1941 g->process.cache_dirty = FALSE;
1942 dt_drawlayer_paint_runtime_state_reset(&g->process.cache_dirty_rect);
1943 g->process.cache_imgid = -1;
1944 g->process.cache_layer_name[0] = '\0';
1945 g->process.cache_layer_order = -1;
1947 }
1949 }
1950 }
1951
1952 _layerio_log_errors(errors);
1953 g_string_free(errors, TRUE);
1954 return deleted;
1955}
1956
1957static gboolean _confirm_delete_layer(dt_iop_module_t *self, const gboolean removing_module)
1958{
1959 if(IS_NULL_PTR(self->dev)) return FALSE;
1960
1962 if(!_layer_name_non_empty(params->layer_name)) return removing_module;
1963
1964 GtkWidget *dialog = gtk_message_dialog_new(
1965 GTK_WINDOW(dt_gui_main_window()), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1966 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s",
1967 removing_module
1968 ? _("Delete the linked drawing layer from the sidecar TIFF before removing this module instance?")
1969 : _("Delete the linked drawing layer from the sidecar TIFF?"));
1970 if(removing_module)
1971 {
1972 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Keep layer"), GTK_RESPONSE_NO);
1973 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Delete layer"), GTK_RESPONSE_YES);
1974 }
1975 else
1976 {
1977 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
1978 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Delete"), GTK_RESPONSE_ACCEPT);
1979 }
1980
1981 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
1982 gtk_widget_destroy(dialog);
1983
1984 if(removing_module)
1985 {
1986 if(response == GTK_RESPONSE_YES) _delete_current_layer(self);
1987 /* Module removal must proceed regardless of the answer or any layer-delete failure. */
1988 return TRUE;
1989 }
1990
1991 if(response != GTK_RESPONSE_ACCEPT) return FALSE;
1992 return _delete_current_layer(self);
1993}
1994
1995static gboolean _rename_current_layer_from_gui(dt_iop_module_t *self, const char *requested_name)
1996{
1998 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
1999 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2000 if(!_layer_name_non_empty(params->layer_name)) return FALSE;
2001
2002 char new_name[DRAWLAYER_NAME_SIZE] = { 0 };
2003 char stripped_requested[DRAWLAYER_NAME_SIZE] = { 0 };
2004 if(!IS_NULL_PTR(requested_name)) g_strlcpy(stripped_requested, requested_name, sizeof(stripped_requested));
2005 g_strstrip(stripped_requested);
2006 if(stripped_requested[0] == '\0') return FALSE;
2007 _sanitize_requested_layer_name(requested_name, new_name, sizeof(new_name));
2008 if(new_name[0] == '\0') return FALSE;
2009
2010 if(!g_strcmp0(new_name, params->layer_name)) return TRUE;
2011
2012 GString *errors = g_string_new(NULL);
2013 gboolean renamed = FALSE;
2014 int layer_width = 0;
2015 int layer_height = 0;
2016 if(!_resolve_layer_geometry(self, NULL, NULL, &layer_width, &layer_height, NULL, NULL))
2017 {
2019 layer_width = geometry.raw_width;
2020 layer_height = geometry.raw_height;
2021 }
2022
2023 if(!_commit_dabs(self, FALSE))
2024 _layerio_append_error(errors, _("failed to commit drawing stroke before renaming"));
2025 else if(!_flush_layer_cache(self))
2026 _layerio_append_error(errors, _("failed to write drawing layer sidecar"));
2027 else
2028 {
2029 char path[DT_PATH_MAX] = { 0 };
2030 if(!dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, path, sizeof(path)))
2031 _layerio_append_error(errors, _("failed to resolve drawlayer sidecar path"));
2032 else if(!g_file_test(path, G_FILE_TEST_EXISTS))
2033 _layerio_append_error(errors, _("drawlayer sidecar TIFF is missing"));
2034 else
2035 {
2036 dt_drawlayer_io_layer_info_t info = { 0 };
2037 if(!dt_drawlayer_io_rename_layer(path, params->layer_name, new_name, params->work_profile,
2038 layer_width, layer_height, &info))
2039 _layerio_append_error(errors, _("failed to rename drawing layer in sidecar"));
2040 else
2041 {
2042 g_strlcpy(params->layer_name, new_name, sizeof(params->layer_name));
2043 params->layer_order = info.index;
2044 params->sidecar_timestamp = _sidecar_timestamp_from_path(path);
2045 g_strlcpy(g->process.cache_layer_name, params->layer_name, sizeof(g->process.cache_layer_name));
2046 g->process.cache_layer_order = params->layer_order;
2047 renamed = TRUE;
2048 }
2049 }
2050 }
2051
2052 if(renamed)
2053 {
2054 g->session.missing_layer_error[0] = '\0';
2056 if(self->dev) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2057 }
2058 else
2059 {
2061 }
2062
2063 _layerio_log_errors(errors);
2064 g_string_free(errors, TRUE);
2065 return renamed;
2066}
2067
2068static gboolean _create_new_layer(dt_iop_module_t *self, const char *requested_name)
2069{
2071 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2072 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2073
2074 char new_name[DRAWLAYER_NAME_SIZE] = { 0 };
2075 char stripped_requested[DRAWLAYER_NAME_SIZE] = { 0 };
2076 if(!IS_NULL_PTR(requested_name)) g_strlcpy(stripped_requested, requested_name, sizeof(stripped_requested));
2077 g_strstrip(stripped_requested);
2078 if(stripped_requested[0] == '\0') return FALSE;
2079 _sanitize_requested_layer_name(requested_name, new_name, sizeof(new_name));
2080 if(new_name[0] == '\0') return FALSE;
2081
2082 char path[DT_PATH_MAX] = { 0 };
2083 if(dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, path, sizeof(path))
2084 && dt_drawlayer_io_layer_name_exists(path, new_name, -1))
2085 {
2086 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2087 _("A drawing layer with that name already exists."),
2088 _("Choose a different layer name."));
2089 return FALSE;
2090 }
2091
2092 const dt_iop_drawlayer_params_t previous = *params;
2093
2094 if(!_commit_dabs(self, FALSE)) return FALSE;
2095 if(!_flush_layer_cache(self)) return FALSE;
2096
2097 g_strlcpy(params->layer_name, new_name, sizeof(params->layer_name));
2098 params->layer_order = -1;
2099 params->sidecar_timestamp = 0;
2100 memset(params->work_profile, 0, sizeof(params->work_profile));
2101
2103 {
2104 *params = previous;
2105 gui_update(self);
2106 return FALSE;
2107 }
2108
2109 /* Creating a layer must materialize it in the TIFF immediately so the
2110 * shared combobox only ever lists real on-disk layers and drawing can start
2111 * on the same authoritative cache line without a later bootstrap step. */
2112 if(!g->process.cache_valid || IS_NULL_PTR(g->process.base_patch.pixels))
2113 {
2114 *params = previous;
2115 gui_update(self);
2116 return FALSE;
2117 }
2118 g->process.cache_dirty = TRUE;
2119 if(!_flush_layer_cache(self))
2120 {
2121 *params = previous;
2122 gui_update(self);
2123 return FALSE;
2124 }
2125
2126 g->session.missing_layer_error[0] = '\0';
2127 _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2128 if(self->dev)
2129 {
2130 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2132 }
2134 gui_update(self);
2135 return TRUE;
2136}
2137
2138static void _build_pre_module_filter_string(dt_iop_module_t *self, char *filter, const size_t filter_size)
2139{
2140 if(IS_NULL_PTR(filter) || filter_size == 0) return;
2141 filter[0] = '\0';
2142 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(self->dev->pipe)) return;
2143
2144 dt_dev_pixelpipe_iop_t *self_piece = dt_dev_distort_get_iop_pipe(self->dev->pipe, self);
2145 const char *prev_op = NULL;
2146 if(self_piece && self->dev->pipe)
2147 {
2148 for(GList *nodes = self->dev->pipe->nodes; nodes; nodes = g_list_next(nodes))
2149 {
2151 if(piece == self_piece) break;
2152 if(piece->enabled && piece->module && piece->module->op[0]) prev_op = piece->module->op;
2153 }
2154 }
2155 if(!IS_NULL_PTR(prev_op) && prev_op[0]) g_snprintf(filter, filter_size, "pre:%s", prev_op);
2156}
2157
2158static gboolean _background_layer_job_done_idle(gpointer user_data)
2159{
2161 if(IS_NULL_PTR(result)) return G_SOURCE_REMOVE;
2162
2163 dt_control_log("%s", result->message[0] ? result->message : _("background layer job finished"));
2164
2166
2167 gboolean cleared_initiator = FALSE;
2168 if(dev && dev->image_storage.id == result->imgid)
2169 {
2170 for(GList *modules = dev->iop; modules; modules = g_list_next(modules))
2171 {
2172 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
2173 if(!module || g_strcmp0(module->op, "drawlayer")) continue;
2174
2176 dt_iop_drawlayer_params_t *params = (dt_iop_drawlayer_params_t *)module->params;
2177 if(g && params && !g_strcmp0(params->layer_name, result->initiator_layer_name)
2178 && params->layer_order == result->initiator_layer_order)
2179 {
2180 g->session.background_job_running = FALSE;
2181 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, TRUE);
2182 cleared_initiator = TRUE;
2183 }
2184
2185 if(result->success && params)
2186 {
2187 params->sidecar_timestamp = result->sidecar_timestamp;
2188 _refresh_layer_widgets(module);
2189 }
2190 }
2191 }
2192
2193 if(!cleared_initiator && dev)
2194 {
2195 for(GList *modules = dev->iop; modules; modules = g_list_next(modules))
2196 {
2197 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
2198 if(!module || g_strcmp0(module->op, "drawlayer")) continue;
2200 if(IS_NULL_PTR(g) || !g->session.background_job_running) continue;
2201 g->session.background_job_running = FALSE;
2202 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, TRUE);
2203 }
2204 }
2205
2206 dt_free(result);
2207 return G_SOURCE_REMOVE;
2208}
2209
2211{
2213 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2214 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2215 if(g->session.background_job_running) return FALSE;
2216 if(!_layer_name_non_empty(params->layer_name)) return FALSE;
2217
2218 if(!_commit_dabs(self, FALSE)) return FALSE;
2219 if(!_ensure_layer_cache(self)) return FALSE;
2220 if(!_flush_layer_cache(self)) return FALSE;
2221
2223 int layer_width = 0;
2224 int layer_height = 0;
2225 if(!_resolve_layer_geometry(self, self->dev->pipe, piece, &layer_width, &layer_height, NULL, NULL)) return FALSE;
2226
2227 char sidecar_path[DT_PATH_MAX] = { 0 };
2228 if(!dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, sidecar_path, sizeof(sidecar_path))) return FALSE;
2229
2230 drawlayer_dir_info_t current_info = { 0 };
2231 dt_drawlayer_io_layer_info_t io_info = { 0 };
2232 if(!dt_drawlayer_io_find_layer(sidecar_path, params->layer_name, params->layer_order, &io_info)) return FALSE;
2233 current_info.found = io_info.found;
2234 current_info.index = io_info.index;
2235 current_info.count = io_info.count;
2236 current_info.width = io_info.width;
2237 current_info.height = io_info.height;
2238 g_strlcpy(current_info.name, io_info.name, sizeof(current_info.name));
2239 g_strlcpy(current_info.work_profile, io_info.work_profile, sizeof(current_info.work_profile));
2240
2242 job_params->imgid = self->dev->image_storage.id;
2243 job_params->layer_width = layer_width;
2244 job_params->layer_height = layer_height;
2245 job_params->dst_x = 0;
2246 job_params->dst_y = 0;
2247 job_params->insert_after_order = current_info.index;
2248 g_strlcpy(job_params->sidecar_path, sidecar_path, sizeof(job_params->sidecar_path));
2249 g_strlcpy(job_params->work_profile, params->work_profile, sizeof(job_params->work_profile));
2250 g_snprintf(job_params->requested_bg_name, sizeof(job_params->requested_bg_name), "%s-bg", params->layer_name);
2251 g_strlcpy(job_params->initiator_layer_name, params->layer_name, sizeof(job_params->initiator_layer_name));
2252 job_params->initiator_layer_order = params->layer_order;
2254 _build_pre_module_filter_string(self, job_params->filter, sizeof(job_params->filter));
2255
2257 "drawlayer create background layer");
2258 if(IS_NULL_PTR(job))
2259 {
2260 dt_free(job_params);
2261 return FALSE;
2262 }
2263
2264 dt_control_job_set_params(job, job_params, g_free);
2265 dt_control_job_add_progress(job, _("creating background layer"), TRUE);
2266 g->session.background_job_running = TRUE;
2267 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, FALSE);
2269 {
2270 g->session.background_job_running = FALSE;
2271 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, TRUE);
2272 return FALSE;
2273 }
2274 return TRUE;
2275}
2276
2277static void _widget_changed(GtkWidget *widget, gpointer user_data)
2278{
2279 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2282
2284
2285 if(widget == g->controls.brush_mode || widget == g->controls.brush_shape) _sync_mode_sensitive_widgets(self);
2286
2287 if(widget == g->controls.size || widget == g->controls.softness || widget == g->controls.brush_shape)
2289
2290 if(widget == g->controls.hdr_exposure)
2291 {
2292 float display_rgb[3] = { 0.0f };
2293 if(dt_drawlayer_widgets_get_display_color(g->ui.widgets, display_rgb))
2294 _sync_cached_brush_colors(self, display_rgb);
2295 }
2296
2297 if(widget == g->controls.brush_shape || widget == g->controls.opacity || widget == g->controls.softness || widget == g->controls.sprinkles
2298 || widget == g->controls.sprinkle_size || widget == g->controls.sprinkle_coarseness)
2300}
2301
2304 dt_iop_drawlayer_params_t *params, const char *layer_name,
2305 const int layer_order)
2306{
2307 if(IS_NULL_PTR(self) || IS_NULL_PTR(g) || IS_NULL_PTR(params) || layer_order < 0 || !_layer_name_non_empty(layer_name)) return FALSE;
2308
2309 char previous_name[DRAWLAYER_NAME_SIZE] = { 0 };
2310 g_strlcpy(previous_name, params->layer_name, sizeof(previous_name));
2311 const int previous_order = params->layer_order;
2312
2313 g_strlcpy(params->layer_name, layer_name, sizeof(params->layer_name));
2314 params->layer_order = layer_order;
2316 {
2317 g_strlcpy(params->layer_name, previous_name, sizeof(params->layer_name));
2318 params->layer_order = previous_order;
2319 gui_update(self);
2320 return FALSE;
2321 }
2322
2323 g->session.missing_layer_error[0] = '\0';
2324 _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2325 if(self->dev)
2326 {
2327 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2329 }
2331 return TRUE;
2332}
2333
2334static void _layer_selected(GtkWidget *widget, gpointer user_data)
2335{
2336 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2340
2341 const int active = dt_bauhaus_combobox_get(widget);
2342 if(active < 0) return;
2343
2344 const char *text = dt_bauhaus_combobox_get_text(g->controls.layer_select);
2345 if(IS_NULL_PTR(text)) return;
2346
2347 if(!_layer_name_non_empty(params->layer_name))
2348 {
2350 return;
2351 }
2352 _apply_selected_layer_attachment(self, g, params, text, active);
2353}
2354
2355static void _attach_selected_layer_clicked(GtkButton *button, gpointer user_data)
2356{
2357 (void)button;
2358 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2360 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2361 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return;
2362 if(_layer_name_non_empty(params->layer_name)) return;
2363
2364 const int active = dt_bauhaus_combobox_get(g->controls.layer_select);
2365 if(active < 0) return;
2366
2367 const char *text = dt_bauhaus_combobox_get_text(g->controls.layer_select);
2368 if(!_layer_name_non_empty(text)) return;
2369 _apply_selected_layer_attachment(self, g, params, text, active);
2370}
2371
2372static void _rename_layer_clicked(GtkButton *button, gpointer user_data)
2373{
2374 (void)button;
2375 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2377 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2378 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return;
2379 if(!_layer_name_non_empty(params->layer_name)) return;
2380
2381 char requested_name[DRAWLAYER_NAME_SIZE] = { 0 };
2382 if(!_prompt_layer_name_dialog(_("Rename drawing layer"),
2383 _("Enter the new name for the current drawing layer."),
2384 params->layer_name, requested_name, sizeof(requested_name)))
2385 return;
2386
2387 if(!_rename_current_layer_from_gui(self, requested_name))
2388 dt_control_log(_("failed to rename drawing layer"));
2389}
2390
2391static void _delete_layer_clicked(GtkButton *button, gpointer user_data)
2392{
2393 (void)button;
2394 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2395 if(IS_NULL_PTR(self->dev)) return;
2396 if(!_commit_dabs(self, FALSE)) return;
2397 if(!_confirm_delete_layer(self, FALSE)) return;
2398
2400 _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2401 if(self->dev) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2403 gui_update(self);
2404}
2405
2406static gboolean _fill_current_layer(dt_iop_module_t *self, const float value)
2407{
2409 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2410 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2411
2412 if(!_commit_dabs(self, FALSE)) return FALSE;
2414 if(IS_NULL_PTR(g->process.base_patch.pixels)) return FALSE;
2415
2416 const size_t count = (size_t)g->process.base_patch.width * g->process.base_patch.height;
2417 float *const pixels = g->process.base_patch.pixels;
2418 const float gray = _clamp01(value);
2419 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
2420 __OMP_PARALLEL_FOR__( if(count > 4096))
2421 for(size_t k = 0; k < count; k++)
2422 {
2423 float *pixel = pixels + 4 * k;
2424 pixel[0] = gray;
2425 pixel[1] = gray;
2426 pixel[2] = gray;
2427 pixel[3] = 1.0f;
2428 }
2429 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
2430
2431 g->process.cache_dirty = TRUE;
2432 g->process.cache_dirty_rect.valid = TRUE;
2433 g->process.cache_dirty_rect.nw[0] = 0;
2434 g->process.cache_dirty_rect.nw[1] = 0;
2435 g->process.cache_dirty_rect.se[0] = g->process.base_patch.width;
2436 g->process.cache_dirty_rect.se[1] = g->process.base_patch.height;
2437 _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2439
2440 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2441 return TRUE;
2442}
2443
2445{
2447 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2448 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2449
2450 if(!_commit_dabs(self, FALSE)) return FALSE;
2452 if(IS_NULL_PTR(g->process.base_patch.pixels)) return FALSE;
2453
2454 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
2455 dt_drawlayer_cache_clear_transparent_float(g->process.base_patch.pixels,
2456 (size_t)g->process.base_patch.width * g->process.base_patch.height);
2457 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
2458
2459 g->process.cache_dirty = TRUE;
2460 g->process.cache_dirty_rect.valid = TRUE;
2461 g->process.cache_dirty_rect.nw[0] = 0;
2462 g->process.cache_dirty_rect.nw[1] = 0;
2463 g->process.cache_dirty_rect.se[0] = g->process.base_patch.width;
2464 g->process.cache_dirty_rect.se[1] = g->process.base_patch.height;
2465 _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2467
2468 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2469 return TRUE;
2470}
2471
2472static void _fill_white_clicked(GtkButton *button, gpointer user_data)
2473{
2474 (void)button;
2475 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2476 if(!_fill_current_layer(self, 1.0f)) dt_control_log(_("failed to fill drawing layer"));
2477}
2478
2479static void _fill_black_clicked(GtkButton *button, gpointer user_data)
2480{
2481 (void)button;
2482 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2483 if(!_fill_current_layer(self, 0.0f)) dt_control_log(_("failed to fill drawing layer"));
2484}
2485
2486static void _fill_transparent_clicked(GtkButton *button, gpointer user_data)
2487{
2488 (void)button;
2489 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2490 if(!_clear_current_layer(self)) dt_control_log(_("failed to clear drawing layer"));
2491}
2492
2493static void _save_layer_clicked(GtkButton *button, gpointer user_data)
2494{
2495 (void)button;
2496 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2498 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g)) return;
2499
2500 if(!g->process.cache_valid || IS_NULL_PTR(g->process.base_patch.pixels))
2501 {
2502 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2503 _("No drawing layer is loaded in memory."),
2504 _("The sidecar save was aborted."));
2505 return;
2506 }
2507 if(!_layer_name_non_empty(g->process.cache_layer_name))
2508 {
2509 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2510 _("Layer name is empty."),
2511 _("The sidecar save was aborted."));
2513 return;
2514 }
2515
2516 GtkWidget *dialog = gtk_message_dialog_new(
2517 GTK_WINDOW(dt_gui_main_window()), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
2518 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", _("Save the drawing sidecar now?"));
2519 gtk_message_dialog_format_secondary_text(
2520 GTK_MESSAGE_DIALOG(dialog), "%s",
2521 _("This writes the current in-memory drawing layer to the sidecar TIFF immediately."));
2522 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
2523 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Save"), GTK_RESPONSE_ACCEPT);
2524
2525 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
2526 gtk_widget_destroy(dialog);
2527 if(response != GTK_RESPONSE_ACCEPT)
2528 {
2529 return;
2530 }
2531
2532 _drawlayer_wait_for_rasterization_modal(g, _("Saving layer"),
2533 _("Waiting for the layer rasterization to finish..."));
2534
2535 /* Saving the sidecar is explicit persistence, not a history edit.
2536 * We still finalize any pending stroke first so the flush sees the latest
2537 * authoritative cache state, then write that cache to the TIFF immediately. */
2538 if(!_commit_dabs(self, FALSE))
2539 {
2540 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2541 _("Failed to finalize the drawing stroke."),
2542 _("The sidecar was not saved."));
2543 return;
2544 }
2545
2546 _rekey_shared_base_patch(&g->process.base_patch, self->dev->image_storage.id,
2547 (const dt_iop_drawlayer_params_t *)self->params);
2548
2549 if(!_flush_layer_cache(self))
2550 {
2551 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2552 _("Failed to write the drawing layer sidecar."),
2553 _("The sidecar TIFF could not be updated."));
2554 return;
2555 }
2556
2559
2560 _show_drawlayer_modal_message(GTK_MESSAGE_INFO,
2561 _("Drawing sidecar saved."),
2562 _("The current in-memory layer has been written to the sidecar TIFF."));
2563}
2564
2565static void _create_layer_clicked(GtkButton *button, gpointer user_data)
2566{
2567 (void)button;
2568 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2569 if(IS_NULL_PTR(self)) return;
2570
2571 char requested_name[DRAWLAYER_NAME_SIZE] = { 0 };
2572 if(!_prompt_layer_name_dialog(_("Create drawing layer"),
2573 _("Enter the name of the new drawing layer."),
2574 "", requested_name, sizeof(requested_name)))
2575 return;
2576
2577 if(!_create_new_layer(self, requested_name))
2578 dt_control_log(_("failed to create drawing layer"));
2579}
2580
2581static void _create_background_clicked(GtkButton *button, gpointer user_data)
2582{
2583 (void)button;
2584 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2585 if(!_create_background_layer_from_input(self)) dt_control_log(_("failed to create background layer from input"));
2586}
2587
2588static void _preview_bg_toggled(GtkToggleButton *button, gpointer user_data)
2589{
2590 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2592 dt_iop_drawlayer_params_t *params = self ? (dt_iop_drawlayer_params_t *)self->params : NULL;
2593 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || (dt_gui_get_global() && dt_gui_widgets_suppressed()) || !gtk_toggle_button_get_active(button))
2594 return;
2595
2596 if(GTK_WIDGET(button) == g->controls.preview_bg_white)
2597 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_WHITE;
2598 else if(GTK_WIDGET(button) == g->controls.preview_bg_grey)
2599 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_GREY;
2600 else if(GTK_WIDGET(button) == g->controls.preview_bg_black)
2601 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_BLACK;
2602 else
2603 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_IMAGE;
2604
2606 if(params) _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2607 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2609}
2610
2611static gboolean _build_raw_input_event(dt_iop_module_t *self, const double wx, const double wy, const double pressure,
2612 const dt_drawlayer_paint_stroke_pos_t stroke_pos,
2614{
2616 if(IS_NULL_PTR(g) || IS_NULL_PTR(input)) return FALSE;
2617
2618 dt_control_pointer_input_t pointer_input = { 0 };
2619 dt_control_get_pointer_input(&pointer_input);
2620 const float input_wx = isfinite(pointer_input.x) ? (float)pointer_input.x : (float)wx;
2621 const float input_wy = isfinite(pointer_input.y) ? (float)pointer_input.y : (float)wy;
2622 const float pressure_norm = pointer_input.has_pressure ? _clamp01(pointer_input.pressure) : _clamp01(pressure);
2624 .wx = input_wx,
2625 .wy = input_wy,
2626 .pressure = pressure_norm,
2627 .tilt = (float)_clamp01(pointer_input.tilt),
2628 .acceleration = (float)_clamp01(pointer_input.acceleration),
2629 .event_ts = g_get_monotonic_time(),
2630 .stroke_batch = g->stroke.current_stroke_batch,
2631 .event_index = ++g->stroke.stroke_event_index,
2632 .stroke_pos = stroke_pos,
2633 };
2634 _fill_input_layer_coords(self, input);
2635 _fill_input_brush_settings(self, input);
2636 return TRUE;
2637}
2638
2640{
2642 if(IS_NULL_PTR(self) || IS_NULL_PTR(g)) return;
2643
2644 uint32_t stroke_batch = g->stroke.current_stroke_batch + 1u;
2645 if(stroke_batch == 0u) stroke_batch++;
2646 const uint32_t event_index = first_input ? first_input->event_index : 0u;
2647 if(!IS_NULL_PTR(first_input) && first_input->stroke_batch != 0u) stroke_batch = first_input->stroke_batch;
2648
2650 g->session.pointer_valid = TRUE;
2651 g->stroke.current_stroke_batch = stroke_batch;
2652 if(!dt_drawlayer_worker_active(g->stroke.worker))
2654 g->stroke.finish_commit_pending = FALSE;
2655 g->stroke.stroke_sample_count = 0;
2656 g->stroke.stroke_event_index = event_index;
2657 g->stroke.last_dab_valid = FALSE;
2660}
2661
2667
2670 const dt_drawlayer_runtime_event_t event,
2671 const gboolean flush_pending)
2672{
2674 .ok = TRUE,
2675 .raw_input_ok = TRUE,
2676 };
2677 if(IS_NULL_PTR(self) || IS_NULL_PTR(g)) return result;
2678
2679 const drawlayer_runtime_host_context_t runtime_host = {
2680 .runtime = {
2681 .self = self,
2682 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
2683 .gui = g,
2684 .manager = &g->manager,
2685 .process_state = &g->process,
2686 },
2687 };
2688 const dt_drawlayer_runtime_host_t runtime_manager = {
2689 .user_data = (void *)&runtime_host,
2690 .collect_inputs = _drawlayer_runtime_collect_inputs,
2691 .perform_action = _drawlayer_runtime_perform_action,
2692 };
2694 &g->manager,
2696 .event = event,
2697 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
2698 .flush_pending = flush_pending,
2699 },
2700 &runtime_manager);
2701}
2702
2704 const dt_drawlayer_runtime_feedback_t feedback)
2705{
2706 if(IS_NULL_PTR(g)) return;
2707
2708 switch(feedback)
2709 {
2711 _drawlayer_wait_for_rasterization_modal(g, _("Finishing drawing"),
2712 _("Waiting for the drawing rasterization to finish..."));
2713 break;
2714
2716 _drawlayer_wait_for_rasterization_modal(g, _("Saving layer"),
2717 _("Waiting for the layer rasterization to finish..."));
2718 break;
2719
2721 default:
2722 break;
2723 }
2724}
2725
2727const char *name()
2728{
2729 return C_("modulename", "drawing");
2730}
2731
2733const char **description(struct dt_iop_module_t *self)
2734{
2735 return dt_iop_set_description(self, _("paint premultiplied RGB layers in a TIFF sidecar"), _("creative"),
2736 _("linear, RGB, scene-referred"), _("geometric, RGB"),
2737 _("linear, RGB, scene-referred"));
2738}
2739
2740#ifdef HAVE_OPENCL
2743{
2744 const int program = 3; // blendop.cl, from programs.conf
2745 dt_iop_drawlayer_global_data_t *gd = calloc(1, sizeof(*gd));
2746 if(IS_NULL_PTR(gd)) return;
2747 module->data = gd;
2748 gd->kernel_premult_over = -1;
2749
2750 /* Reuse the existing blendop OpenCL program and add one drawlayer-specific
2751 * kernel to handle premultiplied "over" directly. This avoids the costly
2752 * de-premultiply + mask split that the stock blend kernels would require. */
2753 gd->kernel_premult_over = dt_opencl_create_kernel(program, "blendop_premult_over");
2754}
2755
2758{
2760 if(IS_NULL_PTR(gd)) return;
2762 dt_free(gd);
2763 module->data = NULL;
2764}
2765#endif
2766
2769{
2770 return IOP_GROUP_EFFECTS;
2771}
2772
2775{
2777}
2778
2781{
2782 return IOP_CS_RGB;
2783}
2784
2787{
2788 module->params = calloc(1, sizeof(dt_iop_drawlayer_params_t));
2789 module->default_params = calloc(1, sizeof(dt_iop_drawlayer_params_t));
2790 module->params_size = sizeof(dt_iop_drawlayer_params_t);
2791
2792 if(module->params) ((dt_iop_drawlayer_params_t *)module->params)->layer_order = -1;
2793 if(module->default_params) ((dt_iop_drawlayer_params_t *)module->default_params)->layer_order = -1;
2794}
2795
2797{
2798 /* The aligned family, like every other module: the DEFAULT cleanup_pipe frees
2799 * piece->data with dt_free_align(), so a module that allocates any other way only stays
2800 * correct for as long as it also overrides cleanup_pipe. That is a standing trap on
2801 * Windows -- _aligned_malloc against g_free -- and invisible on Linux, where both end at
2802 * free(). */
2804 if(IS_NULL_PTR(piece->data)) return;
2805 piece->data_size = sizeof(dt_iop_drawlayer_params_t);
2807 data->params.layer_order = -1;
2811 &data->runtime_manager, &data->runtime_process,
2812 &data->runtime_display_pipe);
2813}
2814
2817{
2818 (void)self;
2819 (void)pipe;
2820 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->data)) return;
2824 dt_free_align(piece->data);
2825 piece->data_size = 0;
2826}
2827
2831{
2833 const gboolean display_pipe = self && self->dev && dt_iop_gui_data(self) && pipe
2834 && (pipe == self->dev->pipe || pipe == self->dev->preview_pipe);
2836 dt_drawlayer_runtime_manager_bind_piece(&data->headless_manager, &data->process, g ? &g->manager : NULL,
2837 g ? &g->process : NULL, display_pipe, &data->runtime_manager,
2838 &data->runtime_process, &data->runtime_display_pipe);
2839 memcpy(&data->params, params, sizeof(dt_iop_drawlayer_params_t));
2840 _sanitize_params(self, &data->params);
2841
2842 /* Every pipe now warms the same authoritative base-patch snapshot through
2843 * the pixelpipe cache during `commit_params()`. GUI pipes still keep their
2844 * own transformed ROI cache on top, but they attach to the same shared base
2845 * line as headless pipes instead of carrying a private sidecar mirror. */
2846 _refresh_piece_base_cache(self, data, &data->params, pipe, piece);
2847}
2848
2851{
2852 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return;
2853 if(!_commit_dabs(self, FALSE)) return;
2854 if(!_confirm_delete_layer(self, FALSE)) return;
2855
2857 params->layer_name[0] = '\0';
2858 params->layer_order = -1;
2859 params->sidecar_timestamp = 0;
2860 memset(params->work_profile, 0, sizeof(params->work_profile));
2861 _touch_stroke_commit_hash(params, 0, FALSE, 0.0f, 0.0f, 0u);
2863 if(!IS_NULL_PTR(g))
2864 {
2865 g->session.missing_layer_error[0] = '\0';
2866 drawlayer_runtime_host_context_t runtime_host = {
2867 .runtime = {
2868 .self = self,
2869 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
2870 .gui = g,
2871 .manager = &g->manager,
2872 .process_state = &g->process,
2873 },
2874 };
2875 const dt_drawlayer_runtime_host_t runtime_manager = {
2876 .user_data = &runtime_host,
2877 .collect_inputs = _drawlayer_runtime_collect_inputs,
2878 .perform_action = _drawlayer_runtime_perform_action,
2879 };
2881 &g->manager,
2883 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_RESYNC,
2884 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
2885 },
2886 &runtime_manager);
2887 }
2888
2891}
2892
2895{
2896 if(IS_NULL_PTR(self->dev)) return TRUE;
2897 if(!_commit_dabs(self, FALSE)) return FALSE;
2898 _flush_layer_cache(self);
2899 return _confirm_delete_layer(self, TRUE);
2900}
2901
2904{
2905 IOP_GUI_ALLOC(drawlayer);
2909 g->ui.widgets = dt_drawlayer_widgets_init();
2913 _sanitize_params(self, params);
2914
2915 dt_drawlayer_worker_init(self, &g->stroke.worker, &g->manager.painting_active,
2916 &g->stroke.finish_commit_pending, &g->stroke.stroke_sample_count,
2917 &g->stroke.current_stroke_batch);
2918 g->session.background_job_running = FALSE;
2919 g->session.last_view_x = 0.0f;
2920 g->session.last_view_y = 0.0f;
2921 g->session.last_view_scale = 1.0f;
2922 if(self->dev)
2923 {
2924 g->session.last_view_x = dt_dev_viewport_center_x(self->dev);
2925 g->session.last_view_y = dt_dev_viewport_center_y(self->dev);
2926 g->session.last_view_scale = dt_dev_viewport_scaling(self->dev);
2927 }
2928
2929 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2930 if(self->gui->reset_button) gtk_widget_hide(self->gui->reset_button);
2931
2932 GtkWidget *history_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2933 g->controls.save_layer = gtk_button_new_with_label(_("save sidecar"));
2934 gtk_box_pack_start(GTK_BOX(history_box), g->controls.save_layer, TRUE, TRUE, 0);
2935 gtk_box_pack_start(GTK_BOX(self->gui->widget), history_box, FALSE, FALSE, 0);
2936
2937 GtkWidget *notebook = gtk_notebook_new();
2938 g->controls.notebook = notebook;
2939 gtk_widget_set_hexpand(notebook, TRUE);
2940 gtk_box_pack_start(GTK_BOX(self->gui->widget), notebook, FALSE, FALSE, 0);
2941 // image_colorpicker lives on the "Brush" tab; reset it if still active once the
2942 // user switches away from that tab.
2943 dt_ui_notebook_set_picker_owner(GTK_NOTEBOOK(notebook), self);
2944
2945 GtkWidget *brush_tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2946 GtkWidget *layer_tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2947 GtkWidget *input_tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2948 g->controls.brush_tab = brush_tab;
2949 g->controls.layer_tab = layer_tab;
2950 g->controls.input_tab = input_tab;
2951
2952 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), brush_tab, gtk_label_new(_("Brush")));
2953 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), layer_tab, gtk_label_new(_("Layer")));
2954 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), input_tab, gtk_label_new(_("Input")));
2955 gtk_container_child_set(GTK_CONTAINER(notebook), brush_tab, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2956 gtk_container_child_set(GTK_CONTAINER(notebook), layer_tab, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2957 gtk_container_child_set(GTK_CONTAINER(notebook), input_tab, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
2958
2959 GtkWidget *preview_title = gtk_label_new(_("Background"));
2960 g->controls.preview_title = preview_title;
2961 gtk_widget_set_halign(preview_title, GTK_ALIGN_START);
2962 GtkWidget *preview_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2963 g->controls.preview_box = preview_box;
2964 GSList *preview_group = NULL;
2965 g->controls.preview_bg_image = gtk_radio_button_new_with_label(preview_group, _("image"));
2966 preview_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(g->controls.preview_bg_image));
2967 g->controls.preview_bg_white = gtk_radio_button_new_with_label(preview_group, _("white"));
2968 preview_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(g->controls.preview_bg_white));
2969 g->controls.preview_bg_grey = gtk_radio_button_new_with_label(preview_group, _("grey"));
2970 preview_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(g->controls.preview_bg_grey));
2971 g->controls.preview_bg_black = gtk_radio_button_new_with_label(preview_group, _("black"));
2972 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_image, TRUE, TRUE, 0);
2973 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_white, TRUE, TRUE, 0);
2974 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_grey, TRUE, TRUE, 0);
2975 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_black, TRUE, TRUE, 0);
2976 gtk_box_pack_start(GTK_BOX(layer_tab), preview_title, FALSE, FALSE, 0);
2977 gtk_box_pack_start(GTK_BOX(layer_tab), preview_box, FALSE, FALSE, 0);
2978
2979 g->controls.brush_mode = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
2980 dt_bauhaus_combobox_add(g->controls.brush_mode, _("paint"));
2981 dt_bauhaus_combobox_add(g->controls.brush_mode, _("erase"));
2982 dt_bauhaus_combobox_add(g->controls.brush_mode, _("blur"));
2983 dt_bauhaus_combobox_add(g->controls.brush_mode, _("smudge"));
2984 dt_bauhaus_widget_set_label(g->controls.brush_mode, _("Paint mode"));
2985 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.brush_mode, TRUE, TRUE, 0);
2986
2987 GtkWidget *color_title = gtk_label_new(_("Color"));
2988 gtk_label_set_xalign(GTK_LABEL(color_title), 0.0f);
2989 dt_gui_add_class(color_title, "dt_section_label");
2990 gtk_box_pack_start(GTK_BOX(brush_tab), color_title, TRUE, TRUE, 0);
2991 g->controls.color = gtk_drawing_area_new();
2992 gtk_widget_set_size_request(g->controls.color, -1, DT_DRAWLAYER_COLOR_PICKER_HEIGHT);
2993 gtk_widget_add_events(g->controls.color, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
2994 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.color, TRUE, TRUE, 0);
2995 g->controls.color_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2996 g->controls.color_swatch = gtk_drawing_area_new();
2997 gtk_widget_set_size_request(g->controls.color_swatch, -1, DT_PIXEL_APPLY_DPI(DT_DRAWLAYER_COLOR_HISTORY_HEIGHT));
2998 gtk_widget_add_events(g->controls.color_swatch, GDK_BUTTON_PRESS_MASK);
2999 gtk_box_pack_start(GTK_BOX(g->controls.color_row), g->controls.color_swatch, TRUE, TRUE, 0);
3000 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.color_row, TRUE, TRUE, 0);
3001 GtkWidget *picker_controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3002 g->controls.image_colorpicker = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_POINT_AREA, NULL, IOP_CS_NONE);
3003 g->controls.image_colorpicker_source = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
3004 dt_bauhaus_combobox_add(g->controls.image_colorpicker_source, _("input"));
3005 dt_bauhaus_combobox_add(g->controls.image_colorpicker_source, _("output"));
3006 dt_bauhaus_widget_set_label(g->controls.image_colorpicker_source, _("Pick from"));
3007 gtk_box_pack_start(GTK_BOX(picker_controls), g->controls.image_colorpicker, TRUE, TRUE, 0);
3008 gtk_box_pack_start(GTK_BOX(picker_controls), g->controls.image_colorpicker_source, TRUE, TRUE, 0);
3009 gtk_box_pack_start(GTK_BOX(brush_tab), picker_controls, TRUE, TRUE, 0);
3010
3011 g->controls.hdr_exposure
3012 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 4.0f, 0.1f, 0.0f, 2);
3013 dt_bauhaus_widget_set_label(g->controls.hdr_exposure, _("HDR exposure"));
3014 dt_bauhaus_slider_set_format(g->controls.hdr_exposure, _(" EV"));
3015 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.hdr_exposure, TRUE, TRUE, 0);
3016
3017 GtkWidget *geometry_title = gtk_label_new(_("Geometry"));
3018 gtk_label_set_xalign(GTK_LABEL(geometry_title), 0.0f);
3019 dt_gui_add_class(geometry_title, "dt_section_label");
3020 gtk_box_pack_start(GTK_BOX(brush_tab), geometry_title, TRUE, TRUE, 0);
3021 GtkWidget *brush_shape_title = gtk_label_new(_("Fall-off"));
3022 gtk_label_set_xalign(GTK_LABEL(brush_shape_title), 0.0f);
3023 gtk_box_pack_start(GTK_BOX(brush_tab), brush_shape_title, TRUE, TRUE, 0);
3024 g->controls.brush_shape = gtk_drawing_area_new();
3025 gtk_widget_set_size_request(g->controls.brush_shape, -1, DT_PIXEL_APPLY_DPI(72));
3026 gtk_widget_add_events(g->controls.brush_shape, GDK_BUTTON_PRESS_MASK);
3027 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.brush_shape, TRUE, TRUE, 0);
3028
3029 g->controls.size
3030 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 1.0f, 2048.0f, 1.0f, 64.0f, 0);
3031 dt_bauhaus_widget_set_label(g->controls.size, _("Size"));
3032 dt_bauhaus_slider_set_format(g->controls.size, _(" px"));
3033 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.size, TRUE, TRUE, 0);
3034 g->controls.distance
3035 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 0.0f, 2);
3036 dt_bauhaus_widget_set_label(g->controls.distance, _("Sampling distance"));
3037 dt_bauhaus_slider_set_format(g->controls.distance, "%");
3038 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.distance, TRUE, TRUE, 0);
3039 g->controls.smoothing
3040 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 0.0f, 2);
3041 dt_bauhaus_widget_set_label(g->controls.smoothing, _("Smoothing"));
3042 dt_bauhaus_slider_set_format(g->controls.smoothing, "%");
3043 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.smoothing, TRUE, TRUE, 0);
3044 g->controls.softness
3045 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 1.0f, 0.01f, 0.5f, 2);
3046 dt_bauhaus_widget_set_label(g->controls.softness, _("Hardness"));
3047 dt_bauhaus_slider_set_factor(g->controls.softness, 100.0f);
3048 dt_bauhaus_slider_set_format(g->controls.softness, "%");
3049 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.softness, TRUE, TRUE, 0);
3050
3051 GtkWidget *thickness_title = gtk_label_new(_("Thickness"));
3052 gtk_label_set_xalign(GTK_LABEL(thickness_title), 0.0f);
3053 dt_gui_add_class(thickness_title, "dt_section_label");
3054 gtk_box_pack_start(GTK_BOX(brush_tab), thickness_title, TRUE, TRUE, 0);
3055 g->controls.opacity
3056 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 100.0f, 2);
3057 dt_bauhaus_widget_set_label(g->controls.opacity, _("Opacity"));
3058 dt_bauhaus_slider_set_format(g->controls.opacity, "%");
3059 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.opacity, TRUE, TRUE, 0);
3060 g->controls.flow
3061 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 100.0f, 2);
3062 dt_bauhaus_widget_set_label(g->controls.flow, _("Flow"));
3063 dt_bauhaus_slider_set_format(g->controls.flow, "%");
3064 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.flow, TRUE, TRUE, 0);
3065
3066 GtkWidget *texture_title = gtk_label_new(_("Texture"));
3067 gtk_label_set_xalign(GTK_LABEL(texture_title), 0.0f);
3068 dt_gui_add_class(texture_title, "dt_section_label");
3069 gtk_box_pack_start(GTK_BOX(brush_tab), texture_title, TRUE, TRUE, 0);
3070 g->controls.sprinkles
3071 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 0.0f, 2);
3072 dt_bauhaus_widget_set_label(g->controls.sprinkles, _("Sprinkles"));
3073 dt_bauhaus_slider_set_format(g->controls.sprinkles, "%");
3074 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.sprinkles, TRUE, TRUE, 0);
3075 g->controls.sprinkle_size
3076 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 1.0f, 256.0f, 1.0f, 3.0f, 0);
3077 dt_bauhaus_widget_set_label(g->controls.sprinkle_size, _("Sprinkle size"));
3078 dt_bauhaus_slider_set_format(g->controls.sprinkle_size, _(" px"));
3079 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.sprinkle_size, TRUE, TRUE, 0);
3080 g->controls.sprinkle_coarseness
3081 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 50.0f, 2);
3082 dt_bauhaus_widget_set_label(g->controls.sprinkle_coarseness, _("Coarseness"));
3083 dt_bauhaus_slider_set_format(g->controls.sprinkle_coarseness, "%");
3084 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.sprinkle_coarseness, TRUE, TRUE, 0);
3085
3086 GtkWidget *layer_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3087 GtkWidget *layer_status = gtk_label_new("");
3088 g->controls.layer_status = layer_status;
3089 gtk_widget_set_halign(layer_status, GTK_ALIGN_START);
3090 gtk_label_set_xalign(GTK_LABEL(layer_status), 0.0f);
3091 gtk_label_set_line_wrap(GTK_LABEL(layer_status), TRUE);
3092 GtkWidget *layer_fill_title = gtk_label_new(_("Fill"));
3093 g->controls.layer_fill_title = layer_fill_title;
3094 gtk_widget_set_halign(layer_fill_title, GTK_ALIGN_START);
3095 GtkWidget *layer_action_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3096 g->controls.layer_action_row = layer_action_row;
3097 GtkWidget *layer_fill_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3098 g->controls.layer_fill_row = layer_fill_row;
3099 g->controls.layer_select = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
3100 dt_bauhaus_widget_set_label(g->controls.layer_select, _("Source layer"));
3101 g->controls.delete_layer = gtk_button_new_with_label(_("delete layer"));
3102 g->controls.create_layer = gtk_button_new_with_label(_("create new layer"));
3103 g->controls.rename_layer = gtk_button_new_with_label(_("rename layer"));
3104 g->controls.attach_layer = gtk_button_new_with_label(_("reuse selected layer"));
3105 g->controls.create_background = gtk_button_new_with_label(_("create background from input"));
3106 g->controls.fill_white = gtk_button_new_with_label(_("white"));
3107 g->controls.fill_black = gtk_button_new_with_label(_("black"));
3108 g->controls.fill_transparent = gtk_button_new_with_label(_("transparency"));
3109 gtk_box_pack_start(GTK_BOX(layer_box), layer_status, FALSE, FALSE, 0);
3110 gtk_box_pack_start(GTK_BOX(layer_box), g->controls.layer_select, FALSE, FALSE, 0);
3111 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.create_layer, TRUE, TRUE, 0);
3112 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.rename_layer, TRUE, TRUE, 0);
3113 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.attach_layer, TRUE, TRUE, 0);
3114 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.delete_layer, TRUE, TRUE, 0);
3115 gtk_box_pack_start(GTK_BOX(layer_box), layer_action_row, FALSE, FALSE, 0);
3116 gtk_box_pack_start(GTK_BOX(layer_box), layer_fill_title, FALSE, FALSE, 0);
3117 gtk_box_pack_start(GTK_BOX(layer_fill_row), g->controls.fill_white, TRUE, TRUE, 0);
3118 gtk_box_pack_start(GTK_BOX(layer_fill_row), g->controls.fill_black, TRUE, TRUE, 0);
3119 gtk_box_pack_start(GTK_BOX(layer_fill_row), g->controls.fill_transparent, TRUE, TRUE, 0);
3120 gtk_box_pack_start(GTK_BOX(layer_box), layer_fill_row, FALSE, FALSE, 0);
3121 gtk_box_pack_start(GTK_BOX(layer_box), g->controls.create_background, FALSE, FALSE, 0);
3122 gtk_box_pack_start(GTK_BOX(layer_tab), layer_box, FALSE, FALSE, 0);
3123
3124 GtkWidget *mapping_title = gtk_label_new(_("tablet mapping"));
3125 gtk_widget_set_halign(mapping_title, GTK_ALIGN_START);
3126 GtkWidget *grid = gtk_grid_new();
3127 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3128 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3129
3130 const char *labels[4] = { _("size"), _("opacity"), _("flow"), _("hardness") };
3131 const char *rows[3] = { _("pressure"), _("tilt"), _("acceleration") };
3132 GtkWidget **targets[3][4] = {
3133 { &g->controls.map_pressure_size, &g->controls.map_pressure_opacity, &g->controls.map_pressure_flow, &g->controls.map_pressure_softness },
3134 { &g->controls.map_tilt_size, &g->controls.map_tilt_opacity, &g->controls.map_tilt_flow, &g->controls.map_tilt_softness },
3135 { &g->controls.map_accel_size, &g->controls.map_accel_opacity, &g->controls.map_accel_flow, &g->controls.map_accel_softness },
3136 };
3137 GtkWidget **profiles[3] = { &g->controls.pressure_profile, &g->controls.tilt_profile, &g->controls.accel_profile };
3138
3139 for(int c = 0; c < 4; c++)
3140 {
3141 GtkWidget *label = gtk_label_new(labels[c]);
3142 gtk_label_set_angle(GTK_LABEL(label), 90.0);
3143 gtk_grid_attach(GTK_GRID(grid), label, c + 1, 0, 1, 1);
3144 }
3145 gtk_grid_attach(GTK_GRID(grid), gtk_label_new(_("profile")), 5, 0, 1, 1);
3146
3147 for(int r = 0; r < 3; r++)
3148 {
3149 gtk_grid_attach(GTK_GRID(grid), gtk_label_new(rows[r]), 0, r + 1, 1, 1);
3150 for(int c = 0; c < 4; c++)
3151 {
3152 *targets[r][c] = gtk_check_button_new();
3153 gtk_grid_attach(GTK_GRID(grid), *targets[r][c], c + 1, r + 1, 1, 1);
3154 }
3156 dt_bauhaus_combobox_add(*profiles[r], _("linear"));
3157 dt_bauhaus_combobox_add(*profiles[r], _("quadratic"));
3158 dt_bauhaus_combobox_add(*profiles[r], _("square root"));
3159 dt_bauhaus_combobox_add(*profiles[r], _("inverse linear"));
3160 dt_bauhaus_combobox_add(*profiles[r], _("inverse square root"));
3161 dt_bauhaus_combobox_add(*profiles[r], _("inverse quadratic"));
3162 gtk_grid_attach(GTK_GRID(grid), *profiles[r], 5, r + 1, 1, 1);
3163 }
3164
3165 gtk_box_pack_start(GTK_BOX(input_tab), mapping_title, FALSE, FALSE, 0);
3166 gtk_box_pack_start(GTK_BOX(input_tab), grid, FALSE, FALSE, 0);
3167
3168 g_signal_connect(g->controls.brush_shape, "draw", G_CALLBACK(_brush_profile_draw), self);
3169 g_signal_connect(g->controls.brush_shape, "button-press-event", G_CALLBACK(_brush_profile_button_press), self);
3170 g_signal_connect(G_OBJECT(g->controls.brush_mode), "value-changed", G_CALLBACK(_widget_changed), self);
3171 g_signal_connect(g->controls.color, "draw", G_CALLBACK(_color_picker_draw), self);
3172 g_signal_connect(g->controls.color_swatch, "draw", G_CALLBACK(_color_swatch_draw), self);
3173 g_signal_connect(g->controls.color_swatch, "button-press-event", G_CALLBACK(_color_swatch_button_press), self);
3174 g_signal_connect(g->controls.color, "button-press-event", G_CALLBACK(_color_picker_button_press), self);
3175 g_signal_connect(g->controls.color, "button-release-event", G_CALLBACK(_color_picker_button_release), self);
3176 g_signal_connect(g->controls.color, "motion-notify-event", G_CALLBACK(_color_picker_motion), self);
3177 g_signal_connect(G_OBJECT(g->controls.image_colorpicker_source), "value-changed", G_CALLBACK(_widget_changed), self);
3178 g_signal_connect(G_OBJECT(g->controls.size), "value-changed", G_CALLBACK(_widget_changed), self);
3179 g_signal_connect(G_OBJECT(g->controls.distance), "value-changed", G_CALLBACK(_widget_changed), self);
3180 g_signal_connect(G_OBJECT(g->controls.smoothing), "value-changed", G_CALLBACK(_widget_changed), self);
3181 g_signal_connect(G_OBJECT(g->controls.opacity), "value-changed", G_CALLBACK(_widget_changed), self);
3182 g_signal_connect(G_OBJECT(g->controls.flow), "value-changed", G_CALLBACK(_widget_changed), self);
3183 g_signal_connect(G_OBJECT(g->controls.sprinkles), "value-changed", G_CALLBACK(_widget_changed), self);
3184 g_signal_connect(G_OBJECT(g->controls.sprinkle_size), "value-changed", G_CALLBACK(_widget_changed), self);
3185 g_signal_connect(G_OBJECT(g->controls.sprinkle_coarseness), "value-changed", G_CALLBACK(_widget_changed), self);
3186 g_signal_connect(G_OBJECT(g->controls.softness), "value-changed", G_CALLBACK(_widget_changed), self);
3187 g_signal_connect(G_OBJECT(g->controls.hdr_exposure), "value-changed", G_CALLBACK(_widget_changed), self);
3188 g_signal_connect(G_OBJECT(g->controls.layer_select), "value-changed", G_CALLBACK(_layer_selected), self);
3189 g_signal_connect(g->controls.preview_bg_image, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3190 g_signal_connect(g->controls.preview_bg_white, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3191 g_signal_connect(g->controls.preview_bg_grey, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3192 g_signal_connect(g->controls.preview_bg_black, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3193 g_signal_connect(g->controls.create_layer, "clicked", G_CALLBACK(_create_layer_clicked), self);
3194 g_signal_connect(g->controls.rename_layer, "clicked", G_CALLBACK(_rename_layer_clicked), self);
3195 g_signal_connect(g->controls.attach_layer, "clicked", G_CALLBACK(_attach_selected_layer_clicked), self);
3196 g_signal_connect(g->controls.create_background, "clicked", G_CALLBACK(_create_background_clicked), self);
3197 g_signal_connect(g->controls.save_layer, "clicked", G_CALLBACK(_save_layer_clicked), self);
3198 g_signal_connect(g->controls.delete_layer, "clicked", G_CALLBACK(_delete_layer_clicked), self);
3199 g_signal_connect(g->controls.fill_white, "clicked", G_CALLBACK(_fill_white_clicked), self);
3200 g_signal_connect(g->controls.fill_black, "clicked", G_CALLBACK(_fill_black_clicked), self);
3201 g_signal_connect(g->controls.fill_transparent, "clicked", G_CALLBACK(_fill_transparent_clicked), self);
3202
3203 for(int r = 0; r < 3; r++)
3204 {
3205 for(int c = 0; c < 4; c++) g_signal_connect(*targets[r][c], "toggled", G_CALLBACK(_widget_changed), self);
3206 g_signal_connect(G_OBJECT(*profiles[r]), "value-changed", G_CALLBACK(_widget_changed), self);
3207 }
3208
3210 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
3211
3212 if(self->dev)
3213 {
3214 drawlayer_runtime_host_context_t runtime_host = {
3215 .runtime = {
3216 .self = self,
3217 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3218 .gui = g,
3219 .manager = &g->manager,
3220 .process_state = &g->process,
3221 },
3222 };
3223 const dt_drawlayer_runtime_host_t runtime_manager = {
3224 .user_data = &runtime_host,
3225 .collect_inputs = _drawlayer_runtime_collect_inputs,
3226 .perform_action = _drawlayer_runtime_perform_action,
3227 };
3229 &g->manager,
3231 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_RESYNC,
3232 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3233 },
3234 &runtime_manager);
3235 }
3236}
3237
3240{
3243 if(IS_NULL_PTR(g)) return;
3244
3245 _sanitize_params(self, params);
3246
3247 dt_bauhaus_combobox_set(g->controls.brush_mode, _conf_brush_mode());
3248 dt_bauhaus_slider_set(g->controls.size, _conf_size());
3249 dt_bauhaus_slider_set(g->controls.distance, _conf_distance());
3250 dt_bauhaus_slider_set(g->controls.smoothing, _conf_smoothing());
3251 dt_bauhaus_slider_set(g->controls.opacity, _conf_opacity());
3252 dt_bauhaus_slider_set(g->controls.flow, _conf_flow());
3253 dt_bauhaus_slider_set(g->controls.sprinkles, _conf_sprinkles());
3254 dt_bauhaus_slider_set(g->controls.sprinkle_size, _conf_sprinkle_size());
3255 dt_bauhaus_slider_set(g->controls.sprinkle_coarseness, _conf_sprinkle_coarseness());
3256 dt_bauhaus_slider_set(g->controls.softness, _conf_hardness());
3257 if(g->controls.image_colorpicker_source) dt_bauhaus_combobox_set(g->controls.image_colorpicker_source, _conf_pick_source());
3258 dt_bauhaus_slider_set(g->controls.hdr_exposure, _conf_hdr_exposure());
3259
3262 if(g->controls.color) gtk_widget_queue_draw(g->controls.color);
3263
3264 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_pressure_size))
3265 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_pressure_size),
3267 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_pressure_opacity))
3268 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_pressure_opacity),
3270 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_pressure_flow))
3271 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_pressure_flow),
3273 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_pressure_softness))
3274 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_pressure_softness),
3276
3277 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_tilt_size))
3278 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_tilt_size),
3280 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_tilt_opacity))
3281 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_tilt_opacity),
3283 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_tilt_flow))
3284 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_tilt_flow),
3286 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_tilt_softness))
3287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_tilt_softness),
3289
3290 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_accel_size))
3291 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_accel_size),
3293 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_accel_opacity))
3294 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_accel_opacity),
3296 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_accel_flow))
3297 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_accel_flow),
3299 if(GTK_IS_TOGGLE_BUTTON(g->controls.map_accel_softness))
3300 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.map_accel_softness),
3302
3303 if(g->controls.pressure_profile)
3305 if(g->controls.tilt_profile) dt_bauhaus_combobox_set(g->controls.tilt_profile, _conf_mapping_profile(DRAWLAYER_CONF_TILT_PROFILE));
3306 if(g->controls.accel_profile)
3308
3313
3314 if(self->dev)
3315 {
3316 drawlayer_runtime_host_context_t runtime_host = {
3317 .runtime = {
3318 .self = self,
3319 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3320 .gui = g,
3321 .manager = &g->manager,
3322 .process_state = &g->process,
3323 },
3324 };
3325 const dt_drawlayer_runtime_host_t runtime_manager = {
3326 .user_data = &runtime_host,
3327 .collect_inputs = _drawlayer_runtime_collect_inputs,
3328 .perform_action = _drawlayer_runtime_perform_action,
3329 };
3331 &g->manager,
3333 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_RESYNC,
3334 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3335 },
3336 &runtime_manager);
3337 }
3338}
3339
3342{
3343 if(dt_iop_gui_data(self))
3344 {
3346 g->session.missing_layer_error[0] = '\0';
3347 drawlayer_runtime_host_context_t runtime_host = {
3348 .runtime = {
3349 .self = self,
3350 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3351 .gui = g,
3352 .manager = &g->manager,
3353 .process_state = &g->process,
3354 },
3355 };
3356 const dt_drawlayer_runtime_host_t runtime_manager = {
3357 .user_data = &runtime_host,
3358 .collect_inputs = _drawlayer_runtime_collect_inputs,
3359 .perform_action = _drawlayer_runtime_perform_action,
3360 };
3363 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3364 };
3365 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3366 }
3367}
3368
3370void gui_focus(dt_iop_module_t *self, gboolean in)
3371{
3372 if(!in)
3373 {
3376 const int pending_samples = g ? (int)g->stroke.stroke_sample_count : 0;
3377 const gboolean had_pending_edits
3378 = (g && (g->process.cache_dirty || g->stroke.stroke_sample_count > 0));
3379 drawlayer_runtime_host_context_t runtime_host = {
3380 .runtime = {
3381 .self = self,
3382 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3383 .gui = g,
3384 .manager = &g->manager,
3385 .process_state = &g->process,
3386 },
3387 };
3388 const dt_drawlayer_runtime_host_t runtime_manager = {
3389 .user_data = &runtime_host,
3390 .collect_inputs = _drawlayer_runtime_collect_inputs,
3391 .perform_action = _drawlayer_runtime_perform_action,
3392 };
3395 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3396 };
3398 if(g) dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3399 if(had_pending_edits && params)
3400 _touch_stroke_commit_hash(params, pending_samples, g->stroke.last_dab_valid, g->stroke.last_dab_x,
3401 g->stroke.last_dab_y, 0u);
3402 if(had_pending_edits && self->dev && !IS_NULL_PTR(params))
3403 {
3410 if(self->post_history_commit) self->post_history_commit(self);
3413 }
3414 }
3415 else if(dt_iop_gui_data(self))
3416 {
3418 drawlayer_runtime_host_context_t runtime_host = {
3419 .runtime = {
3420 .self = self,
3421 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3422 .gui = g,
3423 .manager = &g->manager,
3424 .process_state = &g->process,
3425 },
3426 };
3427 const dt_drawlayer_runtime_host_t runtime_manager = {
3428 .user_data = &runtime_host,
3429 .collect_inputs = _drawlayer_runtime_collect_inputs,
3430 .perform_action = _drawlayer_runtime_perform_action,
3431 };
3434 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3435 };
3436 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3437 }
3438}
3439
3442{
3443 /* Darkroom is leaving and is about to tear down the pixelpipe nodes and history. The paint worker
3444 * runs stroke commits off the GUI thread, and a commit performs a full pipeline history resync
3445 * (dt_dev_pixelpipe_update_history_all) that reads/commits every pipe piece. If that runs while
3446 * leave() frees the nodes, it faults on freed piece->data. Stop and join the worker now, while the
3447 * pipe is still alive, so any in-flight commit completes against valid nodes and no further commit
3448 * can start during teardown. */
3450 if(IS_NULL_PTR(g)) return;
3451 dt_drawlayer_worker_stop(self, g->stroke.worker);
3452}
3453
3474
3485
3488{
3489 if(IS_NULL_PTR(state)) return;
3490
3491 const float pressure_norm
3492 = _clamp01((pointer_input && pointer_input->has_pressure) ? pointer_input->pressure : 1.0f);
3493 const float tilt_norm = _clamp01((pointer_input && pointer_input->has_tilt) ? pointer_input->tilt : 0.0f);
3494 const float accel_norm = _clamp01(pointer_input ? pointer_input->acceleration : 0.0f);
3498 const float pressure_coeff = _mapping_profile_value(pressure_profile, pressure_norm);
3499 const float tilt_coeff = _mapping_profile_value(tilt_profile, tilt_norm);
3500 const float accel_coeff = _mapping_profile_value(accel_profile, accel_norm);
3501
3502 float radius = _conf_size();
3503 float opacity = _conf_opacity() / 100.0f;
3504 float flow = _conf_flow() / 100.0f;
3505 float hardness = _conf_hardness();
3506
3507 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_SIZE)) radius *= pressure_coeff;
3508 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_OPACITY)) opacity *= pressure_coeff;
3509 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_FLOW)) flow *= pressure_coeff;
3510 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_SOFTNESS)) hardness *= pressure_coeff;
3511
3512 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_SIZE)) radius *= tilt_coeff;
3513 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_OPACITY)) opacity *= tilt_coeff;
3514 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_FLOW)) flow *= tilt_coeff;
3515 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_SOFTNESS)) hardness *= tilt_coeff;
3516
3517 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_SIZE)) radius *= accel_coeff;
3518 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_OPACITY)) opacity *= accel_coeff;
3519 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_FLOW)) flow *= accel_coeff;
3520 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_SOFTNESS)) hardness *= accel_coeff;
3521
3522 state->pressure = pressure_norm;
3523 state->tilt = tilt_norm;
3524 state->acceleration = accel_norm;
3525 state->radius = fmaxf(0.5f, radius);
3526 state->opacity = _clamp01(opacity);
3527 state->flow = _clamp01(flow);
3528 state->hardness = _clamp01(hardness);
3529}
3530
3531static void _draw_brush_hud(cairo_t *cr, const drawlayer_hud_brush_state_t *state)
3532{
3533 if(IS_NULL_PTR(cr) || IS_NULL_PTR(state)) return;
3534
3535 char lines[3][128] = { { 0 } };
3536 g_snprintf(lines[0], sizeof(lines[0]), _("size %.1f px hardness %.2f%%"), state->radius * 2.0f,
3537 state->hardness * 100.0f);
3538 g_snprintf(lines[1], sizeof(lines[1]), _("opacity %.2f%% flow %.2f%%"), state->opacity * 100.0f,
3539 state->flow * 100.0f);
3540 g_snprintf(lines[2], sizeof(lines[2]), _("pressure %.2f%% tilt %.2f%% acceleration %.2f%%"),
3541 state->pressure * 100.0f, state->tilt * 100.0f, state->acceleration * 100.0f);
3542
3543 const double pad = DT_PIXEL_APPLY_DPI(6.0);
3544 const double line_h = DT_PIXEL_APPLY_DPI(13.0);
3545 const double fs = DT_PIXEL_APPLY_DPI(12.0);
3546 double max_w = 0.0;
3547
3548 cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
3549 cairo_set_font_size(cr, fs);
3550 for(int i = 0; i < 3; i++)
3551 {
3552 cairo_text_extents_t ext = { 0 };
3553 cairo_text_extents(cr, lines[i], &ext);
3554 max_w = fmax(max_w, ext.x_advance);
3555 }
3556
3557 const double box_w = max_w + 2.0 * pad;
3558 const double box_h = 3.0 * line_h + 2.0 * pad;
3559 const double x = DT_PIXEL_APPLY_DPI(10.0);
3560 const double y = DT_PIXEL_APPLY_DPI(10.0);
3561
3562 cairo_save(cr);
3563 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.55);
3564 cairo_rectangle(cr, x, y, box_w, box_h);
3565 cairo_fill(cr);
3566
3567 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.92);
3568 for(int i = 0; i < 3; i++)
3569 {
3570 cairo_move_to(cr, x + pad, y + pad + (i + 1) * line_h - DT_PIXEL_APPLY_DPI(2.0));
3571 cairo_show_text(cr, lines[i]);
3572 }
3573 cairo_restore(cr);
3574}
3575
3577void gui_post_expose(dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx,
3578 int32_t pointery)
3579{
3581 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return;
3582
3583 cairo_save(cr);
3584 cairo_set_line_width(cr, 1.0);
3585 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.8);
3586
3587 if(g->session.pointer_valid)
3588 {
3589 dt_control_pointer_input_t pointer_input = { 0 };
3590 dt_control_get_pointer_input(&pointer_input);
3591 const float widget_x = isfinite(pointer_input.x)
3592 ? (float)pointer_input.x
3593 : ((pointerx >= 0 && pointery >= 0) ? (float)pointerx : -1.0f);
3594 const float widget_y = isfinite(pointer_input.y)
3595 ? (float)pointer_input.y
3596 : ((pointerx >= 0 && pointery >= 0) ? (float)pointery : -1.0f);
3597 if(widget_x < 0.0f || widget_y < 0.0f)
3598 {
3599 cairo_restore(cr);
3600 return;
3601 }
3602 drawlayer_hud_brush_state_t hud = { 0 };
3603 _compute_hud_brush_state(&pointer_input, &hud);
3604
3605 float radius = hud.radius;
3606 const int brush_mode = _conf_brush_mode();
3607 const gboolean show_paint_fill = (brush_mode == DT_DRAWLAYER_BRUSH_MODE_PAINT);
3608
3609 float draw_x = widget_x;
3610 float draw_y = widget_y;
3611 float lx = 0.0f;
3612 float ly = 0.0f;
3613 float widget_radius = radius * dt_dev_get_overlay_scale(self->dev);
3614 if(dt_drawlayer_widget_to_layer_coords(self, widget_x, widget_y, &lx, &ly))
3615 {
3617 .x = lx,
3618 .y = ly,
3619 .radius = radius,
3620 };
3621 if(!_layer_to_widget_coords(self, lx, ly, &draw_x, &draw_y))
3622 {
3623 draw_x = widget_x;
3624 draw_y = widget_y;
3625 }
3626 widget_radius = dt_drawlayer_widget_brush_radius(self, &dab, widget_radius);
3627 }
3628
3629 // Draw the brush mipmap
3630 const float draw_radius = fmaxf(0.5f, widget_radius);
3631 if(show_paint_fill)
3632 {
3633 _ensure_cursor_stamp_surface(self, draw_radius, hud.opacity, hud.hardness);
3634 if(g->ui.cursor_surface)
3635 {
3636 const float surface_half_extent = 0.5f * (float)g->ui.cursor_surface_size / (float)g->ui.cursor_surface_ppd;
3637 cairo_set_source_surface(cr, g->ui.cursor_surface, draw_x - surface_half_extent, draw_y - surface_half_extent);
3638 cairo_paint(cr);
3639 }
3640 }
3641
3642 // Draw the outer circle in case we are lost
3643 cairo_set_source_rgba(cr, 0., 0., 0., 0.5);
3644 cairo_set_line_width(cr, 2.5);
3645 cairo_arc(cr, draw_x, draw_y, draw_radius + 1.0f, 0.0, 2.0 * M_PI);
3646 cairo_stroke(cr);
3647
3648 cairo_set_source_rgba(cr, 1., 1., 1., 0.5);
3649 cairo_set_line_width(cr, 1.0);
3650 cairo_arc(cr, draw_x, draw_y, draw_radius, 0.0, 2.0 * M_PI);
3651 cairo_stroke(cr);
3652
3653 if(self->dev->gui_module == self)
3654 {
3655 _draw_brush_hud(cr, &hud);
3656 }
3657 }
3658
3659 cairo_restore(cr);
3660}
3661
3664{
3666 if(IS_NULL_PTR(g)) return 0;
3667 drawlayer_runtime_host_context_t runtime_host = {
3668 .runtime = {
3669 .self = self,
3670 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3671 .gui = g,
3672 .manager = &g->manager,
3673 .process_state = &g->process,
3674 },
3675 };
3676 const dt_drawlayer_runtime_host_t runtime_manager = {
3677 .user_data = &runtime_host,
3678 .collect_inputs = _drawlayer_runtime_collect_inputs,
3679 .perform_action = _drawlayer_runtime_perform_action,
3680 };
3683 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3684 };
3685 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3686 return 0;
3687}
3688
3690int mouse_moved(dt_iop_module_t *self, double x, double y, double pressure, int which)
3691{
3693 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return 0;
3694
3696 {
3697 /* When the standard image color picker is active, drawlayer must stop
3698 * capturing the pointer entirely so darkroom can drive the picker overlay
3699 * and sampling path without competing cursor state from the brush tool. */
3700 drawlayer_runtime_host_context_t runtime_host = {
3701 .runtime = {
3702 .self = self,
3703 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3704 .gui = g,
3705 .manager = &g->manager,
3706 .process_state = &g->process,
3707 },
3708 };
3709 const dt_drawlayer_runtime_host_t runtime_manager = {
3710 .user_data = &runtime_host,
3711 .collect_inputs = _drawlayer_runtime_collect_inputs,
3712 .perform_action = _drawlayer_runtime_perform_action,
3713 };
3714 const dt_drawlayer_runtime_update_request_t leave_update = {
3716 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3717 };
3720 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3721 };
3722 if(g->session.pointer_valid) dt_drawlayer_runtime_manager_update(&g->manager, &leave_update, &runtime_manager);
3723 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3724 return 0;
3725 }
3726
3727 if(!g->session.pointer_valid)
3728 {
3730 .runtime = {
3731 .self = self,
3732 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3733 .gui = g,
3734 .manager = &g->manager,
3735 .process_state = &g->process,
3736 },
3737 };
3738 const dt_drawlayer_runtime_host_t enter_manager = {
3739 .user_data = &enter_host,
3740 .collect_inputs = _drawlayer_runtime_collect_inputs,
3741 .perform_action = _drawlayer_runtime_perform_action,
3742 };
3744 &g->manager,
3746 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_MOUSE_ENTER,
3747 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3748 },
3749 &enter_manager);
3750 }
3751
3752 if(g->manager.painting_active)
3753 {
3754 dt_drawlayer_paint_raw_input_t input = { 0 };
3755 drawlayer_runtime_host_context_t runtime_host = {
3756 .runtime = {
3757 .self = self,
3758 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3759 .gui = g,
3760 .manager = &g->manager,
3761 .process_state = &g->process,
3762 },
3763 .raw_input = &input,
3764 };
3765 const dt_drawlayer_runtime_host_t runtime_manager = {
3766 .user_data = &runtime_host,
3767 .collect_inputs = _drawlayer_runtime_collect_inputs,
3768 .perform_action = _drawlayer_runtime_perform_action,
3769 };
3772 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_SAMPLE,
3773 };
3774 const dt_drawlayer_runtime_result_t dispatch
3775 = _build_raw_input_event(self, x, y, pressure, DT_DRAWLAYER_PAINT_STROKE_MIDDLE, &input)
3776 ? dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager)
3777 : (dt_drawlayer_runtime_result_t){ .ok = FALSE, .raw_input_ok = FALSE };
3778 if(!dispatch.ok || !dispatch.raw_input_ok)
3779 {
3780 /* Queue overflow or enqueue failure aborts the current stroke so GUI and
3781 * worker stay in sync on stroke boundaries. */
3782 dt_print(DT_DEBUG_PERF, "[drawlayer] stroke abort from mouse_moved: dispatch.ok=%d raw_input_ok=%d\n",
3783 dispatch.ok, dispatch.raw_input_ok);
3785 &g->manager,
3787 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_STROKE_ABORT,
3788 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3789 },
3790 &runtime_manager);
3791 }
3792 }
3793 else
3794 {
3795 drawlayer_runtime_host_context_t runtime_host = {
3796 .runtime = {
3797 .self = self,
3798 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3799 .gui = g,
3800 .manager = &g->manager,
3801 .process_state = &g->process,
3802 },
3803 };
3804 const dt_drawlayer_runtime_host_t runtime_manager = {
3805 .user_data = &runtime_host,
3806 .collect_inputs = _drawlayer_runtime_collect_inputs,
3807 .perform_action = _drawlayer_runtime_perform_action,
3808 };
3811 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_SAMPLE,
3812 };
3813 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3814 }
3815
3816 gtk_widget_queue_draw(dt_gui_center_widget());
3817 return g->manager.painting_active ? 1 : 0;
3818}
3819
3821int button_pressed(dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
3822{
3825 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev) || which != 1) return 0;
3826 if(!_layer_name_non_empty(params ? params->layer_name : NULL)) return 0;
3827
3828 if(self->dev->gui_module != self)
3829 {
3831 return 1;
3832 }
3833
3834 dt_control_pointer_input_t pointer_input = { 0 };
3835 dt_control_get_pointer_input(&pointer_input);
3836 const float input_wx = isfinite(pointer_input.x) ? (float)pointer_input.x : (float)x;
3837 const float input_wy = isfinite(pointer_input.y) ? (float)pointer_input.y : (float)y;
3838 const float pressure_norm = pointer_input.has_pressure ? _clamp01(pointer_input.pressure) : _clamp01(pressure);
3839 uint32_t stroke_batch = g->stroke.current_stroke_batch + 1u;
3840 if(stroke_batch == 0u) stroke_batch++;
3842 .wx = input_wx,
3843 .wy = input_wy,
3844 .pressure = pressure_norm,
3845 .tilt = (float)_clamp01(pointer_input.tilt),
3846 .acceleration = (float)_clamp01(pointer_input.acceleration),
3847 .event_ts = g_get_monotonic_time(),
3848 .stroke_batch = stroke_batch,
3849 .event_index = 1u,
3850 .stroke_pos = DT_DRAWLAYER_PAINT_STROKE_FIRST,
3851 };
3852 _fill_input_layer_coords(self, &first);
3853 _fill_input_brush_settings(self, &first);
3854 drawlayer_runtime_host_context_t runtime_host = {
3855 .runtime = {
3856 .self = self,
3857 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3858 .gui = g,
3859 .manager = &g->manager,
3860 .process_state = &g->process,
3861 },
3862 .raw_input = &first,
3863 };
3864 const dt_drawlayer_runtime_host_t runtime_manager = {
3865 .user_data = &runtime_host,
3866 .collect_inputs = _drawlayer_runtime_collect_inputs,
3867 .perform_action = _drawlayer_runtime_perform_action,
3868 };
3872 };
3873 const dt_drawlayer_runtime_result_t dispatch
3874 = dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3875 if(!dispatch.ok)
3876 return 0;
3877 if(!dispatch.raw_input_ok)
3878 dt_control_log(_("failed to queue live drawing stroke"));
3881 return 1;
3882}
3883
3885int button_released(dt_iop_module_t *self, double x, double y, int which, uint32_t state)
3886{
3888 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev) || which != 1) return 0;
3889
3890 if(g->manager.painting_active)
3891 {
3892 dt_control_pointer_input_t pointer_input = { 0 };
3893 dt_control_get_pointer_input(&pointer_input);
3894 const float input_wx = isfinite(pointer_input.x) ? (float)pointer_input.x : (float)x;
3895 const float input_wy = isfinite(pointer_input.y) ? (float)pointer_input.y : (float)y;
3897 .wx = input_wx,
3898 .wy = input_wy,
3899 .pressure = pointer_input.has_pressure ? _clamp01(pointer_input.pressure) : 1.0f,
3900 .tilt = (float)_clamp01(pointer_input.tilt),
3901 .acceleration = (float)_clamp01(pointer_input.acceleration),
3902 .event_ts = g_get_monotonic_time(),
3903 .stroke_batch = g->stroke.current_stroke_batch,
3904 .event_index = ++g->stroke.stroke_event_index,
3905 .stroke_pos = DT_DRAWLAYER_PAINT_STROKE_END,
3906 };
3907 _fill_input_layer_coords(self, &end);
3908 _fill_input_brush_settings(self, &end);
3909 drawlayer_runtime_host_context_t runtime_host = {
3910 .runtime = {
3911 .self = self,
3912 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3913 .gui = g,
3914 .manager = &g->manager,
3915 .process_state = &g->process,
3916 },
3917 .raw_input = &end,
3918 };
3919 const dt_drawlayer_runtime_host_t runtime_manager = {
3920 .user_data = &runtime_host,
3921 .collect_inputs = _drawlayer_runtime_collect_inputs,
3922 .perform_action = _drawlayer_runtime_perform_action,
3923 };
3927 };
3928 const dt_drawlayer_runtime_result_t dispatch
3929 = dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3930 if(!dispatch.ok || !dispatch.raw_input_ok)
3931 dt_control_log(_("failed to queue drawing stroke end"));
3933 return 1;
3934 }
3935
3936 return 0;
3937}
3938
3940int scrolled(dt_iop_module_t *self, double x, double y, int up, uint32_t state)
3941{
3942 if(IS_NULL_PTR(self->dev) || self->dev->gui_module != self) return 0;
3943
3944 // A drawn blend mask being edited (this module's own blending options, not its own painting
3945 // state below, which never goes through dev->forms) must not leak scroll into the brush-size
3946 // change here.
3947 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)) return 0;
3948
3949 const gboolean increase = dt_mask_scroll_increases(up);
3950 const float factor = increase ? 1.1f : 0.9f;
3951 const float new_size = CLAMP(_conf_size() * factor, 1.0f, 2048.0f);
3953
3954 if(dt_iop_gui_data(self))
3955 {
3957 dt_bauhaus_slider_set(g->controls.size, new_size);
3958 drawlayer_runtime_host_context_t runtime_host = {
3959 .runtime = {
3960 .self = self,
3961 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3962 .gui = g,
3963 .manager = &g->manager,
3964 .process_state = &g->process,
3965 },
3966 };
3967 const dt_drawlayer_runtime_host_t runtime_manager = {
3968 .user_data = &runtime_host,
3969 .collect_inputs = _drawlayer_runtime_collect_inputs,
3970 .perform_action = _drawlayer_runtime_perform_action,
3971 };
3974 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3975 };
3976 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3977 }
3978 return 1;
3979}
3980
3981#ifdef HAVE_OPENCL
3983int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
3984{
3985 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3986 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3987 const gint64 process_t0 = g_get_monotonic_time();
3990 const gboolean have_gui = (!IS_NULL_PTR(gui));
3991 {
3992 const gboolean display_pipe = have_gui && (pipe == self->dev->pipe || pipe == self->dev->preview_pipe);
3994 dt_drawlayer_runtime_manager_bind_piece(&data->headless_manager, &data->process, gui ? &gui->manager : NULL,
3995 gui ? &gui->process : NULL, display_pipe, &data->runtime_manager,
3996 &data->runtime_process, &data->runtime_display_pipe);
3997 }
3998 dt_iop_drawlayer_data_t *runtime_data = (dt_iop_drawlayer_data_t *)piece->data;
3999 const dt_iop_drawlayer_params_t *runtime_params
4000 = &runtime_data->params;
4001 if(runtime_params->layer_name[0] != '\0')
4002 _refresh_piece_base_cache(self, runtime_data, runtime_params, (dt_dev_pixelpipe_t *)pipe,
4003 (dt_dev_pixelpipe_iop_t *)piece);
4004 const drawlayer_runtime_request_t runtime_request = {
4005 .self = self,
4006 .pipe = pipe,
4007 .piece = (dt_dev_pixelpipe_iop_t *)piece,
4008 .runtime_params = runtime_params,
4009 .gui = gui,
4010 .manager = runtime_data->runtime_manager,
4011 .process_state = runtime_data->runtime_process,
4012 .display_pipe = runtime_data->runtime_display_pipe,
4013 .roi_in = roi_in,
4014 .roi_out = roi_out,
4015 .use_opencl = TRUE,
4016 };
4017 drawlayer_runtime_host_context_t runtime_host = {
4018 .runtime = runtime_request,
4019 };
4020 const dt_drawlayer_runtime_host_t runtime_manager = {
4021 .user_data = &runtime_host,
4022 .collect_inputs = _drawlayer_runtime_collect_inputs,
4023 .perform_action = _drawlayer_runtime_perform_action,
4024 };
4025 const dt_drawlayer_runtime_update_request_t process_pre = {
4027 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
4028 };
4031 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
4032 };
4033 dt_drawlayer_runtime_manager_t *const manager = runtime_request.manager;
4034 dt_drawlayer_runtime_manager_update(manager, &process_pre, &runtime_manager);
4035 if(IS_NULL_PTR(global) || global->kernel_premult_over < 0)
4036 {
4037 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4038 return FALSE;
4039 }
4040 gboolean fallback = (runtime_params->layer_name[0] == '\0');
4041
4042 const drawlayer_preview_background_t preview_bg = _resolve_preview_background(self, gui);
4044 if(IS_NULL_PTR(scratch))
4045 {
4046 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4047 return FALSE;
4048 }
4049
4050 dt_drawlayer_runtime_source_t source = { 0 };
4051 if(!fallback) fallback = !_update_runtime_state(&runtime_request, &source);
4052 if(!fallback)
4053 {
4054 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
4055 const gboolean reuse_device_buffers = realtime;
4056 dt_iop_roi_t target_roi = source.target_roi;
4057 dt_iop_roi_t source_roi = source.source_roi;
4058 int source_width = source.width;
4059 int source_height = source.height;
4060 gboolean direct_copy = source.direct_copy;
4061 const float *source_pixels = source.pixels;
4062 dt_pixel_cache_entry_t *source_entry = source.cache_entry;
4063
4064 if(fallback)
4065 goto process_cl_fallback;
4066
4067 /* The realtime output cacheline is reused in place while only the layer host
4068 * pixels change, so when the same device buffer comes back for the same layer
4069 * and geometry it still holds the previous full composite and only the painted
4070 * sub-rect needs refreshing. The node's global_hash is NOT stable across a
4071 * stroke (the heartbeat bumps stroke_commit_hash every frame), so we key on
4072 * the stable base-patch layer identity instead. Validate here; the blend
4073 * records the new state on success. */
4074 dt_drawlayer_process_state_t *const pstate = runtime_request.process_state;
4075 const uint64_t layer_hash = pstate ? pstate->base_patch.cache_hash : 0;
4076 const gboolean g_valid = pstate && pstate->last_composite_valid;
4077 const gboolean g_devout = pstate && pstate->last_composite_dev_out == (void *)dev_out;
4078 const gboolean g_hash = pstate && layer_hash != 0 && pstate->last_composite_layer_hash == layer_hash;
4079 const gboolean g_roi = pstate && !memcmp(&pstate->last_composite_target_roi, &target_roi, sizeof(dt_iop_roi_t));
4080 const gboolean allow_partial = realtime && g_valid && g_devout && g_hash && g_roi;
4081 if(realtime && pstate && !allow_partial && (dt_get_debug_flags() & DT_DEBUG_VERBOSE))
4083 "[drawlayer] partial gate declined: valid=%d devout=%d hash=%d roi=%d\n",
4084 g_valid, g_devout, g_hash, g_roi);
4085
4086 gboolean ok = _blend_layer_over_input_cl(
4087 pipe->devid, global->kernel_premult_over, dev_out, dev_in, scratch, source_pixels, source_entry, NULL,
4088 source_width, source_height, runtime_request.process_state, &target_roi, &source_roi, direct_copy,
4089 preview_bg.enabled, preview_bg.value,
4090 reuse_device_buffers, FALSE, allow_partial);
4091
4092 if(pstate)
4093 {
4094 if(ok && realtime && !preview_bg.enabled && layer_hash != 0)
4095 {
4096 /* dev_out now holds a valid full composite for this layer/geometry,
4097 * whether produced by the full or the partial path. */
4098 pstate->last_composite_dev_out = (void *)dev_out;
4099 pstate->last_composite_layer_hash = layer_hash;
4100 pstate->last_composite_target_roi = target_roi;
4101 pstate->last_composite_valid = TRUE;
4102 }
4103 else
4104 {
4105 pstate->last_composite_valid = FALSE;
4106 pstate->last_composite_dev_out = NULL;
4107 }
4108 }
4109
4110 process_post.release = (dt_drawlayer_runtime_release_t){
4111 .process = runtime_request.process_state,
4112 .source = &source,
4113 };
4115 dt_print(DT_DEBUG_PERF, "[drawlayer] process_cl step=blend-base total=%.3f ok=%d\n",
4116 (g_get_monotonic_time() - process_t0) / 1000.0, ok ? 1 : 0);
4117 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4118 return ok;
4119 }
4120
4121process_cl_fallback:
4123 dt_print(DT_DEBUG_PERF, "[drawlayer] process_cl step=no-cache-pass-through total=%.3f\n",
4124 (g_get_monotonic_time() - process_t0) / 1000.0);
4125 const gboolean ok = dt_iop_clip_and_zoom_roi_cl(pipe->devid, dev_out, dev_in, roi_out, roi_in)
4126 == CL_SUCCESS;
4127 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4128 return ok;
4129}
4130#endif
4131
4135 const void *const ivoid, void *const ovoid)
4136{
4137 const dt_iop_roi_t *const roi_in = &piece->roi_in;
4138 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4140 const gboolean have_gui = (!IS_NULL_PTR(gui));
4141 {
4142 const gboolean display_pipe = have_gui && (pipe == self->dev->pipe || pipe == self->dev->preview_pipe);
4144 dt_drawlayer_runtime_manager_bind_piece(&data->headless_manager, &data->process, gui ? &gui->manager : NULL,
4145 gui ? &gui->process : NULL, display_pipe, &data->runtime_manager,
4146 &data->runtime_process, &data->runtime_display_pipe);
4147 }
4148 dt_iop_drawlayer_data_t *runtime_data = (dt_iop_drawlayer_data_t *)piece->data;
4149 const dt_iop_drawlayer_params_t *runtime_params
4150 = &runtime_data->params;
4151 if(runtime_params->layer_name[0] != '\0')
4152 _refresh_piece_base_cache(self, runtime_data, runtime_params, (dt_dev_pixelpipe_t *)pipe,
4153 (dt_dev_pixelpipe_iop_t *)piece);
4154 const drawlayer_runtime_request_t runtime_request = {
4155 .self = self,
4156 .pipe = pipe,
4157 .piece = (dt_dev_pixelpipe_iop_t *)piece,
4158 .runtime_params = runtime_params,
4159 .gui = gui,
4160 .manager = runtime_data->runtime_manager,
4161 .process_state = runtime_data->runtime_process,
4162 .display_pipe = runtime_data->runtime_display_pipe,
4163 .roi_in = roi_in,
4164 .roi_out = roi_out,
4165 .use_opencl = FALSE,
4166 };
4167 drawlayer_runtime_host_context_t runtime_host = {
4168 .runtime = runtime_request,
4169 };
4170 const dt_drawlayer_runtime_host_t runtime_manager = {
4171 .user_data = &runtime_host,
4172 .collect_inputs = _drawlayer_runtime_collect_inputs,
4173 .perform_action = _drawlayer_runtime_perform_action,
4174 };
4175 const dt_drawlayer_runtime_update_request_t process_pre = {
4177 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
4178 };
4181 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
4182 };
4183 const float *input = (const float *)ivoid;
4184 float *output = (float *)ovoid;
4185 const size_t pixels = (size_t)roi_out->width * roi_out->height;
4186 const gint64 process_t0 = g_get_monotonic_time();
4187 dt_drawlayer_runtime_manager_t *const manager = runtime_request.manager;
4188 dt_drawlayer_runtime_manager_update(manager, &process_pre, &runtime_manager);
4189
4190 /* `process()` keeps the pipeline contract simple:
4191 * - the in-memory cache and stroke math stay in float32,
4192 * - TIFF I/O converts to/from half-float only at the file boundary. */
4193 gboolean fallback = (runtime_params->layer_name[0] == '\0');
4194 const drawlayer_preview_background_t preview_bg = _resolve_preview_background(self, gui);
4195
4196 dt_drawlayer_runtime_source_t source = { 0 };
4197 if(!fallback) fallback = !_update_runtime_state(&runtime_request, &source);
4198 if(!fallback)
4199 {
4200 const float *layer_pixels = source.pixels;
4201 if(!source.direct_copy)
4202 {
4204 if(IS_NULL_PTR(scratch)) fallback = TRUE;
4205
4206 float *layerbuf = NULL;
4207 if(!fallback)
4208 layerbuf = dt_drawlayer_cache_ensure_scratch_buffer(&scratch->layerbuf, &scratch->layerbuf_pixels, pixels,
4209 "drawlayer process scratch");
4210 if(!fallback && IS_NULL_PTR(layerbuf)) fallback = TRUE;
4211 if(fallback)
4212 {
4213 process_post.release = (dt_drawlayer_runtime_release_t){
4214 .process = runtime_request.process_state,
4215 .source = &source,
4216 };
4217 goto fallback_pass_through;
4218 }
4219 /* Bilinear (not the user pref) for the layer matte — see the OpenCL path in
4220 * _drawlayer_copy_or_resample_layer_roi: no negative lobes => no overshoot,
4221 * so no edge halos / out-of-range premultiplied alpha. */
4222 {
4224 dt_interpolation_resample(itor, layerbuf, &source.target_roi, source.pixels, &source.source_roi);
4225 }
4226 layer_pixels = layerbuf;
4227 }
4228
4229 _blend_layer_over_input(output, input, layer_pixels, pixels, preview_bg.enabled, preview_bg.value);
4231 dt_print(DT_DEBUG_PERF, "[drawlayer] process step=blend-base total=%.3f\n",
4232 (g_get_monotonic_time() - process_t0) / 1000.0);
4233
4234 process_post.release = (dt_drawlayer_runtime_release_t){
4235 .process = runtime_request.process_state,
4236 .source = &source,
4237 };
4238 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4239 return 0;
4240 }
4241
4242 /* The sidecar is intentionally managed outside of `process()`. Once a layer
4243 * is loaded, the in-memory caches are authoritative until module-level flush
4244 * points write them back. If we do not have a usable in-memory cache here,
4245 * the correct backend behavior is therefore a no-op pass-through rather than
4246 * reopening/scanning/loading the TIFF in the hot process path. */
4247fallback_pass_through:
4249 dt_print(DT_DEBUG_PERF, "[drawlayer] process step=no-cache-pass-through total=%.3f\n",
4250 (g_get_monotonic_time() - process_t0) / 1000.0);
4251 dt_iop_image_copy_by_size(output, input, roi_out->width, roi_out->height, 4);
4252 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4253 return 0;
4254}
GtkWidget * dt_gui_main_window(void)
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1963
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1970
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1632
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3423
Dab-level brush rasterization API for drawlayer.
@ DT_DRAWLAYER_BRUSH_MODE_PAINT
Definition brush.h:49
@ DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN
Definition brush.h:41
@ DT_DRAWLAYER_BRUSH_SHAPE_LINEAR
Definition brush.h:40
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
@ IOP_CS_NONE
GtkWidget * dt_color_picker_new_with_cst(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w, const dt_iop_colorspace_type_t cst)
@ DT_COLOR_PICKER_POINT_AREA
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
int dt_conf_get_bool(const char *name)
void dt_conf_set_float(const char *name, float val)
void dt_control_get_pointer_input(dt_control_pointer_input_t *input)
Definition control.c:83
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_mouse_is_painting(gboolean state)
Definition control.c:437
#define dt_control_set_cursor_visible(visible)
Definition control.h:153
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
gboolean dt_drawlayer_layer_canvas_for_pipe(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, int *width, int *height)
Definition coordinates.c:83
gboolean dt_drawlayer_raw_placement_for_frame(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *const frame, dt_drawlayer_raw_placement_t *placement)
float dt_drawlayer_widget_brush_radius(dt_iop_module_t *self, const dt_drawlayer_brush_dab_t *dab, const float fallback)
gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy, float *lx, float *ly)
Shared coordinate transforms and geometry computations for drawlayer.
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
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
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.
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_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_notify_change(dt_develop_t *dev, const int32_t imgid)
Notify the rest of the app that history changes were written.
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_update_history_all(dev)
float dt_dev_viewport_center_y(const dt_develop_t *dev)
float dt_dev_viewport_center_x(const dt_develop_t *dev)
float dt_dev_viewport_scaling(const dt_develop_t *dev)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
__DT_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
dt_dev_pixelpipe_iop_t * dt_dev_distort_get_iop_pipe(struct dt_dev_pixelpipe_t *pipe, struct dt_iop_module_t *module)
Definition develop.c:1833
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1959
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1854
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1865
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:499
GtkWidget * geometry
its size, under the preview
static gboolean _rekey_shared_base_patch(drawlayer_patch_t *patch, const int32_t imgid, const dt_iop_drawlayer_params_t *params)
Definition drawlayer.c:412
static void _retain_base_patch_loaded_ref(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:460
void init(dt_iop_module_t *module)
Allocate and initialize module parameter blocks.
Definition drawlayer.c:2786
static void _sanitize_requested_layer_name(const char *requested, char *name, size_t name_size)
Definition drawlayer.c:1529
void dt_drawlayer_touch_stroke_commit_hash(dt_iop_drawlayer_params_t *params, const int dab_count, const gboolean have_last_dab, const float last_dab_x, const float last_dab_y, const uint32_t publish_serial)
Definition drawlayer.c:1651
const char ** description(struct dt_iop_module_t *self)
Module description strings used by UI/help.
Definition drawlayer.c:2733
#define _set_drawlayer_pipeline_realtime_mode
Definition drawlayer.c:128
int default_group()
Return default iop group for drawlayer module.
Definition drawlayer.c:2768
gboolean dt_drawlayer_flush_layer_cache(dt_iop_module_t *self)
Definition layers.c:359
static void _draw_brush_hud(cairo_t *cr, const drawlayer_hud_brush_state_t *state)
Definition drawlayer.c:3531
#define _commit_dabs
Definition drawlayer.c:125
void gui_reset(dt_iop_module_t *self)
Reset GUI/session state for current drawlayer instance.
Definition drawlayer.c:2850
static gboolean _confirm_delete_layer(dt_iop_module_t *self, const gboolean removing_module)
Definition drawlayer.c:1957
static gboolean _background_layer_job_done_idle(gpointer user_data)
Definition drawlayer.c:2158
#define _drawlayer_wait_for_rasterization_modal
Definition drawlayer.c:131
static drawlayer_process_scratch_t * _get_process_scratch(void)
Definition drawlayer.c:347
void dt_drawlayer_show_runtime_feedback(const dt_iop_drawlayer_gui_data_t *g, const dt_drawlayer_runtime_feedback_t feedback)
Definition drawlayer.c:2703
static void _preview_bg_toggled(GtkToggleButton *button, gpointer user_data)
Definition drawlayer.c:2588
#define _flush_layer_cache
Definition drawlayer.c:126
int mouse_leave(dt_iop_module_t *self)
Mouse leave handler.
Definition drawlayer.c:3663
static gboolean _apply_selected_layer_attachment(dt_iop_module_t *self, dt_iop_drawlayer_gui_data_t *g, dt_iop_drawlayer_params_t *params, const char *layer_name, const int layer_order)
Apply one selected on-disk layer from the combobox to module params/history.
Definition drawlayer.c:2303
static gboolean _fill_current_layer(dt_iop_module_t *self, const float value)
Definition drawlayer.c:2406
gboolean dt_drawlayer_sync_widget_cache(dt_iop_module_t *self)
Definition layers.c:469
void gui_focus(dt_iop_module_t *self, gboolean in)
Focus transition hook (enter/leave) for drawlayer GUI mode.
Definition drawlayer.c:3370
static int _drawlayer_run_premult_over_kernel(const int devid, const int kernel_premult_over, cl_mem dev_background, cl_mem dev_layer_rgba, cl_mem dev_out, const int width, const int height, const int background_offset_x, const int background_offset_y)
Definition drawlayer.c:852
static gboolean _color_picker_set_from_position(dt_iop_module_t *self, const float x, const float y)
Definition drawlayer.c:1326
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition drawlayer.c:3239
static void _attach_selected_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2355
__DT_CLONE_TARGETS__ int process(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
CPU processing path for layer-over-input compositing.
Definition drawlayer.c:4134
static dt_drawlayer_runtime_result_t _update_gui_runtime_manager(dt_iop_module_t *self, dt_iop_drawlayer_gui_data_t *g, dt_drawlayer_runtime_event_t event, gboolean flush_pending)
Definition drawlayer.c:2668
int button_pressed(dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Button press handler (starts stroke capture on left button).
Definition drawlayer.c:3821
static gboolean _color_picker_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition drawlayer.c:1478
static gboolean _prompt_layer_name_dialog(const char *title, const char *message, const char *initial_name, char *name, size_t name_size)
Definition drawlayer.c:1288
static void _fill_transparent_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2486
#define _release_all_base_patch_extra_refs
Definition drawlayer.c:130
void dt_drawlayer_begin_gui_stroke_capture(dt_iop_module_t *self, const dt_drawlayer_paint_raw_input_t *first_input)
Definition drawlayer.c:2639
static void _show_drawlayer_modal_message(const GtkMessageType type, const char *primary, const char *secondary)
Definition drawlayer.c:1275
static void _destroy_process_scratch(gpointer data)
Definition drawlayer.c:335
static void _create_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2565
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition drawlayer.c:2796
void dt_drawlayer_release_all_base_patch_extra_refs(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:474
static int64_t _sidecar_timestamp_from_path(const char *path)
Definition drawlayer.c:1126
static void _save_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2493
static gboolean _get_current_work_profile_key(dt_iop_module_t *self, GList *iop_list, dt_dev_pixelpipe_t *pipe, char *key, const size_t key_size)
Definition drawlayer.c:310
static void _layer_selected(GtkWidget *widget, gpointer user_data)
Definition drawlayer.c:2334
static gboolean _drawlayer_sync_host_image_to_device(const int devid, cl_mem device_image, void *host_pixels, const int width, const int height, const int bpp, const dt_drawlayer_damaged_rect_t *dirty_rect)
Definition drawlayer.c:676
static gboolean _brush_profile_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition drawlayer.c:1377
static void _build_pre_module_filter_string(dt_iop_module_t *self, char *filter, const size_t filter_size)
Definition drawlayer.c:2138
const char * name()
Module display name.
Definition drawlayer.c:2727
static void _sync_brush_profile_preview_widget(dt_iop_module_t *self)
Definition drawlayer.c:1366
static void _widget_changed(GtkWidget *widget, gpointer user_data)
Definition drawlayer.c:2277
static void _brush_pipeline_color_from_display(dt_iop_module_t *self, const float display_rgb[3], float pipeline_rgb[3])
Convert one display-space brush color snapshot to pipeline space.
Definition drawlayer.c:165
static gboolean _clear_current_layer(dt_iop_module_t *self)
Definition drawlayer.c:2444
static void _sync_cached_brush_colors(dt_iop_module_t *self, const float display_rgb[3])
Cache brush colors in GUI state so stroke input snapshots don't re-transform per event.
Definition drawlayer.c:198
int scrolled(dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Scroll handler used for interactive brush-size changes.
Definition drawlayer.c:3940
gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, gboolean record_history)
Definition drawlayer.c:1754
void quiesce(dt_iop_module_t *self)
Destroy GUI resources and stop background worker.
Definition drawlayer.c:3441
void gui_init(dt_iop_module_t *self)
Build GUI widgets and initialize worker/caches.
Definition drawlayer.c:2903
static int _drawlayer_copy_or_resample_layer_roi(const int devid, cl_mem dev_source_rgba, cl_mem dev_layer_rgba, const int source_w, const int source_h, const dt_iop_roi_t *const target_roi, const dt_iop_roi_t *const source_roi)
Definition drawlayer.c:782
static gboolean _create_background_layer_from_input(dt_iop_module_t *self)
Definition drawlayer.c:2210
void dt_drawlayer_set_pipeline_realtime_mode(dt_iop_module_t *self, gboolean state)
Definition layers.c:451
static drawlayer_wait_dialog_t _show_drawlayer_wait_dialog(const char *title, const char *message)
Definition drawlayer.c:1191
static void _rename_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2372
static gboolean _color_picker_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1463
static gboolean _color_swatch_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1354
#define _touch_stroke_commit_hash
Definition drawlayer.c:134
static gboolean _drawlayer_acquire_layer_image(const int devid, dt_pixel_cache_entry_t *resolved_entry, const gboolean realtime_reuse, const gboolean direct_copy, cl_mem dev_source_rgba, const int source_w, const int source_h, const dt_iop_roi_t *const target_roi, const dt_iop_roi_t *const source_roi, drawlayer_cl_image_handle_t *layer, int *err)
Definition drawlayer.c:812
static gboolean _color_picker_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1455
void gui_cleanup(dt_iop_module_t *self)
Definition drawlayer.c:3454
static int _blend_layer_over_input_cl(const int devid, const int kernel_premult_over, cl_mem dev_out, cl_mem dev_in, drawlayer_process_scratch_t *scratch, const float *layer_pixels, dt_pixel_cache_entry_t *source_entry, cl_mem source_mem_override, const int source_w, const int source_h, dt_drawlayer_process_state_t *process, const dt_iop_roi_t *const target_roi, const dt_iop_roi_t *const source_roi, const gboolean direct_copy, const gboolean use_preview_bg, const float preview_bg, const gboolean realtime_reuse, const gboolean force_device_copy, const gboolean allow_partial)
Definition drawlayer.c:920
#define _drawlayer_runtime_perform_action
Definition drawlayer.c:136
static gboolean _drawlayer_modal_wait_tick(gpointer user_data)
Definition drawlayer.c:1237
static void _sync_preview_bg_buttons(dt_iop_module_t *self)
Definition drawlayer.c:1735
static gboolean _delete_current_layer(dt_iop_module_t *self)
Definition drawlayer.c:1896
void cleanup_global(dt_iop_module_so_t *module)
Release global OpenCL resources for drawlayer.
Definition drawlayer.c:2757
gboolean module_will_remove(dt_iop_module_t *self)
Hook called before module removal from history stack.
Definition drawlayer.c:2894
static gboolean _drawlayer_map_source_damage_to_target(const dt_drawlayer_damaged_rect_t *src_damage, const dt_iop_roi_t *const target_roi, const dt_iop_roi_t *const source_roi, dt_drawlayer_damaged_rect_t *out)
Definition drawlayer.c:883
static gboolean _drawlayer_acquire_source_image(const int devid, const float *layer_pixels, dt_pixel_cache_entry_t *resolved_entry, const gboolean force_device_copy, const gboolean realtime_reuse, const int source_w, const int source_h, dt_drawlayer_process_state_t *process, drawlayer_cl_image_handle_t *source)
Definition drawlayer.c:711
int mouse_moved(dt_iop_module_t *self, double x, double y, double pressure, int which)
Mouse motion handler.
Definition drawlayer.c:3690
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Return default colorspace expected by drawlayer process paths.
Definition drawlayer.c:2780
static gboolean _color_swatch_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition drawlayer.c:1346
int flags()
Return module capability flags.
Definition drawlayer.c:2774
static gboolean _working_rgb_to_display_rgb(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const float working_rgb[3], float display_rgb[3])
Definition drawlayer.c:1403
static gboolean _profile_key_is_sane(const char *value)
Definition drawlayer.c:1110
static gboolean _color_picker_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition drawlayer.c:1337
int button_released(dt_iop_module_t *self, double x, double y, int which, uint32_t state)
Button release handler (ends current stroke).
Definition drawlayer.c:3885
#define _layer_to_widget_coords
Definition drawlayer.c:133
static gboolean _create_new_layer(dt_iop_module_t *self, const char *requested_name)
Definition drawlayer.c:2068
void commit_params(dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Commit params to runtime piece and refresh base cache state.
Definition drawlayer.c:2829
void change_image(dt_iop_module_t *self)
Invalidate module state when active image changes.
Definition drawlayer.c:3341
static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
Definition drawlayer.c:1852
#define _drawlayer_runtime_collect_inputs
Definition drawlayer.c:135
static void _create_background_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2581
void dt_drawlayer_wait_for_rasterization_modal(const dt_iop_drawlayer_gui_data_t *g, const char *title, const char *message)
Definition drawlayer.c:1250
dt_drawlayer_runtime_context_t drawlayer_runtime_host_context_t
Definition drawlayer.c:123
static gboolean _brush_profile_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1386
static GPrivate _drawlayer_process_scratch_key
Definition drawlayer.c:345
static __DT_CLONE_TARGETS__ void _blend_layer_over_input(float *output, const float *input, const float *layerbuf, const size_t pixels, const gboolean use_preview_bg, const float preview_bg)
Definition drawlayer.c:638
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Cleanup per-pipe runtime data.
Definition drawlayer.c:2816
void gui_post_expose(dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Draw post-expose overlay (cursor, HUD, temp preview).
Definition drawlayer.c:3577
static void _fill_black_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2479
dt_drawlayer_runtime_request_t drawlayer_runtime_request_t
Definition drawlayer.c:122
void dt_drawlayer_end_gui_stroke_capture(dt_iop_module_t *self)
Definition drawlayer.c:2662
static void _sync_layer_controls(dt_iop_module_t *self)
Definition drawlayer.c:1688
static void _fill_input_layer_coords(dt_iop_module_t *self, dt_drawlayer_paint_raw_input_t *input)
Definition drawlayer.c:277
static gboolean _layer_name_non_empty(const char *name)
Definition drawlayer.c:301
static void _fill_input_brush_settings(dt_iop_module_t *self, dt_drawlayer_paint_raw_input_t *input)
Definition drawlayer.c:210
void init_global(dt_iop_module_so_t *module)
Initialize global OpenCL resources for drawlayer kernels.
Definition drawlayer.c:2742
static gboolean _rename_current_layer_from_gui(dt_iop_module_t *self, const char *requested_name)
Definition drawlayer.c:1995
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
OpenCL processing path for layer-over-input compositing.
Definition drawlayer.c:3983
#define DRAWLAYER_RESAMPLE_DAMAGE_MARGIN
Definition drawlayer.c:876
static void _compute_hud_brush_state(const dt_control_pointer_input_t *pointer_input, drawlayer_hud_brush_state_t *state)
Definition drawlayer.c:3486
#define _ensure_layer_cache
Definition drawlayer.c:129
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Apply selected picker color to drawlayer brush color.
Definition drawlayer.c:1437
static void _sync_mode_sensitive_widgets(dt_iop_module_t *self)
Definition drawlayer.c:1881
static void _fill_white_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2472
static void _sanitize_params(dt_iop_module_t *self, dt_iop_drawlayer_params_t *params)
Definition drawlayer.c:1487
static void _ensure_cursor_stamp_surface(dt_iop_module_t *self, const float widget_radius, const float opacity, const float hardness)
Definition drawlayer.c:1135
static gboolean _build_raw_input_event(dt_iop_module_t *self, const double wx, const double wy, const double pressure, const dt_drawlayer_paint_stroke_pos_t stroke_pos, dt_drawlayer_paint_raw_input_t *input)
Definition drawlayer.c:2611
static void _refresh_layer_widgets(dt_iop_module_t *self)
Definition drawlayer.c:1675
static void _retain_base_patch_stroke_ref(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:467
static void _delete_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2391
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
#define DT_GUI_MODULE(x)
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
int bpp
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:215
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
gboolean dt_mask_scroll_increases(int up)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
int dt_iop_clip_and_zoom_roi_cl(int devid, cl_mem dev_out, cl_mem dev_in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in)
void *const ovoid
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
void dt_interpolation_resample(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
int dt_interpolation_resample_cl(const struct dt_interpolation *itor, const int devid, cl_mem dev_out, const dt_iop_roi_t *const roi_out, cl_mem dev_in, const dt_iop_roi_t *const roi_in)
@ DT_INTERPOLATION_BILINEAR
int32_t dt_drawlayer_io_background_layer_job_run(dt_job_t *job)
Worker entrypoint for async "create background from input" sidecar jobs.
Definition io.c:1064
gboolean dt_drawlayer_io_layer_name_exists(const char *path, const char *candidate, const int ignore_index)
Test if a layer name already exists in sidecar TIFF.
Definition io.c:614
gboolean dt_drawlayer_io_find_layer(const char *path, const char *target_name, const int target_order, dt_drawlayer_io_layer_info_t *info)
Find target layer metadata in sidecar TIFF.
Definition io.c:686
gboolean dt_drawlayer_io_sidecar_path(const int32_t imgid, char *path, const size_t path_size)
Build absolute sidecar path from image id.
Definition io.c:675
gboolean dt_drawlayer_io_rename_layer(const char *path, const char *current_name, const char *new_name, const char *work_profile, const int layer_width, const int layer_height, dt_drawlayer_io_layer_info_t *info)
Rename one existing layer page while preserving its directory payload.
Definition io.c:928
gboolean dt_drawlayer_io_load_layer(const char *path, const char *target_name, const int target_order, const int layer_width, const int layer_height, dt_drawlayer_io_patch_t *patch)
Load one sidecar layer patch into float RGBA destination patch.
Definition io.c:700
gboolean dt_drawlayer_io_delete_layer(const char *path, const char *target_name, const int layer_width, const int layer_height)
Delete one existing layer page from the sidecar TIFF.
Definition io.c:958
TIFF sidecar I/O API for drawlayer layers.
static float _clamp01(const float v)
Clamp scalar to [0,1].
gboolean dt_drawlayer_brush_rasterize_dab_argb8(const dt_drawlayer_brush_dab_t *dab, uint8_t *argb, const int width, const int height, const int stride, const float center_x, const float center_y, const float opacity_multiplier)
Render one dab to 8-bit ARGB surface for GUI cursor preview.
Patch/cache helpers for drawlayer process and preview buffers.
Drawlayer configuration read/write helpers (private include unit).
static float _conf_smoothing(void)
Read and clamp configured smoothing parameter (%).
#define DRAWLAYER_CONF_MAP_TILT_SIZE
static void _ensure_gui_conf_defaults(void)
Ensure all drawlayer GUI config keys exist with sane defaults.
static float _conf_sprinkle_coarseness(void)
Read and clamp configured sprinkle octave mix (%).
static drawlayer_mapping_profile_t _conf_mapping_profile(const char *key)
Read and clamp one mapping-profile enum key.
static float _conf_hardness(void)
Derive hardness as complementary value of softness.
static float _conf_flow(void)
Read and clamp configured flow (%).
static float _conf_size(void)
Read and clamp configured brush size (px).
static void _conf_display_color(float rgb[3])
Read configured display RGB brush color.
#define DRAWLAYER_CONF_MAP_PRESSURE_SIZE
static float _conf_sprinkles(void)
Read and clamp configured sprinkles amount (%).
#define DRAWLAYER_CONF_ACCEL_PROFILE
static void _remember_display_color(dt_iop_module_t *self, const float display_rgb[3])
Push current display color to history and trigger swatch redraw.
static float _conf_opacity(void)
Read and clamp configured stroke opacity (%).
#define DRAWLAYER_CONF_MAP_PRESSURE_SOFTNESS
#define DRAWLAYER_CONF_PRESSURE_PROFILE
#define DRAWLAYER_CONF_MAP_TILT_FLOW
static float _conf_distance(void)
Read and clamp configured distance/sampling parameter (%).
#define DRAWLAYER_CONF_MAP_ACCEL_FLOW
static drawlayer_pick_source_t _conf_pick_source(void)
Read and clamp color picker source selector.
#define DRAWLAYER_CONF_MAP_ACCEL_SIZE
#define DRAWLAYER_CONF_MAP_PRESSURE_OPACITY
#define DRAWLAYER_CONF_SIZE
static void _sync_color_picker_from_conf(dt_iop_module_t *self)
Refresh picker widgets from persisted config color.
#define DRAWLAYER_CONF_MAP_TILT_SOFTNESS
#define DRAWLAYER_CONF_TILT_PROFILE
#define DRAWLAYER_CONF_MAP_ACCEL_OPACITY
static void _load_color_history(dt_iop_drawlayer_gui_data_t *g)
Load persisted color-history stack from config into widgets state.
static dt_iop_drawlayer_brush_shape_t _conf_brush_shape(void)
Read and clamp configured brush shape.
static void _sync_params_from_gui(dt_iop_module_t *self, const gboolean record_history)
Sync active GUI widget values back into persistent config keys.
#define DRAWLAYER_CONF_MAP_PRESSURE_FLOW
static float _conf_hdr_exposure(void)
Read and clamp HDR picker exposure compensation (EV).
#define DRAWLAYER_CONF_MAP_TILT_OPACITY
#define DRAWLAYER_CONF_MAP_ACCEL_SOFTNESS
static float _conf_sprinkle_size(void)
Read and clamp configured sprinkle feature size (px).
static dt_iop_drawlayer_brush_mode_t _conf_brush_mode(void)
Read and clamp configured brush blend mode.
static void _apply_display_brush_color(dt_iop_module_t *self, const float display_rgb[3], const gboolean remember)
Apply display-space brush color to conf, widgets and redraw.
Private drawlayer module types and lightweight shared helpers.
static float _mapping_profile_value(const drawlayer_mapping_profile_t profile, const float x)
@ DRAWLAYER_INPUT_MAP_ACCEL_SIZE
@ DRAWLAYER_INPUT_MAP_ACCEL_FLOW
@ DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_OPACITY
@ DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_SIZE
@ DRAWLAYER_INPUT_MAP_PRESSURE_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS
@ DRAWLAYER_INPUT_MAP_PRESSURE_SIZE
drawlayer_mapping_profile_t
@ DRAWLAYER_PREVIEW_BG_WHITE
@ DRAWLAYER_PREVIEW_BG_IMAGE
@ DRAWLAYER_PREVIEW_BG_BLACK
@ DRAWLAYER_PREVIEW_BG_GREY
drawlayer_pick_source_t
@ DRAWLAYER_PICK_SOURCE_OUTPUT
void dt_drawlayer_paint_runtime_state_reset(dt_drawlayer_damaged_rect_t *state)
Reset stroke-damage accumulator to empty/invalid.
Stroke-level path sampling and runtime-state API for drawlayer.
dt_drawlayer_paint_stroke_pos_t
Position of a raw input event inside a stroke lifecycle.
@ DT_DRAWLAYER_PAINT_STROKE_MIDDLE
@ DT_DRAWLAYER_PAINT_STROKE_FIRST
@ DT_DRAWLAYER_PAINT_STROKE_END
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_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:614
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:114
@ DT_JOB_QUEUE_USER_BG
Definition jobs.h:56
Private drawlayer layer cache, sidecar sync and widget cache state.
static void _layerio_log_errors(GString *errors)
Definition layers.c:37
static void _reset_stroke_session(dt_iop_drawlayer_gui_data_t *g)
Definition layers.c:89
static void _populate_layer_list(dt_iop_module_t *self)
Definition layers.c:43
static void _layerio_append_error(GString *errors, const char *message)
Definition layers.c:30
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_INPUT
Definition logging.h:64
@ DT_DEBUG_OPENCL
Definition logging.h:57
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_VERBOSE
Definition logging.h:78
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
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.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#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
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
void * dt_opencl_alloc_device_use_host_pointer(const int devid, const int width, const int height, const int bpp, void *host, const int flags)
Definition opencl.c:2917
gboolean dt_opencl_is_pinned_memory(cl_mem mem)
Definition opencl.c:238
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2894
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2679
int dt_opencl_unmap_mem_object(const int devid, cl_mem mem_object, void *mapped_ptr)
Definition opencl.c:2852
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
void * dt_opencl_map_image(const int devid, cl_mem buffer, const int blocking, const int flags, size_t width, size_t height, int bpp)
Definition opencl.c:2832
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1671
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2765
int dt_opencl_write_host_to_device_raw(const int devid, const void *host, void *device, const size_t *origin, const size_t *region, const int rowpitch, const int blocking)
Definition opencl.c:2667
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
int dt_opencl_write_host_to_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2634
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const float factor
Definition pdf.h:91
void * dt_dev_pixelpipe_cache_get_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid, int width, int height, int bpp, int flags, gboolean *out_reused)
Acquire a pinned OpenCL image for a host buffer tracked by the pixelpipe cache.
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
void dt_dev_pixelpipe_cache_release_cl_buffer(void **cl_mem_buffer, dt_pixel_cache_entry_t *cache_entry, void *host_ptr, const gboolean cache_device)
Release or cache an OpenCL image associated with a host cache line.
void dt_dev_pixelpipe_cache_put_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, void **mem)
Release or cache a pinned OpenCL image acquired with dt_dev_pixelpipe_cache_get_pinned_image().
void * dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *const host_ptr, const dt_iop_roi_t *roi, const size_t bpp, dt_iop_module_t *module, const char *message, dt_pixel_cache_entry_t *cache_entry, gboolean *out_reused, void *keep)
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
int dt_dev_pixelpipe_cache_rekey(const uint64_t old_hash, const uint64_t new_hash, dt_pixel_cache_entry_t *entry)
Change the hash/key of an existing cache line in place, without freeing, reallocating or invalidating...
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_ref_entry_for_host_ptr(void *host_ptr)
Resolve and retain the cache entry owning a host pointer.
Pixelpipe cache for storing intermediate results in the pixelpipe.
gboolean dt_dev_pixelpipe_get_realtime(const dt_dev_pixelpipe_t *pipe)
int main()
Definition prova.c:47
void dt_drawlayer_runtime_manager_bind_piece(dt_drawlayer_runtime_manager_t *headless_manager, dt_drawlayer_process_state_t *headless_process, dt_drawlayer_runtime_manager_t *gui_manager, dt_drawlayer_process_state_t *gui_process, const gboolean display_pipe, dt_drawlayer_runtime_manager_t **runtime_manager, dt_drawlayer_process_state_t **runtime_process, gboolean *runtime_display_pipe)
Definition runtime.c:913
void dt_drawlayer_runtime_manager_cleanup(dt_drawlayer_runtime_manager_t *state)
Definition runtime.c:85
void dt_drawlayer_runtime_manager_init(dt_drawlayer_runtime_manager_t *state)
Definition runtime.c:77
dt_drawlayer_runtime_result_t dt_drawlayer_runtime_manager_update(dt_drawlayer_runtime_manager_t *state, const dt_drawlayer_runtime_update_request_t *request, const dt_drawlayer_runtime_host_t *host)
Definition runtime.c:687
void dt_drawlayer_process_state_init(dt_drawlayer_process_state_t *state)
Definition runtime.c:927
void dt_drawlayer_ui_cursor_clear(dt_drawlayer_ui_state_t *state)
Definition runtime.c:986
void dt_drawlayer_process_state_cleanup(dt_drawlayer_process_state_t *state)
Definition runtime.c:935
void dt_drawlayer_runtime_manager_note_buffer_lock(dt_drawlayer_runtime_manager_t *state, const dt_drawlayer_runtime_buffer_t buffer, const dt_drawlayer_runtime_actor_t actor, const gboolean write_lock, const gboolean acquire)
Definition runtime.c:96
void dt_drawlayer_process_state_invalidate(dt_drawlayer_process_state_t *state)
Definition runtime.c:952
Private runtime state/helpers shared by drawlayer module entrypoints.
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE
Definition runtime.h:205
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_STROKE_BEGIN
Definition runtime.h:207
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_STROKE_END
Definition runtime.h:208
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_SAMPLE
Definition runtime.h:206
@ DT_DRAWLAYER_SOURCE_BASE_PATCH
Definition runtime.h:308
@ DT_DRAWLAYER_RUNTIME_ACTOR_PIPELINE_CPU
Definition runtime.h:163
@ DT_DRAWLAYER_RUNTIME_ACTOR_PIPELINE_CL
Definition runtime.h:164
@ DT_DRAWLAYER_RUNTIME_BUFFER_BASE_PATCH
Definition runtime.h:172
dt_drawlayer_runtime_event_t
Definition runtime.h:178
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_FOCUS_GAIN
Definition runtime.h:180
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CPU_AFTER
Definition runtime.h:192
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_SCROLL
Definition runtime.h:184
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CL_BEFORE
Definition runtime.h:193
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CL_AFTER
Definition runtime.h:194
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_CHANGE_IMAGE
Definition runtime.h:186
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_RAW_INPUT
Definition runtime.h:190
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CPU_BEFORE
Definition runtime.h:191
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_MOUSE_LEAVE
Definition runtime.h:183
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_STROKE_ABORT
Definition runtime.h:189
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_FOCUS_LOSS
Definition runtime.h:181
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_SYNC_TEMP_BUFFERS
Definition runtime.h:185
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED
This signal is raised when pipe is finished and the gui is attached no param, no returned value.
Definition signal.h:182
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
float * dt_drawlayer_cache_ensure_scratch_buffer(float **buffer, size_t *capacity_pixels, const size_t needed_pixels, const char *name)
Ensure scratch RGBA float capacity in pixels.
void dt_drawlayer_cache_patch_wrunlock(const dt_drawlayer_cache_patch_t *patch)
Release write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_wrlock(const dt_drawlayer_cache_patch_t *patch)
Acquire write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_clear(dt_drawlayer_cache_patch_t *patch, const char *external_alloc_name)
Release patch storage and reset patch metadata.
gboolean dt_drawlayer_cache_patch_alloc_shared(dt_drawlayer_cache_patch_t *patch, const uint64_t hash, const size_t pixel_count, const int width, const int height, const char *name, int *created_out)
Allocate patch storage from shared pixel cache entry.
void dt_drawlayer_cache_free_temp_buffer(void **buffer, const char *name)
Free temporary aligned cache buffer.
void dt_drawlayer_cache_patch_rdlock(const dt_drawlayer_cache_patch_t *patch)
Acquire read lock on shared patch cache entry.
void dt_drawlayer_cache_patch_rdunlock(const dt_drawlayer_cache_patch_t *patch)
Release read lock on shared patch cache entry.
void dt_drawlayer_cache_clear_transparent_float(float *pixels, const size_t pixel_count)
Fill float RGBA buffer with transparent black.
Shared drawlayer runtime helpers used across module/runtime files.
#define DRAWLAYER_NAME_SIZE
dt_drawlayer_runtime_feedback_t
@ DT_DRAWLAYER_RUNTIME_FEEDBACK_NONE
@ DT_DRAWLAYER_RUNTIME_FEEDBACK_FOCUS_LOSS_WAIT
@ DT_DRAWLAYER_RUNTIME_FEEDBACK_SAVE_WAIT
#define DRAWLAYER_PROFILE_SIZE
const float uint32_t state[4]
const float r
unsigned __int64 uint64_t
Definition strptime.c:75
char work_profile[DRAWLAYER_PROFILE_SIZE]
Definition layers.c:18
char name[DRAWLAYER_NAME_SIZE]
Definition layers.c:17
uint32_t height
Definition layers.c:16
const dt_iop_drawlayer_gui_data_t * g
Definition drawlayer.c:1234
Objective facts about the image a dev is working on.
struct dt_iop_module_t *void * data
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:167
dt_image_t image_storage
Definition develop.h:225
GList * iop
Definition develop.h:269
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
Fully resolved input dab descriptor.
Definition brush.h:65
Generic float RGBA patch stored either in malloc memory or pixel cache.
dt_pixel_cache_entry_t * cache_entry
Integer axis-aligned rectangle in buffer coordinates.
Parameters owned by the async "create background from input" job.
Definition io.h:61
char sidecar_path[DT_PATH_MAX]
Definition io.h:68
Result posted back to the UI after background-layer creation.
Definition io.h:79
Metadata returned when probing one layer directory in sidecar TIFF.
Definition io.h:47
char work_profile[256]
Definition io.h:54
Float RGBA patch used by drawlayer I/O routines.
Definition io.h:37
One raw pointer event queued to stroke processing.
uint64_t last_composite_layer_hash
Definition runtime.h:66
dt_drawlayer_cache_patch_t base_patch
Definition runtime.h:37
char cache_layer_name[DRAWLAYER_NAME_SIZE]
Definition runtime.h:48
dt_iop_roi_t last_composite_target_roi
Definition runtime.h:67
dt_drawlayer_damaged_rect_t cache_dirty_rect
Definition runtime.h:45
Where the raw-anchored layer canvas sits in this module's own frame.
Definition coordinates.h:48
dt_drawlayer_runtime_request_t runtime
Definition runtime.h:296
dt_drawlayer_process_state_t * process
Definition runtime.h:329
const dt_iop_drawlayer_params_t * runtime_params
Definition runtime.h:284
dt_iop_module_t * self
Definition runtime.h:281
const dt_iop_roi_t * roi_in
Definition runtime.h:289
const dt_dev_pixelpipe_t * pipe
Definition runtime.h:282
dt_drawlayer_process_state_t * process_state
Definition runtime.h:287
const dt_iop_roi_t * roi_out
Definition runtime.h:290
dt_dev_pixelpipe_iop_t * piece
Definition runtime.h:283
dt_drawlayer_runtime_manager_t * manager
Definition runtime.h:286
dt_drawlayer_runtime_buffer_t tracked_buffer
Definition runtime.h:322
dt_drawlayer_runtime_source_kind_t kind
Definition runtime.h:313
dt_pixel_cache_entry_t * cache_entry
Definition runtime.h:316
dt_drawlayer_runtime_actor_t tracked_actor
Definition runtime.h:323
dt_drawlayer_runtime_event_t event
Definition runtime.h:335
dt_drawlayer_runtime_release_t release
Definition runtime.h:339
int32_t id
Definition image.h:401
dt_drawlayer_process_state_t * runtime_process
dt_drawlayer_process_state_t process
dt_drawlayer_runtime_manager_t headless_manager
dt_iop_drawlayer_params_t params
dt_drawlayer_runtime_manager_t * runtime_manager
dt_drawlayer_process_state_t process
Definition runtime.h:272
dt_drawlayer_session_state_t session
Definition runtime.h:271
dt_drawlayer_runtime_manager_t manager
Definition runtime.h:274
GtkWidget * reset_button
Definition imageop_gui.h:60
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:272
dt_iop_params_t * default_params
Definition imageop.h:333
dt_aligned_pixel_t picked_output_color_min
Definition imageop.h:289
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_aligned_pixel_t picked_output_color_max
Definition imageop.h:289
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_output_color
Definition imageop.h:289
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
dt_iop_color_intent_t intent
Rendering intent, passed to cmsCreateTransform on the lcms2 fallback only. The matrix path ignores it...
dt_colorspaces_color_profile_type_t type
Profile identity, half of the memo key. DT_COLORSPACE_NONE means "unset" and makes every apply functi...
char filename[DT_IOP_COLOR_ICC_LEN]
ICC file name, the other half of the memo key; "" for every built-in. Compared with strcmp,...
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
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_widgets_suppressed(void)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
gboolean dt_drawlayer_widgets_finish_picker_drag(dt_drawlayer_widgets_t *widgets, float display_rgb[3])
End picker drag mode and optionally output current color.
Definition widgets.c:504
gboolean dt_drawlayer_widgets_pick_history_color(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, float x, float y, float display_rgb[3])
Pick one history swatch color from widget coordinates.
Definition widgets.c:519
gboolean dt_drawlayer_widgets_update_from_picker_position(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, float x, float y, float display_rgb[3])
Update picker state from mouse position and return selected color.
Definition widgets.c:445
dt_drawlayer_widgets_t * dt_drawlayer_widgets_init(void)
Allocate and initialize widget runtime state.
Definition widgets.c:347
gboolean dt_drawlayer_widgets_get_display_color(const dt_drawlayer_widgets_t *widgets, float display_rgb[3])
Get current color projected to display RGB.
Definition widgets.c:379
void dt_drawlayer_widgets_set_brush_profile_preview(dt_drawlayer_widgets_t *widgets, const float opacity, const float hardness, const float sprinkles, const float sprinkle_size, const float sprinkle_coarseness, const int selected_shape)
Update cached brush-profile preview parameters and selected profile.
Definition widgets.c:699
gboolean dt_drawlayer_widgets_is_picker_dragging(const dt_drawlayer_widgets_t *widgets)
Tell whether picker drag interaction is currently active.
Definition widgets.c:513
gboolean dt_drawlayer_widgets_pick_brush_profile(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, const float x, const float y, int *shape)
Hit-test and select one brush profile from the preview row.
Definition widgets.c:842
void dt_drawlayer_widgets_cleanup(dt_drawlayer_widgets_t **widgets)
Free widget runtime state and owned cairo resources.
Definition widgets.c:362
gboolean dt_drawlayer_widgets_draw_picker(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr, double pixels_per_dip)
Draw color picker map, controls and selection markers.
Definition widgets.c:542
gboolean dt_drawlayer_widgets_draw_swatch(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr)
Draw compact color-history swatch grid.
Definition widgets.c:665
gboolean dt_drawlayer_widgets_draw_brush_profiles(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr, const double pixels_per_dip)
Draw selectable row of brush-profile previews.
Definition widgets.c:737
Public color-picker/history widget API for drawlayer GUI.
#define DT_DRAWLAYER_COLOR_HISTORY_HEIGHT
Definition widgets.h:33
#define DT_DRAWLAYER_COLOR_PICKER_HEIGHT
Definition widgets.h:29
Drawlayer realtime worker thread and FIFO event queue.
void dt_drawlayer_worker_stop(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop realtime and full-resolution worker threads.
Definition worker.c:1561
void dt_drawlayer_worker_cleanup(dt_drawlayer_worker_t **worker)
Public worker cleanup entry point.
Definition worker.c:1539
void dt_drawlayer_worker_reset_backend_path(dt_drawlayer_worker_t *worker)
Reset worker-owned backend damage accumulator.
Definition worker.c:1603
static void _cancel_async_commit(dt_drawlayer_worker_t *rt)
Cancel deferred commit request state if any.
Definition worker.c:1393
void dt_drawlayer_worker_init(dt_iop_module_t *self, dt_drawlayer_worker_t **worker, gboolean *painting, gboolean *finish_commit_pending, guint *stroke_sample_count, uint32_t *current_stroke_batch)
Public worker initialization entry point.
Definition worker.c:1531
gboolean dt_drawlayer_worker_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when worker has pending activity.
Definition worker.c:1545
void dt_drawlayer_worker_publish_backend_stroke_damage(dt_iop_module_t *self)
Publish accumulated backend stroke damage into drawlayer process/runtime state.
Definition worker.c:355
void dt_drawlayer_worker_request_commit(dt_drawlayer_worker_t *worker)
Public commit request helper.
Definition worker.c:1582
gboolean dt_drawlayer_worker_any_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when any worker still has pending activity.
Definition worker.c:1551
void dt_drawlayer_worker_reset_live_publish(dt_drawlayer_worker_t *worker)
Reset worker-owned transient live-publish state.
Definition worker.c:1611
void dt_drawlayer_worker_seal_for_commit(dt_drawlayer_worker_t *worker)
Seal current stroke for synchronous commit.
Definition worker.c:1598
void dt_drawlayer_worker_reset_stroke(dt_drawlayer_worker_t *worker)
Clear preserved stroke runtime/history after commit completed.
Definition worker.c:1620
static gboolean _wait_worker_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Wait until worker queue is drained and not busy.
Definition worker.c:1260
Background stroke worker API for drawlayer realtime painting.