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