Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
drawlayer.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifdef HAVE_CONFIG_H
20#include "system/macros.h"
22#include "common/logging.h"
23#include "system/mem_alloc.h"
24#include "system/openmp.h"
26#include "common/paths.h"
27#include "common/hash.h"
28#include "config.h"
29#endif
30#include "develop/masks_gui.h"
31
32#include "widgets/bauhaus.h"
34#include "system/dtpthread.h"
35#include "common/imagebuf.h"
37#include "develop/iop_profile.h"
38#include "common/opencl.h"
39#include "common/conf.h"
40#include "control/control.h"
41#include "control/jobs.h"
42#include "develop/dev_history.h"
43#include "develop/develop.h"
44#include "develop/imageop.h"
45#include "develop/imageop_gui.h"
47#include "develop/masks.h"
49#include "pixel/interpolation.h"
51#include "gui/application.h"
53#include "iop/drawlayer/brush.h"
54#include "iop/drawlayer/cache.h"
56#include "iop/drawlayer/conf.h"
59#include "iop/drawlayer/io.h"
61#include "iop/drawlayer/paint.h"
65#include "iop/iop_api.h"
66
67#include <glib/gstdio.h>
68#include <inttypes.h>
69#include <limits.h>
70#include <math.h>
71#include <sched.h>
72#include <stdlib.h>
73#include <string.h>
74#include "widgets/notebook.h"
76
78
79
83/*
84 * drawlayer architecture summary
85 * ------------------------------
86 *
87 * This module stores a painted premultiplied RGBA layer in a half-float TIFF sidecar.
88 * The persistent layer lives in full image coordinates (raw-sized canvas, but in the
89 * module's current pipeline geometry, not in raw sensor geometry). The GUI keeps:
90 *
91 * 1. a full-resolution half-float cache of the selected TIFF layer (`base_patch`),
92 * 2. (legacy) a widget-sized ARGB preview overlay (`live_surface`) for immediate feedback,
93 * 3. a worker thread that consumes stroke samples while the module has focus.
94 *
95 * The current stroke model is intentionally conservative:
96 * - `distance` defines a spatial metronome for resampling the path independently of
97 * `mouse_moved` dispatch cadence.
98 * - smoothing is applied only to the incoming raw cursor point before that resampling.
99 * - the kept implementation uses a simple linear extrapolation from two anchor samples
100 * separated by one brush radius. Several "cleverer" variants were tried here
101 * (kernel caching, fixed-N smoothing windows, higher-order extrapolation, inertial
102 * direction filters), but they either benchmarked slower, introduced sampling
103 * dependence again, or created visible cusps/overshoot. Those attempts were removed
104 * and are documented locally near the code they affected.
105 *
106 * The guiding rule throughout this file is: keep the authoritative geometry in layer
107 * space, and derive widget-space feedback from it, so GUI preview and pipeline output
108 * cannot drift apart due to duplicated math.
109 */
110
111#ifdef HAVE_OPENCL
116#endif
117
123
126
127
128gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, gboolean record_history);
132static void _sync_layer_controls(dt_iop_module_t *self);
133static void _sanitize_requested_layer_name(const char *requested, char *name, size_t name_size);
134static gboolean _prompt_layer_name_dialog(const char *title, const char *message, const char *initial_name,
135 char *name, size_t name_size);
138static gboolean _background_layer_job_done_idle(gpointer user_data);
142 gboolean flush_pending);
143
148
149#include "control/signal.h"
150
152static void _brush_pipeline_color_from_display(dt_iop_module_t *self, const float display_rgb[3], float pipeline_rgb[3])
153{
154 if(!display_rgb || IS_NULL_PTR(pipeline_rgb)) return;
155
156 pipeline_rgb[0] = _clamp01(display_rgb[0]);
157 pipeline_rgb[1] = _clamp01(display_rgb[1]);
158 pipeline_rgb[2] = _clamp01(display_rgb[2]);
159
160 if(!IS_NULL_PTR(self) && self->dev && self->dev->pipe)
161 {
162 const dt_iop_order_iccprofile_info_t *const display_profile
164 const dt_iop_order_iccprofile_info_t *const work_profile
166 if(!IS_NULL_PTR(display_profile) && !IS_NULL_PTR(work_profile))
167 {
168 float in[4] = { pipeline_rgb[0], pipeline_rgb[1], pipeline_rgb[2], 0.0f };
169 float out[4] = { pipeline_rgb[0], pipeline_rgb[1], pipeline_rgb[2], 0.0f };
170 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, display_profile, work_profile,
171 "drawlayer brush color");
172 pipeline_rgb[0] = out[0];
173 pipeline_rgb[1] = out[1];
174 pipeline_rgb[2] = out[2];
175 }
176 }
177
178 const float gain = exp2f(dt_drawlayer_conf_hdr_exposure());
179 pipeline_rgb[0] *= gain;
180 pipeline_rgb[1] *= gain;
181 pipeline_rgb[2] *= gain;
182}
183
185void dt_drawlayer_sync_cached_brush_colors(dt_iop_module_t *self, const float display_rgb[3])
186{
188 if(IS_NULL_PTR(g) || !display_rgb) return;
189
190 g->ui.brush_display_color[0] = _clamp01(display_rgb[0]);
191 g->ui.brush_display_color[1] = _clamp01(display_rgb[1]);
192 g->ui.brush_display_color[2] = _clamp01(display_rgb[2]);
193 _brush_pipeline_color_from_display(self, g->ui.brush_display_color, g->ui.brush_pipeline_color);
194 g->ui.brush_color_valid = TRUE;
195}
196
207{
208 if(IS_NULL_PTR(g)) return;
209 dt_drawlayer_brush_settings_t *const c = &g->ui.brush_settings;
210
211 uint32_t map_flags = 0u;
224
225 c->map_flags = map_flags;
229 c->distance_percent = dt_drawlayer_conf_distance() / 100.0f;
230 c->smoothing_percent = dt_drawlayer_conf_smoothing() / 100.0f;
231 c->brush_radius = dt_drawlayer_conf_size();
232 c->brush_opacity = dt_drawlayer_conf_opacity() / 100.0f;
233 c->brush_flow = dt_drawlayer_conf_flow() / 100.0f;
234 c->brush_hardness = dt_drawlayer_conf_hardness();
235 c->brush_sprinkles = dt_drawlayer_conf_sprinkles() / 100.0f;
236 c->brush_sprinkle_size = dt_drawlayer_conf_sprinkle_size();
237 c->brush_sprinkle_coarseness = dt_drawlayer_conf_sprinkle_coarseness() / 100.0f;
238 c->brush_shape = dt_drawlayer_conf_brush_shape();
239 c->brush_mode = dt_drawlayer_conf_brush_mode();
240 g->ui.brush_settings_valid = TRUE;
241}
242
247
249{
250 if(IS_NULL_PTR(self) || IS_NULL_PTR(input)) return;
252 if(!IS_NULL_PTR(g) && !g->ui.brush_settings_valid) _refresh_brush_settings_cache(g);
253
254 /* Only reachable without GUI data, where there is nowhere to cache. */
255 uint32_t map_flags = 0u;
256 if(IS_NULL_PTR(g))
257 {
270 }
271
272 float display_rgb[3] = { 0.0f };
273 float pipeline_rgb[3] = { 0.0f };
274 if(!IS_NULL_PTR(g) && g->ui.brush_color_valid)
275 {
276 memcpy(display_rgb, g->ui.brush_display_color, sizeof(display_rgb));
277 memcpy(pipeline_rgb, g->ui.brush_pipeline_color, sizeof(pipeline_rgb));
278 }
279 else
280 {
282 if(!IS_NULL_PTR(g)) dt_drawlayer_sync_cached_brush_colors(self, display_rgb);
283 if(!IS_NULL_PTR(g) && g->ui.brush_color_valid)
284 {
285 memcpy(display_rgb, g->ui.brush_display_color, sizeof(display_rgb));
286 memcpy(pipeline_rgb, g->ui.brush_pipeline_color, sizeof(pipeline_rgb));
287 }
288 else
289 {
290 _brush_pipeline_color_from_display(self, display_rgb, pipeline_rgb);
291 display_rgb[0] = _clamp01(display_rgb[0]);
292 display_rgb[1] = _clamp01(display_rgb[1]);
293 display_rgb[2] = _clamp01(display_rgb[2]);
294 }
295 }
296
297 const dt_drawlayer_brush_settings_t *const c = IS_NULL_PTR(g) ? NULL : &g->ui.brush_settings;
298 input->map_flags = c ? c->map_flags : map_flags;
300 input->tilt_profile = c ? c->tilt_profile : (uint8_t)dt_drawlayer_conf_mapping_profile(DRAWLAYER_CONF_TILT_PROFILE);
301 input->accel_profile = c ? c->accel_profile : (uint8_t)dt_drawlayer_conf_mapping_profile(DRAWLAYER_CONF_ACCEL_PROFILE);
302 input->distance_percent = c ? c->distance_percent : dt_drawlayer_conf_distance() / 100.0f;
303 input->smoothing_percent = c ? c->smoothing_percent : dt_drawlayer_conf_smoothing() / 100.0f;
304 input->brush_radius = c ? c->brush_radius : dt_drawlayer_conf_size();
305 input->brush_opacity = c ? c->brush_opacity : dt_drawlayer_conf_opacity() / 100.0f;
306 input->brush_flow = c ? c->brush_flow : dt_drawlayer_conf_flow() / 100.0f;
307 input->brush_hardness = c ? c->brush_hardness : dt_drawlayer_conf_hardness();
308 input->brush_sprinkles = c ? c->brush_sprinkles : dt_drawlayer_conf_sprinkles() / 100.0f;
309 input->brush_sprinkle_size = c ? c->brush_sprinkle_size : dt_drawlayer_conf_sprinkle_size();
310 input->brush_sprinkle_coarseness = c ? c->brush_sprinkle_coarseness : dt_drawlayer_conf_sprinkle_coarseness() / 100.0f;
311 input->brush_shape = c ? c->brush_shape : dt_drawlayer_conf_brush_shape();
312 input->brush_mode = c ? c->brush_mode : dt_drawlayer_conf_brush_mode();
313 input->color[0] = pipeline_rgb[0];
314 input->color[1] = pipeline_rgb[1];
315 input->color[2] = pipeline_rgb[2];
316 input->display_color[0] = display_rgb[0];
317 input->display_color[1] = display_rgb[1];
318 input->display_color[2] = display_rgb[2];
319}
320
322{
323 if(IS_NULL_PTR(input)) return;
324 input->have_layer_coords = FALSE;
325 input->lx = 0.0f;
326 input->ly = 0.0f;
327 if(IS_NULL_PTR(self)) return;
328
329 float lx = 0.0f;
330 float ly = 0.0f;
331 if(dt_drawlayer_widget_to_layer_coords(self, input->wx, input->wy, &lx, &ly))
332 {
333 input->lx = lx;
334 input->ly = ly;
335 input->have_layer_coords = TRUE;
336 }
337
340 "[drawlayer] raw-input batch=%u event=%u pos=%u widget=(%.3f,%.3f) raster=(%.3f,%.3f) ok=%d\n",
341 input->stroke_batch, input->event_index, input->stroke_pos, input->wx, input->wy, input->lx, input->ly,
342 input->have_layer_coords ? 1 : 0);
343}
344
346{
347 if(IS_NULL_PTR(name)) return FALSE;
348 char tmp[DRAWLAYER_NAME_SIZE] = { 0 };
349 g_strlcpy(tmp, name, sizeof(tmp));
350 g_strstrip(tmp);
351 return tmp[0] != '\0';
352}
353
355 char *key, const size_t key_size)
356{
357 if(IS_NULL_PTR(key) || key_size == 0) return FALSE;
358 key[0] = '\0';
359 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe) || !iop_list) return FALSE;
360
361 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_iop_work_profile_info(self, iop_list);
362 if(IS_NULL_PTR(work_profile)) return FALSE;
363
364 g_snprintf(key, key_size, "%d|%d|%s", (int)work_profile->type, (int)work_profile->intent,
365 work_profile->filename);
366 return key[0] != '\0';
367}
368
376
377static void _destroy_process_scratch(gpointer data)
378{
380 if(IS_NULL_PTR(scratch)) return;
381 dt_drawlayer_cache_free_temp_buffer((void **)&scratch->layerbuf, "drawlayer process scratch");
382 dt_drawlayer_cache_free_temp_buffer((void **)&scratch->cl_background_rgba, "drawlayer process scratch");
383 dt_free(scratch);
384}
385
387
389{
392 if(!IS_NULL_PTR(scratch)) return scratch;
393
394 scratch = g_malloc0(sizeof(*scratch));
395 if(IS_NULL_PTR(scratch)) return NULL;
396 g_private_set(&_drawlayer_process_scratch_key, scratch);
397 return scratch;
398}
399
400
401
402
403
404gboolean dt_drawlayer_rekey_shared_base_patch(drawlayer_patch_t *patch, const int32_t imgid,
406{
407 /* Rekeying lets the same pixelpipe cache line keep its allocated storage and
408 * current in-memory pixels while the serialized module hash advances to a new
409 * history snapshot. This is the central piece that lets other pipelines find
410 * the newest base patch through the cache instead of through GUI internals. */
411 if(IS_NULL_PTR(patch) || IS_NULL_PTR(patch->cache_entry) || IS_NULL_PTR(params)) return FALSE;
412 const uint64_t new_hash = dt_drawlayer_params_cache_hash(imgid, params, patch->width, patch->height);
413 if(new_hash == patch->cache_hash) return TRUE;
414 if(dt_dev_pixelpipe_cache_rekey(patch->cache_hash, new_hash, patch->cache_entry) == 0)
415 {
416 patch->cache_hash = new_hash;
417 return TRUE;
418 }
419
420 /* Fallback path: another cache line already owns `new_hash` (or rekeying
421 * failed for any other reason). Publish the current authoritative pixels into
422 * that target key explicitly so parallel/headless pipelines resolving by the
423 * latest params hash still see up-to-date content.
424 *
425 * We intentionally keep `patch` bound to its original cache entry/hash here,
426 * because this module may hold additional explicit refs on that entry
427 * (`base_patch_loaded_ref`, stroke refs). Rebinding `patch` would desynchronize
428 * those ref counters. */
429 if(IS_NULL_PTR(patch->pixels) || patch->width <= 0 || patch->height <= 0) return FALSE;
430
431 drawlayer_patch_t published = { 0 };
432 int created = 0;
433 if(!dt_drawlayer_cache_patch_alloc_shared(&published, new_hash, (size_t)patch->width * patch->height,
434 patch->width, patch->height, "drawlayer sidecar cache", &created))
435 return FALSE;
436
438 memcpy(published.pixels, patch->pixels, (size_t)patch->width * patch->height * 4 * sizeof(float));
440#ifdef HAVE_OPENCL
442 published.cache_entry, -1);
443#endif
444 dt_drawlayer_cache_patch_clear(&published, "drawlayer patch");
447 "[drawlayer] cache rekey conflict old=%" PRIu64 " new=%" PRIu64 " -> published snapshot instead\n",
448 patch->cache_hash, new_hash);
449 return TRUE;
450}
451
453{
454 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->process.base_patch.cache_entry) || g->process.base_patch_loaded_ref) return;
455 dt_dev_pixelpipe_cache_ref_count_entry(TRUE, g->process.base_patch.cache_entry);
456 g->process.base_patch_loaded_ref = TRUE;
457}
458
460{
461 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->process.base_patch.cache_entry)) return;
462 dt_dev_pixelpipe_cache_ref_count_entry(TRUE, g->process.base_patch.cache_entry);
463 g->process.base_patch_stroke_refs++;
464}
465
467{
468 if(IS_NULL_PTR(g)) return;
469 if(IS_NULL_PTR(g->process.base_patch.cache_entry))
470 {
471 /* Keep refcount bookkeeping state coherent even if the cache entry has
472 * already been detached/cleared. This prevents stale counters from being
473 * applied to a future reused `g->process.base_patch` entry. */
474 g->process.base_patch_loaded_ref = FALSE;
475 g->process.base_patch_stroke_refs = 0;
476 return;
477 }
478
479 if(g->process.base_patch_loaded_ref)
480 {
481 dt_dev_pixelpipe_cache_ref_count_entry(FALSE, g->process.base_patch.cache_entry);
482 g->process.base_patch_loaded_ref = FALSE;
483 }
484
485 while(g->process.base_patch_stroke_refs > 0)
486 {
487 dt_dev_pixelpipe_cache_ref_count_entry(FALSE, g->process.base_patch.cache_entry);
488 g->process.base_patch_stroke_refs--;
489 }
490}
491
492static inline __attribute__((always_inline)) gboolean _refresh_piece_base_cache(dt_iop_module_t *self, dt_iop_drawlayer_data_t *data,
495{
496 if(IS_NULL_PTR(self) || IS_NULL_PTR(data) || IS_NULL_PTR(params) || IS_NULL_PTR(piece)) return FALSE;
497 if(params->layer_name[0] == '\0')
498 {
499 dt_drawlayer_cache_patch_clear(&data->process.base_patch, "drawlayer patch");
500 data->process.cache_valid = FALSE;
501 data->process.cache_dirty = FALSE;
503 data->process.cache_imgid = -1;
504 data->process.cache_layer_name[0] = '\0';
505 data->process.cache_layer_order = -1;
507 return TRUE;
508 }
509
510 /* Display heartbeats hit this path for every pipeline pass. When the current
511 * in-memory patch already matches the selected layer identity and geometry,
512 * stay entirely in RAM and skip sidecar probing altogether. Layer names are
513 * the stable identity here; page order is only a lookup hint and may drift
514 * after sidecar rewrites without invalidating the pixels already cached. */
517 && !g_strcmp0(data->process.cache_layer_name, params->layer_name)
518 && data->process.base_patch.width > 0 && data->process.base_patch.height > 0)
519 {
520 const uint64_t cached_hash = dt_drawlayer_params_cache_hash(pipe->dev->image_storage.id, params,
523 if(data->process.base_patch.cache_hash == cached_hash)
524 return TRUE;
525 }
526
527 char path[DT_PATH_MAX] = { 0 };
528 const gboolean have_sidecar_path = dt_drawlayer_io_sidecar_path(pipe->dev->image_storage.id, path, sizeof(path));
529 const gboolean have_sidecar = have_sidecar_path && g_file_test(path, G_FILE_TEST_EXISTS);
530 dt_drawlayer_io_layer_info_t info = { 0 };
531 if(have_sidecar)
533
534 int layer_width = 0;
535 int layer_height = 0;
536 const gboolean have_pipe_geometry = dt_drawlayer_resolve_layer_geometry(self, pipe, piece, &layer_width, &layer_height, NULL, NULL);
537
538 /* The canvas is the raw frame in every pipe, so the stored page's own dimensions are no longer
539 * consulted to decide it -- they used to be, because a thumbnail or export pipe resolved a
540 * SMALLER canvas than the darkroom did (both followed the module's stage frame, and those pipes
541 * render downscaled), and reinterpreting the page at that size recentred it. Anchoring removes
542 * the divergence rather than papering over it: one canvas size for every pipe, and the pipe's
543 * own scale is absorbed by the placement.
544 *
545 * A page authored before this change was written in the post-crop frame and will be read as
546 * though it were raw, so it appears shifted until repainted. That was the agreed trade; the
547 * alternative was a versioned sidecar and a migration pass. */
548 if(!have_pipe_geometry)
549 {
550 if(info.found && info.width > 0 && info.height > 0)
551 {
552 layer_width = (int)info.width;
553 layer_height = (int)info.height;
554 }
555 else
556 return FALSE;
557 }
558
559 const uint64_t base_hash = dt_drawlayer_params_cache_hash(pipe->dev->image_storage.id, params, layer_width, layer_height);
561 && data->process.base_patch.cache_hash == base_hash && data->process.cache_imgid == pipe->dev->image_storage.id
562 && !g_strcmp0(data->process.cache_layer_name, params->layer_name))
563 return TRUE;
564
565 dt_drawlayer_cache_patch_clear(&data->process.base_patch, "drawlayer patch");
566 data->process.cache_valid = FALSE;
567 data->process.cache_dirty = FALSE;
569 data->process.cache_imgid = -1;
570 data->process.cache_layer_name[0] = '\0';
571 data->process.cache_layer_order = -1;
573
574 if(layer_width > 0 && layer_height > 0)
575 {
576 int created = 0;
578 dt_drawlayer_params_cache_hash(pipe->dev->image_storage.id, params, layer_width,
581 "drawlayer sidecar cache", &created))
582 return FALSE;
583
584 if(!created)
585 {
586 data->process.cache_valid = TRUE;
588 g_strlcpy(data->process.cache_layer_name, params->layer_name, sizeof(data->process.cache_layer_name));
590 return TRUE;
591 }
592
593 gboolean warm_loaded = FALSE;
594 const gboolean load_attempted = have_sidecar && info.found;
595 if(load_attempted)
596 {
597 dt_drawlayer_io_patch_t warm_patch = { 0 };
599 warm_patch.x = data->process.base_patch.x;
600 warm_patch.y = data->process.base_patch.y;
601 warm_patch.width = data->process.base_patch.width;
602 warm_patch.height = data->process.base_patch.height;
603 warm_patch.pixels = data->process.base_patch.pixels;
605 layer_height, &warm_patch);
607 }
608
609 if(warm_loaded)
610 {
611 data->process.cache_valid = TRUE;
613 g_strlcpy(data->process.cache_layer_name, params->layer_name, sizeof(data->process.cache_layer_name));
614 data->process.cache_layer_order = info.found ? info.index : params->layer_order;
616 return TRUE;
617 }
618
619 /* A fresh arena page is NOT zeroed -- `dt_drawlayer_cache_patch_alloc_shared` allocates,
620 * it does not clear -- and below we are about to publish it as a valid canvas. When the
621 * load ran it already memset its destination before touching the file, so the only
622 * uncleared case is the one where it never ran. Clear exactly that one: the GUI-side twin
623 * (`dt_drawlayer_ensure_layer_cache`) has always cleared here, and the two loaders had
624 * diverged. Both key the same cache entry, so an uncleared page created by the pipeline is
625 * adopted by the GUI as the layer's content and a later sidecar write makes it permanent.
626 * Do NOT move this into the allocator: the rekey-conflict caller memcpys the whole buffer
627 * on its next statement and would pay a dead full-canvas memset. */
628 if(!load_attempted)
629 {
632 (size_t)layer_width * layer_height);
634 }
635
636 data->process.cache_valid = TRUE;
637 data->process.cache_dirty = FALSE;
640 g_strlcpy(data->process.cache_layer_name, params->layer_name, sizeof(data->process.cache_layer_name));
641 data->process.cache_layer_order = info.found ? info.index : params->layer_order;
642 return TRUE;
643 }
644 return FALSE;
645}
646
648static void _blend_layer_over_input(float *output, const float *input, const float *layerbuf, const size_t pixels,
649 const gboolean use_preview_bg, const float preview_bg)
650{
651 if(IS_NULL_PTR(output) || IS_NULL_PTR(input) || IS_NULL_PTR(layerbuf) || pixels == 0) return;
652
653 const dt_aligned_pixel_simd_t preview_base = { preview_bg, preview_bg, preview_bg, 1.0f };
655 for(size_t kk = 0; kk < pixels; kk++)
656 {
657 const float *base = input + 4 * kk;
658 const float *layer = layerbuf + 4 * kk;
659 float *pixel = output + 4 * kk;
660 const float src_alpha = _clamp01(layer[3]);
661 if(src_alpha > 1e-8f)
662 {
663 const dt_aligned_pixel_simd_t base_v = use_preview_bg ? preview_base : dt_load_simd_aligned(base);
664 dt_aligned_pixel_simd_t src_v = dt_load_simd_aligned(layer);
665 const float inv_alpha = 1.0f - src_alpha;
666 const dt_aligned_pixel_simd_t inv_alpha_v = { inv_alpha, inv_alpha, inv_alpha, inv_alpha };
667 src_v[3] = src_alpha;
668 dt_store_simd_aligned(pixel, src_v + base_v * inv_alpha_v);
669 }
670 else
671 {
672 const dt_aligned_pixel_simd_t base_v = use_preview_bg ? preview_base : dt_load_simd_aligned(base);
673 dt_store_simd_aligned(pixel, base_v);
674 }
675 }
676}
677
678#ifdef HAVE_OPENCL
685
686static gboolean _drawlayer_sync_host_image_to_device(const int devid, cl_mem device_image, void *host_pixels,
687 const int width, const int height, const int bpp,
688 const dt_drawlayer_damaged_rect_t *dirty_rect)
689{
690 if(IS_NULL_PTR(device_image) || IS_NULL_PTR(host_pixels) || width <= 0 || height <= 0 || bpp <= 0) return FALSE;
691
692 if(dirty_rect && dirty_rect->valid)
693 {
694 const int dirty_x0 = CLAMP(dirty_rect->nw[0], 0, width);
695 const int dirty_y0 = CLAMP(dirty_rect->nw[1], 0, height);
696 const int dirty_x1 = CLAMP(dirty_rect->se[0], 0, width);
697 const int dirty_y1 = CLAMP(dirty_rect->se[1], 0, height);
698 const int dirty_w = dirty_x1 - dirty_x0;
699 const int dirty_h = dirty_y1 - dirty_y0;
700 if(dirty_w > 0 && dirty_h > 0 && (dirty_w < width || dirty_h < height))
701 {
702 const size_t origin[] = { (size_t)dirty_x0, (size_t)dirty_y0, 0 };
703 const size_t region[] = { (size_t)dirty_w, (size_t)dirty_h, 1 };
704 char *host_origin = (char *)host_pixels + ((size_t)dirty_y0 * width + dirty_x0) * (size_t)bpp;
705 if(dt_opencl_write_host_to_device_raw(devid, host_origin, device_image, origin, region, width * bpp,
706 CL_TRUE) == CL_SUCCESS)
707 return TRUE;
708 }
709 }
710
711 if(dt_opencl_is_pinned_memory(device_image))
712 {
713 void *mapped = dt_opencl_map_image(devid, device_image, TRUE, CL_MAP_WRITE, width, height, bpp);
714 if(dt_opencl_unmap_mem_object(devid, device_image, mapped) == CL_SUCCESS)
715 return TRUE;
716 }
717
718 return dt_opencl_write_host_to_device(devid, host_pixels, device_image, width, height, bpp) == CL_SUCCESS;
719}
720
721static gboolean _drawlayer_acquire_source_image(const int devid, const float *layer_pixels,
722 dt_pixel_cache_entry_t *resolved_entry,
723 const gboolean realtime_reuse,
724 const int source_w, const int source_h,
727{
728 if(IS_NULL_PTR(source) || IS_NULL_PTR(layer_pixels) || source_w <= 0 || source_h <= 0) return FALSE;
729 *source = (drawlayer_cl_image_handle_t){ 0 };
730
731 /* Realtime redraws keep revisiting the same host-backed layer cache. Prefer a
732 * reusable device buffer first so the blend path can stay asynchronous instead
733 * of forcing a full `dt_opencl_finish()` before the base patch lock is released. */
734 if(realtime_reuse && resolved_entry)
735 {
736 const dt_iop_roi_t source_roi = { .width = source_w, .height = source_h };
737 gboolean reused_from_cache = FALSE;
738 source->mem = dt_dev_pixelpipe_cache_get_cl_buffer(devid, NULL, &source_roi, 4 * sizeof(float), NULL,
739 "drawlayer source", resolved_entry,
740 &reused_from_cache, NULL);
741 if(source->mem)
742 {
743 const dt_drawlayer_damaged_rect_t *dirty_rect
744 = (reused_from_cache && process) ? &process->cache_dirty_rect : NULL;
745 if(_drawlayer_sync_host_image_to_device(devid, source->mem, (void *)layer_pixels, source_w, source_h,
746 4 * sizeof(float), dirty_rect))
747 {
748 source->is_cached_device = TRUE;
750 return TRUE;
751 }
752
753 dt_dev_pixelpipe_cache_release_cl_buffer((void **)&source->mem, resolved_entry, NULL, FALSE);
754 }
755 }
756
758 (void *)layer_pixels, resolved_entry, devid, source_w, source_h,
759 4 * sizeof(float), CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, NULL);
760 if(source->mem)
761 {
762 source->is_pinned = TRUE;
763 return TRUE;
764 }
765
767 devid, source_w, source_h, 4 * sizeof(float), (void *)layer_pixels, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR);
768 if(source->mem)
769 {
770 if(_drawlayer_sync_host_image_to_device(devid, source->mem, (void *)layer_pixels, source_w, source_h,
771 4 * sizeof(float), NULL))
772 {
773 source->is_pinned = TRUE;
774 return TRUE;
775 }
776
778 source->mem = NULL;
779 }
780
781 source->mem = dt_opencl_copy_host_to_device(devid, (void *)layer_pixels, source_w, source_h, 4 * sizeof(float));
782 return !IS_NULL_PTR(source->mem);
783}
784
785static int _drawlayer_copy_or_resample_layer_roi(const int devid, cl_mem dev_source_rgba, cl_mem dev_layer_rgba,
786 const int source_w, const int source_h,
787 const dt_iop_roi_t *const target_roi,
788 const dt_iop_roi_t *const source_roi)
789{
790 const gboolean can_copy_crop
791 = (fabs(target_roi->scale - 1.0) <= 1e-6 && target_roi->x >= 0
792 && target_roi->y >= 0 && target_roi->x + target_roi->width <= source_w
793 && target_roi->y + target_roi->height <= source_h);
794 if(can_copy_crop)
795 {
796 size_t src_origin[3] = { (size_t)target_roi->x, (size_t)target_roi->y, 0 };
797 size_t dst_origin[3] = { 0, 0, 0 };
798 size_t region[3] = { (size_t)target_roi->width, (size_t)target_roi->height, 1 };
799 const int copy_err
800 = dt_opencl_enqueue_copy_image(devid, dev_source_rgba, dev_layer_rgba, src_origin, dst_origin, region);
801 if(copy_err == CL_SUCCESS) return CL_SUCCESS;
802 }
803
804 /* Force bilinear for the layer matte. The premultiplied-alpha brush stroke is
805 * stamped at full canvas resolution, so this resample only ever previews it at
806 * display scale (mostly downscaling). Bilinear is the only kernel here with no
807 * negative lobes: it cannot overshoot, so it never rings/halos at stroke edges
808 * nor pushes alpha out of [0,1] — unlike the user-pref default (Mitchell, whose
809 * negative excursion is small, ~3%, but non-zero) or the Catmull-Rom "bicubic".
810 * On minification dt_interpolation_resample widens the tap support, so bilinear
811 * acts as a clean area filter (no aliasing). */
813 return dt_interpolation_resample_cl(itor, devid, dev_layer_rgba, target_roi, dev_source_rgba, source_roi);
814}
815
816static gboolean _drawlayer_acquire_layer_image(const int devid, dt_pixel_cache_entry_t *resolved_entry,
817 const gboolean realtime_reuse,
818 cl_mem dev_source_rgba, const int source_w, const int source_h,
819 const dt_iop_roi_t *const target_roi,
820 const dt_iop_roi_t *const source_roi,
821 drawlayer_cl_image_handle_t *layer, int *err)
822{
823 if(IS_NULL_PTR(layer) || !err || IS_NULL_PTR(target_roi) || IS_NULL_PTR(source_roi)) return FALSE;
824 *layer = (drawlayer_cl_image_handle_t){ 0 };
825
826 if(realtime_reuse && resolved_entry)
827 {
828 layer->mem = dt_dev_pixelpipe_cache_get_cl_buffer(devid, NULL, target_roi, 4 * sizeof(float), NULL,
829 "drawlayer layer", resolved_entry, NULL,
830 dev_source_rgba);
831 layer->is_cached_device = (!IS_NULL_PTR(layer->mem));
832 }
833
834 if(IS_NULL_PTR(layer->mem))
835 {
836 layer->mem = dt_opencl_alloc_device(devid, target_roi->width, target_roi->height, 4 * sizeof(float));
837 }
838
839 if(IS_NULL_PTR(layer->mem))
840 {
841 *err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
842 return FALSE;
843 }
844
845 *err = _drawlayer_copy_or_resample_layer_roi(devid, dev_source_rgba, layer->mem, source_w, source_h, target_roi,
846 source_roi);
847 return *err == CL_SUCCESS;
848}
849
850static int _drawlayer_run_premult_over_kernel(const int devid, const int kernel_premult_over,
851 cl_mem dev_background, cl_mem dev_layer_rgba, cl_mem dev_out,
852 const int width, const int height, const int background_offset_x,
853 const int background_offset_y)
854{
855 const int offs[2] = { background_offset_x, background_offset_y };
856 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
857
858 int err = dt_opencl_set_kernel_arg(devid, kernel_premult_over, 0, sizeof(cl_mem), &dev_background);
859 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 1, sizeof(cl_mem), &dev_layer_rgba);
860 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 2, sizeof(cl_mem), &dev_out);
861 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 3, sizeof(int), &width);
862 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 4, sizeof(int), &height);
863 err |= dt_opencl_set_kernel_arg(devid, kernel_premult_over, 5, sizeof(offs), offs);
864 if(err != CL_SUCCESS) return err;
865
866 return dt_opencl_enqueue_kernel_2d(devid, kernel_premult_over, sizes);
867}
868
869/* Influence radius (in target/display pixels) of a changed source pixel through
870 * the resampling interpolation. interpolation.c maps a target output pixel to a
871 * source window whose half-width is ~support/scale source pixels; transformed
872 * back to target space that footprint is ~support pixels regardless of scale, so
873 * a fixed, generous target-space margin keeps partial-resample edges seamless. */
874#define DRAWLAYER_RESAMPLE_DAMAGE_MARGIN 8
875
876/* Map a layer-space (source) damage rectangle to the output (target) display
877 * window it affects, padded by the interpolation support so resampled edges stay
878 * seamless. Returns TRUE and fills *out (nw inclusive / se exclusive, clamped to
879 * the target ROI, in target-local coordinates) only when the mapped window is a
880 * strict sub-rect of the target worth a partial composite. */
882 const dt_iop_roi_t *const target_roi,
883 const dt_iop_roi_t *const source_roi,
885{
886 if(IS_NULL_PTR(src_damage) || !src_damage->valid || IS_NULL_PTR(target_roi) || IS_NULL_PTR(source_roi)
887 || IS_NULL_PTR(out))
888 return FALSE;
889 if(target_roi->width <= 0 || target_roi->height <= 0 || target_roi->scale <= 0.0f) return FALSE;
890
891 const double scale = target_roi->scale;
892 const int margin = DRAWLAYER_RESAMPLE_DAMAGE_MARGIN;
893 /* out = (source + source_roi.x) * scale - target_roi.x (inverse of the
894 * interpolation source mapping source = (target_roi.x + out)/scale - source_roi.x). */
895 const double sx0 = (double)src_damage->nw[0] + source_roi->x;
896 const double sy0 = (double)src_damage->nw[1] + source_roi->y;
897 const double sx1 = (double)src_damage->se[0] + source_roi->x;
898 const double sy1 = (double)src_damage->se[1] + source_roi->y;
899
900 int tx0 = (int)floor(sx0 * scale - target_roi->x) - margin;
901 int ty0 = (int)floor(sy0 * scale - target_roi->y) - margin;
902 int tx1 = (int)ceil(sx1 * scale - target_roi->x) + margin;
903 int ty1 = (int)ceil(sy1 * scale - target_roi->y) + margin;
904
905 tx0 = CLAMP(tx0, 0, target_roi->width);
906 ty0 = CLAMP(ty0, 0, target_roi->height);
907 tx1 = CLAMP(tx1, 0, target_roi->width);
908 ty1 = CLAMP(ty1, 0, target_roi->height);
909
910 if(tx1 <= tx0 || ty1 <= ty0) return FALSE;
911 /* Whole-frame damage is not worth the partial path's bookkeeping. */
912 if(tx0 == 0 && ty0 == 0 && tx1 == target_roi->width && ty1 == target_roi->height) return FALSE;
913
914 *out = (dt_drawlayer_damaged_rect_t){ .valid = TRUE, .nw = { tx0, ty0 }, .se = { tx1, ty1 } };
915 return TRUE;
916}
917
945
947{
948 if(req->devid < 0 || IS_NULL_PTR(req->dev_out) || IS_NULL_PTR(req->dev_in) || IS_NULL_PTR(req->scratch) || IS_NULL_PTR(req->layer_pixels) || req->source_w <= 0
949 || req->source_h <= 0 || !req->target_roi || req->target_roi->width <= 0 || req->target_roi->height <= 0)
950 return FALSE;
951 if(req->kernel_premult_over < 0) return FALSE;
952
953 dt_pixel_cache_entry_t *resolved_entry = req->source_entry;
954 gboolean resolved_entry_ref = FALSE;
955 if(req->realtime_reuse && !resolved_entry)
956 {
957 resolved_entry
959 resolved_entry_ref = (!IS_NULL_PTR(resolved_entry));
960 }
961
962 /* Realtime partial composite: when the caller validated that req->dev_out still
963 * holds this node's previous full composite (same buffer, hash and geometry)
964 * and only the painted sub-rect of the layer changed, refresh just that window
965 * instead of re-resampling the whole display. The source-damage rectangle must
966 * be snapshotted here because _drawlayer_acquire_source_image() consumes (and
967 * resets) req->process->cache_dirty_rect while uploading the dirty source region. */
968 dt_drawlayer_damaged_rect_t target_damage = { 0 };
969 const gboolean partial = req->allow_partial && !req->use_preview_bg && req->process
971 req->source_roi, &target_damage);
972
973 /* Localise the composite's cost. Measured on a 10.4 s stroke, `Drawing' cost 37.5 ms a frame
974 * with a 23 ms FLOOR -- something runs unconditionally -- against a total frame of 109.5 ms.
975 * A per-module total cannot say whether that is the source upload, the resample or the
976 * kernel, and the three have very different fixes. */
977 const gboolean trace_stages = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
978 const gint64 stage_t0 = trace_stages ? g_get_monotonic_time() : 0;
979 gint64 stage_source = stage_t0;
980 gint64 stage_layer = stage_t0;
981
982 drawlayer_cl_image_handle_t source = { 0 };
983 drawlayer_cl_image_handle_t layer = { 0 };
984 cl_mem dev_layer_partial = NULL;
985 cl_mem dev_bg_partial = NULL;
986 cl_mem dev_out_partial = NULL;
987 cl_mem dev_background = NULL;
988 int err = CL_SUCCESS;
989 int result = FALSE;
990 if(!_drawlayer_acquire_source_image(req->devid, req->layer_pixels, resolved_entry, req->realtime_reuse,
991 req->source_w, req->source_h, req->process, &source))
992 goto cleanup;
993 if(trace_stages) stage_source = g_get_monotonic_time();
994
995 if(partial)
996 {
997 const int dw = target_damage.se[0] - target_damage.nw[0];
998 const int dh = target_damage.se[1] - target_damage.nw[1];
999 size_t win_origin[3] = { (size_t)target_damage.nw[0], (size_t)target_damage.nw[1], 0 };
1000 size_t zero_origin[3] = { 0, 0, 0 };
1001 size_t win_region[3] = { (size_t)dw, (size_t)dh, 1 };
1002
1003 /* All three windows are damage-sized device req->scratch buffers. We keep the
1004 * unmodified full-frame `blendop_premult_over` kernel (local 0,0 coords) and
1005 * stage the sub-window through copies so the GPU kernel ABI never changes:
1006 * layer_partial = resample(source, damaged display window)
1007 * bg_partial = req->dev_in[window] (background slice)
1008 * out_partial = over(layer_partial, bg_partial) (full-window kernel)
1009 * req->dev_out[window] = out_partial (refresh in place)
1010 * The rest of req->dev_out keeps the previous full composite. */
1011 dev_layer_partial = dt_opencl_alloc_device(req->devid, dw, dh, 4 * sizeof(float));
1012 dev_bg_partial = dt_opencl_alloc_device(req->devid, dw, dh, 4 * sizeof(float));
1013 dev_out_partial = dt_opencl_alloc_device(req->devid, dw, dh, 4 * sizeof(float));
1014 if(IS_NULL_PTR(dev_layer_partial) || IS_NULL_PTR(dev_bg_partial) || IS_NULL_PTR(dev_out_partial))
1015 {
1016 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1017 goto cleanup;
1018 }
1019 /* Resample only the damaged display window from the full-res source. The
1020 * sub-window's global target origin maps back to the correct source region
1021 * through the unchanged interpolation source mapping. */
1022 const dt_iop_roi_t sub_target_roi = {
1023 .x = req->target_roi->x + target_damage.nw[0],
1024 .y = req->target_roi->y + target_damage.nw[1],
1025 .width = dw,
1026 .height = dh,
1027 .scale = req->target_roi->scale,
1028 };
1029 err = _drawlayer_copy_or_resample_layer_roi(req->devid, source.mem, dev_layer_partial, req->source_w, req->source_h,
1030 &sub_target_roi, req->source_roi);
1031 if(err != CL_SUCCESS) goto cleanup;
1032
1033 err = dt_opencl_enqueue_copy_image(req->devid, req->dev_in, dev_bg_partial, win_origin, zero_origin, win_region);
1034 if(err != CL_SUCCESS) goto cleanup;
1035
1036 err = _drawlayer_run_premult_over_kernel(req->devid, req->kernel_premult_over, dev_bg_partial, dev_layer_partial,
1037 dev_out_partial, dw, dh, 0, 0);
1038 if(err != CL_SUCCESS) goto cleanup;
1039
1040 err = dt_opencl_enqueue_copy_image(req->devid, dev_out_partial, req->dev_out, zero_origin, win_origin, win_region);
1041 if(err != CL_SUCCESS) goto cleanup;
1042
1044 dt_print(DT_DEBUG_PERF, "[drawlayer] partial composite window=%dx%d at (%d,%d) of %dx%d\n", dw, dh,
1045 target_damage.nw[0], target_damage.nw[1], req->target_roi->width, req->target_roi->height);
1046
1047 if(source.is_pinned)
1048 {
1049 if(!dt_opencl_finish(req->devid))
1050 {
1051 err = -1;
1052 goto cleanup;
1053 }
1054 }
1055 result = TRUE;
1056 if(trace_stages)
1058 "[drawlayer] composite partial win=%dx%d src=%.2f ms kernel=%.2f ms total=%.2f ms\n",
1059 dw, dh, (stage_source - stage_t0) / 1000.0,
1060 (g_get_monotonic_time() - stage_source) / 1000.0,
1061 (g_get_monotonic_time() - stage_t0) / 1000.0);
1062 goto cleanup;
1063 }
1064
1065 if(!_drawlayer_acquire_layer_image(req->devid, resolved_entry, req->realtime_reuse, source.mem, req->source_w,
1066 req->source_h, req->target_roi, req->source_roi, &layer, &err))
1067 goto cleanup;
1068 if(trace_stages) stage_layer = g_get_monotonic_time();
1069
1070 if(req->use_preview_bg)
1071 {
1072 const size_t out_pixels = (size_t)req->target_roi->width * req->target_roi->height;
1074 &req->scratch->cl_background_rgba_pixels, out_pixels,
1075 "drawlayer req->process req->scratch");
1076 if(IS_NULL_PTR(background)) goto cleanup;
1077 __OMP_PARALLEL_FOR__(if(out_pixels > 4096))
1078 for(size_t kk = 0; kk < out_pixels; kk++)
1079 {
1080 float *pixel = background + 4 * kk;
1081 pixel[0] = req->preview_bg;
1082 pixel[1] = req->preview_bg;
1083 pixel[2] = req->preview_bg;
1084 pixel[3] = 1.0f;
1085 }
1087 background, NULL, req->devid, req->target_roi->width, req->target_roi->height,
1088 4 * sizeof(float), CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, NULL);
1089 if(IS_NULL_PTR(dev_background)) goto cleanup;
1090 }
1091 else
1092 dev_background = req->dev_in;
1093
1094 err = _drawlayer_run_premult_over_kernel(req->devid, req->kernel_premult_over, dev_background, layer.mem, req->dev_out,
1095 req->target_roi->width, req->target_roi->height, 0, 0);
1096 if(err != CL_SUCCESS) goto cleanup;
1097 const gint64 stage_kernel = trace_stages ? g_get_monotonic_time() : 0;
1098
1099 /* The realtime display source is the host-backed full-resolution cache.
1100 * When imported as CL_MEM_USE_HOST_PTR, queued GPU reads may still touch that
1101 * host memory after process_cl() returns. Finish the queue before releasing
1102 * the cache lock whenever the layer source is host-backed. */
1103 if(source.is_pinned)
1104 {
1105 if(!dt_opencl_finish(req->devid))
1106 {
1107 err = -1;
1108 goto cleanup;
1109 }
1110 }
1111
1112 result = TRUE;
1113 if(trace_stages)
1115 "[drawlayer] composite FULL out=%dx%d src=%dx%d src_up=%.2f ms resample=%.2f ms "
1116 "kernel=%.2f ms finish=%.2f ms total=%.2f ms pinned=%d\n",
1117 req->target_roi->width, req->target_roi->height, req->source_w, req->source_h,
1118 (stage_source - stage_t0) / 1000.0, (stage_layer - stage_source) / 1000.0,
1119 (stage_kernel - stage_layer) / 1000.0,
1120 (g_get_monotonic_time() - stage_kernel) / 1000.0,
1121 (g_get_monotonic_time() - stage_t0) / 1000.0, source.is_pinned ? 1 : 0);
1122
1123cleanup:
1124 if(dev_layer_partial) dt_opencl_release_mem_object(dev_layer_partial);
1125 if(dev_bg_partial) dt_opencl_release_mem_object(dev_bg_partial);
1126 if(dev_out_partial) dt_opencl_release_mem_object(dev_out_partial);
1127 if(req->use_preview_bg)
1129 (void **)&dev_background);
1130 if(layer.mem && layer.mem != source.mem)
1131 {
1132 if(layer.is_cached_device && resolved_entry)
1133 dt_dev_pixelpipe_cache_release_cl_buffer((void **)&layer.mem, resolved_entry, NULL, TRUE);
1134 else
1136 }
1137 if(source.is_pinned)
1138 dt_dev_pixelpipe_cache_put_pinned_image((void *)req->layer_pixels, resolved_entry,
1139 (void **)&source.mem);
1140 else if(source.is_cached_device && resolved_entry)
1141 dt_dev_pixelpipe_cache_release_cl_buffer((void **)&source.mem, resolved_entry, NULL, TRUE);
1142 else if(source.mem)
1144 if(resolved_entry_ref)
1146
1147 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[drawlayer] process_cl blend path failed: %d\n\n", err);
1148
1149 return result;
1150}
1151#endif
1152
1153static gboolean _profile_key_is_sane(const char *value)
1154{
1155 if(IS_NULL_PTR(value) || value[0] == '\0') return FALSE;
1156
1157 int separators = 0;
1158 for(const unsigned char *c = (const unsigned char *)value; *c; c++)
1159 {
1160 if(*c == '|')
1161 separators++;
1162 else if(!g_ascii_isprint(*c))
1163 return FALSE;
1164 }
1165
1166 return separators >= 2;
1167}
1168
1170{
1171 if(IS_NULL_PTR(path) || path[0] == '\0' || !g_file_test(path, G_FILE_TEST_EXISTS)) return 0;
1172
1173 GStatBuf st = { 0 };
1174 if(g_stat(path, &st) != 0) return 0;
1175 return (int64_t)st.st_mtime;
1176}
1177
1178static void _ensure_cursor_stamp_surface(dt_iop_module_t *self, const float widget_radius, const float opacity,
1179 const float hardness)
1180{
1182 if(IS_NULL_PTR(g) || widget_radius <= 0.0f) return;
1183
1184 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1185 float display_rgb[3] = { 0.0f };
1187 const int shape = dt_drawlayer_conf_brush_shape();
1188 const int size_px = MAX(2, (int)ceil((2.0f * widget_radius + 2.0f) * ppd));
1189
1190 const gboolean needs_rebuild
1191 = !g->ui.cursor_surface || g->ui.cursor_surface_size != size_px || fabs(g->ui.cursor_surface_ppd - ppd) > 1e-9
1192 || fabsf(g->ui.cursor_radius - widget_radius) > 1e-3f || fabsf(g->ui.cursor_opacity - opacity) > 1e-6f
1193 || fabsf(g->ui.cursor_hardness - hardness) > 1e-6f || g->ui.cursor_shape != shape
1194 || fabsf(g->ui.cursor_color[0] - display_rgb[0]) > 1e-6f || fabsf(g->ui.cursor_color[1] - display_rgb[1]) > 1e-6f
1195 || fabsf(g->ui.cursor_color[2] - display_rgb[2]) > 1e-6f;
1196 if(!needs_rebuild) return;
1197
1199 g->ui.cursor_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size_px, size_px);
1200 if(cairo_surface_status(g->ui.cursor_surface) != CAIRO_STATUS_SUCCESS)
1201 {
1203 return;
1204 }
1205 cairo_surface_set_device_scale(g->ui.cursor_surface, ppd, ppd);
1206
1207 unsigned char *data = cairo_image_surface_get_data(g->ui.cursor_surface);
1208 const int stride = cairo_image_surface_get_stride(g->ui.cursor_surface);
1209 memset(data, 0, (size_t)stride * size_px);
1210
1212 .radius = fmaxf(widget_radius * (float)ppd, 0.5f),
1213 .shape = shape,
1214 .hardness = hardness,
1215 .opacity = opacity,
1216 .display_color = { display_rgb[0], display_rgb[1], display_rgb[2] },
1217 };
1218
1219 const float half = 0.5f * (float)size_px;
1220 dt_drawlayer_brush_rasterize_dab_argb8(&dab, data, size_px, size_px, stride, half, half, 1.0f);
1221 cairo_surface_mark_dirty(g->ui.cursor_surface);
1222
1223 g->ui.cursor_surface_size = size_px;
1224 g->ui.cursor_surface_ppd = ppd;
1225 g->ui.cursor_radius = widget_radius;
1226 g->ui.cursor_opacity = opacity;
1227 g->ui.cursor_hardness = hardness;
1228 g->ui.cursor_shape = shape;
1229 g->ui.cursor_color[0] = display_rgb[0];
1230 g->ui.cursor_color[1] = display_rgb[1];
1231 g->ui.cursor_color[2] = display_rgb[2];
1232}
1233
1234static drawlayer_wait_dialog_t _show_drawlayer_wait_dialog(const char *title, const char *message)
1235{
1236 drawlayer_wait_dialog_t wait = { 0 };
1237 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui()) || IS_NULL_PTR(title) || !title[0] || IS_NULL_PTR(message) || !message[0]) return wait;
1238
1239 GtkWidget *dialog = gtk_dialog_new();
1241 if(main) gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main));
1242 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1243 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
1244 gtk_window_set_deletable(GTK_WINDOW(dialog), FALSE);
1245 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1246 gtk_window_set_title(GTK_WINDOW(dialog), title);
1247 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
1248
1249 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1250 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1251 GtkWidget *spinner = gtk_spinner_new();
1252 GtkWidget *label = gtk_label_new(message);
1253 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
1254 gtk_box_pack_start(GTK_BOX(box), spinner, FALSE, FALSE, 0);
1255 gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
1256 gtk_container_set_border_width(GTK_CONTAINER(box), DT_PIXEL_APPLY_DPI(12));
1257 gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
1258 gtk_spinner_start(GTK_SPINNER(spinner));
1259 gtk_widget_show_all(dialog);
1260 gtk_widget_show_now(dialog);
1261 gtk_window_present(GTK_WINDOW(dialog));
1262 GdkDisplay *display = gtk_widget_get_display(dialog);
1263 if(display) gdk_display_flush(display);
1264 /* `gui_focus()` / explicit sidecar save show this dialog and then immediately
1265 * enter synchronous worker-drain / TIFF-write code on the UI thread. Give GTK
1266 * a couple of non-blocking iterations so the modal maps and paints before that
1267 * blocking section starts, otherwise it may never become visible at all. */
1268 for(int k = 0; k < 2; k++)
1269 gtk_main_iteration_do(FALSE);
1270 wait.dialog = dialog;
1271 return wait;
1272}
1273
1279
1280static gboolean _drawlayer_modal_wait_tick(gpointer user_data)
1281{
1283 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->loop)) return G_SOURCE_REMOVE;
1284 if(!(state->g && dt_drawlayer_worker_any_active(state->g->stroke.worker)))
1285 {
1286 g_main_loop_quit(state->loop);
1287 return G_SOURCE_REMOVE;
1288 }
1289
1290 return G_SOURCE_CONTINUE;
1291}
1292
1294 const char *title, const char *message)
1295{
1296 if(!(g && dt_drawlayer_worker_any_active(g->stroke.worker))) return;
1297
1299 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
1301 .loop = loop,
1302 .g = g,
1303 };
1304 const guint source_id = g_timeout_add(16, _drawlayer_modal_wait_tick, &state);
1305
1306 if(g && dt_drawlayer_worker_any_active(g->stroke.worker))
1307 g_main_loop_run(loop);
1308
1309 if(source_id) g_source_remove(source_id);
1310 if(wait.dialog)
1311 {
1312 gtk_widget_destroy(wait.dialog);
1313 wait.dialog = NULL;
1314 }
1315 g_main_loop_unref(loop);
1316}
1317
1318static void _show_drawlayer_modal_message(const GtkMessageType type, const char *primary, const char *secondary)
1319{
1320 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui()) || IS_NULL_PTR(primary) || primary[0] == '\0') return;
1321
1322 GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(dt_gui_main_window()),
1323 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1324 type, GTK_BUTTONS_OK, "%s", primary);
1325 if(secondary && secondary[0] != '\0')
1326 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
1327 gtk_dialog_run(GTK_DIALOG(dialog));
1328 gtk_widget_destroy(dialog);
1329}
1330
1331static gboolean _prompt_layer_name_dialog(const char *title, const char *message, const char *initial_name,
1332 char *name, const size_t name_size)
1333{
1334 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui()) || IS_NULL_PTR(title) || title[0] == '\0' || IS_NULL_PTR(name) || name_size == 0) return FALSE;
1335
1336 GtkWidget *dialog = gtk_dialog_new_with_buttons(
1337 title, GTK_WINDOW(dt_gui_main_window()),
1338 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, _("Cancel"), GTK_RESPONSE_CANCEL,
1339 _("Confirm"), GTK_RESPONSE_ACCEPT, NULL);
1340 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1341 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1342 GtkWidget *entry = gtk_entry_new();
1343 if(!IS_NULL_PTR(message) && message[0] != '\0')
1344 {
1345 GtkWidget *label = gtk_label_new(message);
1346 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
1347 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1348 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
1349 }
1350 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
1351 if(initial_name && initial_name[0] != '\0') gtk_entry_set_text(GTK_ENTRY(entry), initial_name);
1352 gtk_box_pack_start(GTK_BOX(box), entry, FALSE, FALSE, 0);
1353 gtk_container_set_border_width(GTK_CONTAINER(box), DT_PIXEL_APPLY_DPI(12));
1354 gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
1355 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1356 gtk_widget_show_all(dialog);
1357
1358 gboolean accepted = FALSE;
1359 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1360 {
1361 _sanitize_requested_layer_name(gtk_entry_get_text(GTK_ENTRY(entry)), name, name_size);
1363 }
1364
1365 gtk_widget_destroy(dialog);
1366 return accepted;
1367}
1368
1369static gboolean _color_picker_set_from_position(dt_iop_module_t *self, const float x, const float y)
1370{
1372 if(IS_NULL_PTR(g) || !g->ui.widgets || IS_NULL_PTR(g->controls.color)) return FALSE;
1373
1374 float display_rgb[3] = { 0.0f };
1375 if(!dt_drawlayer_widgets_update_from_picker_position(g->ui.widgets, g->controls.color, x, y, display_rgb)) return FALSE;
1377 return TRUE;
1378}
1379
1380static gboolean _color_picker_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1381{
1382 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1384 if(IS_NULL_PTR(g) || !g->ui.widgets) return FALSE;
1385 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1386 return dt_drawlayer_widgets_draw_picker(g->ui.widgets, widget, cr, ppd);
1387}
1388
1389static gboolean _color_swatch_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1390{
1391 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1393 if(IS_NULL_PTR(g) || !g->ui.widgets) return FALSE;
1394 return dt_drawlayer_widgets_draw_swatch(g->ui.widgets, widget, cr);
1395}
1396
1397static gboolean _color_swatch_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1398{
1399 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1401 if(IS_NULL_PTR(g) || !g->ui.widgets || !widget || IS_NULL_PTR(event) || event->button != 1) return FALSE;
1402
1403 float display_rgb[3] = { 0.0f };
1404 if(!dt_drawlayer_widgets_pick_history_color(g->ui.widgets, widget, event->x, event->y, display_rgb)) return FALSE;
1406 return TRUE;
1407}
1408
1410{
1412 if(IS_NULL_PTR(g) || !g->ui.widgets || IS_NULL_PTR(g->controls.brush_shape)) return;
1413
1417 gtk_widget_queue_draw(g->controls.brush_shape);
1418}
1419
1420static gboolean _brush_profile_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1421{
1422 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1424 if(IS_NULL_PTR(g) || !g->ui.widgets) return FALSE;
1425 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1426 return dt_drawlayer_widgets_draw_brush_profiles(g->ui.widgets, widget, cr, ppd);
1427}
1428
1429static gboolean _brush_profile_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1430{
1431 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1433 if(IS_NULL_PTR(g) || !g->ui.widgets || !widget || IS_NULL_PTR(event) || event->button != 1) return FALSE;
1434
1436 if(!dt_drawlayer_widgets_pick_brush_profile(g->ui.widgets, widget, event->x, event->y, &shape)) return FALSE;
1437
1442 gtk_widget_queue_draw(widget);
1443 return TRUE;
1444}
1445
1447 const float working_rgb[3], float display_rgb[3])
1448{
1449 if(IS_NULL_PTR(working_rgb) || !display_rgb) return FALSE;
1450
1451 const float gain = exp2f(dt_drawlayer_conf_hdr_exposure());
1452 const float inv_gain = (gain > 0.0f) ? 1.0f / gain : 1.0f;
1453 const float preview_rgb[3] = {
1454 working_rgb[0] * inv_gain,
1455 working_rgb[1] * inv_gain,
1456 working_rgb[2] * inv_gain,
1457 };
1458
1459 display_rgb[0] = _clamp01(preview_rgb[0]);
1460 display_rgb[1] = _clamp01(preview_rgb[1]);
1461 display_rgb[2] = _clamp01(preview_rgb[2]);
1462
1463 if(IS_NULL_PTR(self) || IS_NULL_PTR(pipe)) return TRUE;
1464
1465 const dt_iop_order_iccprofile_info_t *const work_profile
1466 = self->dev ? dt_ioppr_get_iop_work_profile_info(self, self->dev->iop) : NULL;
1468 if(IS_NULL_PTR(work_profile) || !display_profile) return TRUE;
1469
1470 float in[4] = { preview_rgb[0], preview_rgb[1], preview_rgb[2], 0.0f };
1471 float out[4] = { preview_rgb[0], preview_rgb[1], preview_rgb[2], 0.0f };
1472 dt_ioppr_transform_image_colorspace_rgb(in, out, 1, 1, work_profile, display_profile, "drawlayer picked color");
1473 display_rgb[0] = _clamp01(out[0]);
1474 display_rgb[1] = _clamp01(out[1]);
1475 display_rgb[2] = _clamp01(out[2]);
1476 return TRUE;
1477}
1478
1490static gboolean _brush_paints_color(void)
1491{
1493}
1494
1497{
1498 (void)picker;
1499 (void)piece;
1500 if(IS_NULL_PTR(self) || dt_gui_widgets_suppressed()) return;
1502 const float *picked = (source == DRAWLAYER_PICK_SOURCE_OUTPUT) ? self->picked_output_color : self->picked_color;
1503 const float *picked_min
1505 const float *picked_max
1507 if(picked_max[0] < picked_min[0]) return;
1508
1509 float display_rgb[3] = { 0.0f };
1510 if(!_working_rgb_to_display_rgb(self, pipe, picked, display_rgb)) return;
1512}
1513
1514static gboolean _color_picker_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1515{
1516 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1517 if(IS_NULL_PTR(event) || event->button != 1) return FALSE;
1518 (void)widget;
1519 return _color_picker_set_from_position(self, event->x, event->y);
1520}
1521
1522static gboolean _color_picker_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1523{
1524 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1526 (void)widget;
1527 (void)event;
1528 if(!IS_NULL_PTR(g) && g->ui.widgets)
1529 {
1530 float display_rgb[3] = { 0.0f };
1531 if(dt_drawlayer_widgets_finish_picker_drag(g->ui.widgets, display_rgb))
1533 }
1534 return FALSE;
1535}
1536
1537static gboolean _color_picker_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1538{
1539 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1541 (void)widget;
1542 if(IS_NULL_PTR(g) || !g->ui.widgets || IS_NULL_PTR(event) || !dt_drawlayer_widgets_is_picker_dragging(g->ui.widgets)) return FALSE;
1543 return _color_picker_set_from_position(self, event->x, event->y);
1544}
1545
1547{
1548 if(IS_NULL_PTR(params)) return;
1549
1550 if(memchr(params->layer_name, '\0', sizeof(params->layer_name)) == NULL)
1551 memset(params->layer_name, 0, sizeof(params->layer_name));
1552 else
1553 params->layer_name[sizeof(params->layer_name) - 1] = '\0';
1554
1555 char sanitized_layer_name[DRAWLAYER_NAME_SIZE] = { 0 };
1556 _sanitize_requested_layer_name(params->layer_name, sanitized_layer_name, sizeof(sanitized_layer_name));
1557 if(g_strcmp0(params->layer_name, sanitized_layer_name))
1558 g_strlcpy(params->layer_name, sanitized_layer_name, sizeof(params->layer_name));
1559
1560 if(memchr(params->work_profile, '\0', sizeof(params->work_profile)) == NULL)
1561 memset(params->work_profile, 0, sizeof(params->work_profile));
1562 else
1563 params->work_profile[sizeof(params->work_profile) - 1] = '\0';
1564
1566 {
1567 params->layer_name[0] = '\0';
1568 params->layer_order = -1;
1570 }
1571 if(params->layer_order < -1) params->layer_order = -1;
1573 memset(params->work_profile, 0, sizeof(params->work_profile));
1574
1575 // Freshly enabled or migrated instances without a concrete sidecar page should adopt the current profile.
1576 if(params->layer_order < 0 && params->stroke_commit_hash == 0u)
1577 memset(params->work_profile, 0, sizeof(params->work_profile));
1578
1579 if(params->work_profile[0] == '\0' && !IS_NULL_PTR(self) && self->dev)
1580 {
1581 char current_profile[DRAWLAYER_PROFILE_SIZE] = { 0 };
1582 if(dt_drawlayer_current_work_profile_key(self, self->dev->iop, self->dev->pipe, current_profile,
1583 sizeof(current_profile)))
1584 g_strlcpy(params->work_profile, current_profile, sizeof(params->work_profile));
1585 }
1586}
1587
1588static void _sanitize_requested_layer_name(const char *requested, char *name, const size_t name_size)
1589{
1590 if(IS_NULL_PTR(name) || name_size == 0) return;
1591 name[0] = '\0';
1592 if(!(!IS_NULL_PTR(requested) && requested[0])) return;
1593
1594 gboolean last_was_space = FALSE;
1595 size_t out = 0;
1596 for(size_t in = 0; requested[in] != '\0' && out + 1 < name_size; in++)
1597 {
1598 const unsigned char ch = (unsigned char)requested[in];
1599 if(g_ascii_isspace(ch))
1600 {
1601 if(out > 0 && !last_was_space)
1602 {
1603 name[out++] = ' ';
1604 last_was_space = TRUE;
1605 }
1606 continue;
1607 }
1608
1609 name[out++] = (char)ch;
1610 last_was_space = FALSE;
1611 }
1612 name[out] = '\0';
1613 g_strstrip(name);
1614}
1615
1616
1617
1618// Find the cached working layer if any
1619static inline __attribute__((always_inline)) gboolean _update_runtime_state(const drawlayer_runtime_request_t *request,
1621{
1622 if(source) *source = (dt_drawlayer_runtime_source_t){ 0 };
1623 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)
1624 || !request->runtime_params || !source)
1625 return FALSE;
1626
1627 const dt_dev_pixelpipe_t *const pipe = request->pipe;
1628 dt_drawlayer_runtime_manager_t *const manager = request->manager;
1630
1631 if(process && process->cache_valid && process->base_patch.pixels && process->cache_imgid == pipe->dev->image_storage.id)
1632 {
1633 dt_iop_roi_t process_roi = request->roi_out ? *request->roi_out : *request->roi_in;
1634 const dt_iop_roi_t source_full_roi = {
1635 .x = 0,
1636 .y = 0,
1637 .width = process->base_patch.width,
1638 .height = process->base_patch.height,
1639 .scale = 1.0f,
1640 };
1641
1642 /* The canvas has the raw image's dimensions and this module's frame is a window into it, at
1643 * the position the size fold gives that window (piece->buf_in.x/y -- a crop records itself
1644 * there). So the window this render reads is the requested output window shifted by that
1645 * origin -- and by nothing else. No scale: canvas and frame share the pixel pitch. No
1646 * rotation: the flip is already in the canvas's orientation.
1647 *
1648 * dt_interpolation_resample() reads source pixel ((x + i) / scale) for output column i, and
1649 * target_roi->x is in those same scaled units, which is why the offset is multiplied by the
1650 * render's own scale here and by nothing else.
1651 *
1652 * Before this the canvas WAS the module's frame and target_roi indexed it directly -- so the
1653 * canvas changed size with every crop and the authored raster was re-fitted onto the new
1654 * frame. The paint moved and stretched with the crop; that is the bug this fixes.
1655 *
1656 * KNOWN LIMIT: when the frame is LARGER than the canvas (a perspective correction expands it)
1657 * the offset goes negative and the resampler clamps to the canvas edge instead of reading
1658 * transparency, so paint touching the very edge of the canvas smears outward into the added
1659 * margin. Paint that does not reach the edge is unaffected. */
1661 if(dt_drawlayer_raw_placement_for_frame(request->self, pipe, &request->piece->buf_in, &placement))
1662 {
1663 process_roi.x += (int)lroundf((float)placement.offset_x * process_roi.scale);
1664 process_roi.y += (int)lroundf((float)placement.offset_y * process_roi.scale);
1665 }
1667 source->pixels = process->base_patch.pixels;
1668 source->cache_entry = process->base_patch.cache_entry;
1669 source->width = process->base_patch.width;
1670 source->height = process->base_patch.height;
1671 source->source_roi = source_full_roi;
1672 source->target_roi = process_roi;
1674 if(manager)
1675 {
1679 source->tracked_read_lock = TRUE;
1681 TRUE);
1682 }
1683 return TRUE;
1684 }
1685
1686 return FALSE;
1687}
1688
1689static inline __attribute__((always_inline)) drawlayer_preview_background_t _resolve_preview_background(const dt_iop_module_t *self,
1690 const dt_iop_drawlayer_gui_data_t *gui)
1691{
1692 const int preview_mode = (gui && self && self->dev && self->dev->gui_module == self)
1696 .enabled = (preview_mode != DRAWLAYER_PREVIEW_BG_IMAGE),
1697 .value = (preview_mode == DRAWLAYER_PREVIEW_BG_WHITE) ? 1.0f
1698 : (preview_mode == DRAWLAYER_PREVIEW_BG_GREY) ? 0.5f
1699 : 0.0f,
1700 };
1701}
1702
1704 const gboolean have_last_dab, const float last_dab_x,
1705 const float last_dab_y, const uint32_t publish_serial)
1706{
1707 if(IS_NULL_PTR(params)) return;
1708
1709 uint32_t x_bits = 0u;
1710 uint32_t y_bits = 0u;
1711 if(have_last_dab)
1712 {
1713 memcpy(&x_bits, &last_dab_x, sizeof(x_bits));
1714 memcpy(&y_bits, &last_dab_y, sizeof(y_bits));
1715 }
1716
1717 const uint32_t seed[5] = { (uint32_t)dab_count, have_last_dab ? 1u : 0u, x_bits, y_bits, publish_serial };
1718
1720 hash = dt_hash(hash, (const char *)seed, sizeof(seed));
1721
1722 /* Keep the serialized field non-zero so "uninitialized" remains distinguishable
1723 * from "updated at least once" in legacy parameter blobs. */
1724 params->stroke_commit_hash = (uint32_t)(hash ? hash : 1u);
1725}
1726
1728{
1731 GMainContext *const ui_ctx = g_main_context_default();
1732 if(IS_NULL_PTR(g) || IS_NULL_PTR(params) || !(ui_ctx && g_main_context_is_owner(ui_ctx))) return;
1733
1734 if(g->controls.layer_select) dt_drawlayer_layers_populate_list(self);
1736}
1737
1739{
1742 if(IS_NULL_PTR(g) || IS_NULL_PTR(params)) return;
1743
1744 const gboolean attached = dt_drawlayer_layer_name_non_empty(params->layer_name);
1745 const gboolean missing = (g->session.missing_layer_error[0] != '\0');
1746 const gboolean have_existing_layers = g->controls.layer_select && dt_bauhaus_combobox_length(g->controls.layer_select) > 0;
1747
1748 if(g->controls.notebook)
1749 {
1750 if(g->controls.brush_tab) gtk_widget_set_visible(g->controls.brush_tab, attached);
1751 if(g->controls.input_tab) gtk_widget_set_visible(g->controls.input_tab, attached);
1752 if(g->controls.layer_tab) gtk_widget_set_visible(g->controls.layer_tab, TRUE);
1753 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(g->controls.notebook), attached);
1754 gtk_notebook_set_show_border(GTK_NOTEBOOK(g->controls.notebook), attached);
1755 if(!attached && g->controls.layer_tab)
1756 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->controls.notebook),
1757 gtk_notebook_page_num(GTK_NOTEBOOK(g->controls.notebook), g->controls.layer_tab));
1758 }
1759
1760 if(g->controls.preview_title) gtk_widget_set_visible(g->controls.preview_title, attached);
1761 if(g->controls.preview_box) gtk_widget_set_visible(g->controls.preview_box, attached);
1762 if(g->controls.layer_action_row) gtk_widget_set_visible(g->controls.layer_action_row, TRUE);
1763 if(g->controls.layer_status)
1764 {
1765 gtk_label_set_text(GTK_LABEL(g->controls.layer_status), missing ? g->session.missing_layer_error : "");
1766 gtk_widget_set_visible(g->controls.layer_status, missing);
1767 }
1768 if(g->controls.delete_layer) gtk_widget_set_visible(g->controls.delete_layer, attached);
1769 if(g->controls.rename_layer) gtk_widget_set_visible(g->controls.rename_layer, attached);
1770 if(g->controls.layer_fill_title) gtk_widget_set_visible(g->controls.layer_fill_title, attached);
1771 if(g->controls.layer_fill_row) gtk_widget_set_visible(g->controls.layer_fill_row, attached);
1772 if(g->controls.create_background) gtk_widget_set_visible(g->controls.create_background, attached);
1773 if(g->controls.save_layer) gtk_widget_set_visible(g->controls.save_layer, attached);
1774 if(g->controls.attach_layer) gtk_widget_set_visible(g->controls.attach_layer, !attached && have_existing_layers);
1775
1776 if(g->controls.layer_select) gtk_widget_set_visible(g->controls.layer_select, attached || have_existing_layers);
1777 if(g->controls.create_layer) gtk_widget_set_sensitive(g->controls.create_layer, TRUE);
1778 if(g->controls.rename_layer) gtk_widget_set_sensitive(g->controls.rename_layer, attached);
1779 if(g->controls.attach_layer) gtk_widget_set_sensitive(g->controls.attach_layer, have_existing_layers);
1780 if(g->controls.create_background)
1781 gtk_widget_set_sensitive(g->controls.create_background, attached && !g->session.background_job_running);
1782 if(g->controls.save_layer) gtk_widget_set_sensitive(g->controls.save_layer, attached);
1783}
1784
1786{
1788 if(IS_NULL_PTR(g)) return;
1789
1790 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_image))
1791 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_image),
1792 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_IMAGE);
1793 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_white))
1794 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_white),
1795 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_WHITE);
1796 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_grey))
1797 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_grey),
1798 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_GREY);
1799 if(GTK_IS_TOGGLE_BUTTON(g->controls.preview_bg_black))
1800 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->controls.preview_bg_black),
1801 g->session.preview_bg_mode == DRAWLAYER_PREVIEW_BG_BLACK);
1802}
1803
1804gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, const gboolean record_history)
1805{
1808 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return TRUE;
1809 /* A stroke is still physically in progress (button held down). Never finalize or
1810 * reset it here — that would truncate the live path. This must guard BOTH commit
1811 * kinds: a quiet flush (record_history == FALSE, scheduled by GUI_SCROLL /
1812 * GUI_SYNC_TEMP_BUFFERS while pending stroke work exists) would otherwise run
1813 * dt_drawlayer_worker_wait_idle + finalize + dt_drawlayer_layers_reset_stroke_session mid-stroke without ever
1814 * clearing painting_active, silently cutting the stroke short. The realtime worker
1815 * already keeps base_patch updated incrementally, so there is nothing to flush
1816 * mid-stroke; defer a history commit to the worker and no-op a quiet flush. The
1817 * real commit runs on STROKE_END / focus-loss / image-change, where
1818 * _apply_runtime_event() has already cleared painting_active first. */
1819 if(g->manager.painting_active)
1820 {
1821 if(record_history) dt_drawlayer_worker_request_commit(g->stroke.worker);
1822 dt_print(DT_DEBUG_PERF, "[drawlayer] commit_dabs deferred: stroke in progress (record_history=%d)\n",
1823 record_history);
1824 return TRUE;
1825 }
1826
1828
1829 if(!record_history)
1830 dt_drawlayer_worker_seal_for_commit(g->stroke.worker);
1831
1832 /* Commit ordering is strict:
1833 * - the backend worker must be idle,
1834 * - the full-resolution layer cache must already contain the stroke,
1835 * - only then do we mutate params/history so the pipeline invalidation sees a coherent state. */
1836 dt_drawlayer_worker_wait_idle(self, g->stroke.worker);
1837 /* Damage-rectangle ownership stays in drawlayer:
1838 * paint accumulates per-dab bounds into a stroke rectangle, and on commit the
1839 * module consumes that rectangle to update cache dirty state. */
1841
1842 int sample_count = 0;
1843 gboolean had_stroke = FALSE;
1844 gboolean had_last_dab = FALSE;
1845 float last_dab_x = 0.0f;
1846 float last_dab_y = 0.0f;
1848 g->stroke.finish_commit_pending = FALSE;
1849 sample_count = (int)g->stroke.stroke_sample_count;
1850 had_stroke = (sample_count > 0);
1851 had_last_dab = g->stroke.last_dab_valid;
1852 last_dab_x = g->stroke.last_dab_x;
1853 last_dab_y = g->stroke.last_dab_y;
1856 dt_drawlayer_worker_reset_stroke(g->stroke.worker);
1857
1858 if(had_stroke)
1859 {
1860 /* Keep stroke commit lightweight for interactive rendering:
1861 * - do not flush process tile back to base patch here,
1862 * - do not invalidate the process tile here.
1863 *
1864 * Base-patch synchronization is handled at explicit persistence points
1865 * (save/focus-out/mouse-leave/geometry rebuild) so recomputes can keep
1866 * blending the already-updated process tile directly. */
1867 dt_drawlayer_touch_stroke_commit_hash(params, sample_count, had_last_dab, last_dab_x, last_dab_y, 0u);
1869 if(record_history && self->dev)
1870 {
1877 if(self->post_history_commit) self->post_history_commit(self);
1878
1879 g->manager.realtime_active = FALSE;
1884 }
1885
1886 /* The final stroke-end raster batches may land after the button-release
1887 * event already requested one redraw. Queue one more redraw now that the
1888 * worker is idle and the last published process snapshot is coherent, so
1889 * late-finishing strokes become visible even without further UI activity. */
1890 if(self->dev && self->dev->gui_attached) dt_control_queue_redraw_center();
1891 }
1892
1893 if(!(had_stroke && record_history))
1894 {
1895 g->manager.realtime_active = FALSE;
1897 }
1898
1899 return TRUE;
1900}
1901
1902static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
1903{
1904 (void)instance;
1905 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1907 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return;
1909}
1910
1912{
1914 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->controls.color) || IS_NULL_PTR(g->controls.softness)) return;
1915
1916 const gboolean paint_mode = _brush_paints_color();
1917 const gboolean show_hardness = (dt_drawlayer_conf_brush_shape() != DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN);
1918 gtk_widget_set_visible(GTK_WIDGET(g->controls.color), paint_mode);
1919 if(g->controls.color_row) gtk_widget_set_visible(g->controls.color_row, paint_mode);
1920 if(g->controls.color_swatch) gtk_widget_set_visible(g->controls.color_swatch, paint_mode);
1921 if(g->controls.image_colorpicker) gtk_widget_set_visible(g->controls.image_colorpicker, paint_mode);
1922 if(g->controls.image_colorpicker_source) gtk_widget_set_visible(g->controls.image_colorpicker_source, paint_mode);
1923 gtk_widget_set_visible(g->controls.softness, show_hardness);
1924}
1925
1927{
1928 if(IS_NULL_PTR(self->dev)) return FALSE;
1929
1933
1934 GString *errors = g_string_new(NULL);
1935 gboolean deleted = FALSE;
1936 int layer_width = 0;
1937 int layer_height = 0;
1938 if(!dt_drawlayer_resolve_layer_geometry(self, NULL, NULL, &layer_width, &layer_height, NULL, NULL))
1939 {
1941 layer_width = geometry.raw_width;
1942 layer_height = geometry.raw_height;
1943 }
1944
1945 char path[DT_PATH_MAX] = { 0 };
1946 if(!dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, path, sizeof(path)))
1947 {
1948 dt_drawlayer_layers_append_error(errors, _("failed to resolve drawlayer sidecar path"));
1949 }
1950 else if(!g_file_test(path, G_FILE_TEST_EXISTS))
1951 {
1952 dt_drawlayer_layers_append_error(errors, _("drawlayer sidecar TIFF is missing"));
1953 }
1954 else
1955 {
1957 dt_drawlayer_layers_append_error(errors, _("failed to delete drawing layer from sidecar"));
1958 else
1959 {
1960 deleted = TRUE;
1961 params->layer_name[0] = '\0';
1962 params->layer_order = -1;
1964 memset(params->work_profile, 0, sizeof(params->work_profile));
1965 if(g) g->session.missing_layer_error[0] = '\0';
1966 if(!IS_NULL_PTR(g))
1967 {
1969 dt_drawlayer_cache_patch_clear(&g->process.base_patch, "drawlayer patch");
1970 g->process.cache_valid = FALSE;
1971 g->process.cache_dirty = FALSE;
1972 dt_drawlayer_paint_runtime_state_reset(&g->process.cache_dirty_rect);
1973 g->process.cache_imgid = -1;
1974 g->process.cache_layer_name[0] = '\0';
1975 g->process.cache_layer_order = -1;
1977 }
1979 }
1980 }
1981
1983 g_string_free(errors, TRUE);
1984 return deleted;
1985}
1986
1987static gboolean _confirm_delete_layer(dt_iop_module_t *self, const gboolean removing_module)
1988{
1989 if(IS_NULL_PTR(self->dev)) return FALSE;
1990
1992 if(!dt_drawlayer_layer_name_non_empty(params->layer_name)) return removing_module;
1993
1994 GtkWidget *dialog = gtk_message_dialog_new(
1995 GTK_WINDOW(dt_gui_main_window()), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1996 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s",
1997 removing_module
1998 ? _("Delete the linked drawing layer from the sidecar TIFF before removing this module instance?")
1999 : _("Delete the linked drawing layer from the sidecar TIFF?"));
2000 if(removing_module)
2001 {
2002 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Keep layer"), GTK_RESPONSE_NO);
2003 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Delete layer"), GTK_RESPONSE_YES);
2004 }
2005 else
2006 {
2007 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
2008 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Delete"), GTK_RESPONSE_ACCEPT);
2009 }
2010
2011 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
2012 gtk_widget_destroy(dialog);
2013
2014 if(removing_module)
2015 {
2016 if(response == GTK_RESPONSE_YES) _delete_current_layer(self);
2017 /* Module removal must proceed regardless of the answer or any layer-delete failure. */
2018 return TRUE;
2019 }
2020
2021 if(response != GTK_RESPONSE_ACCEPT) return FALSE;
2022 return _delete_current_layer(self);
2023}
2024
2025static gboolean _rename_current_layer_from_gui(dt_iop_module_t *self, const char *requested_name)
2026{
2029 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2031
2032 char new_name[DRAWLAYER_NAME_SIZE] = { 0 };
2033 char stripped_requested[DRAWLAYER_NAME_SIZE] = { 0 };
2034 if(!IS_NULL_PTR(requested_name)) g_strlcpy(stripped_requested, requested_name, sizeof(stripped_requested));
2035 g_strstrip(stripped_requested);
2036 if(stripped_requested[0] == '\0') return FALSE;
2037 _sanitize_requested_layer_name(requested_name, new_name, sizeof(new_name));
2038 if(new_name[0] == '\0') return FALSE;
2039
2040 if(!g_strcmp0(new_name, params->layer_name)) return TRUE;
2041
2042 GString *errors = g_string_new(NULL);
2043 gboolean renamed = FALSE;
2044 int layer_width = 0;
2045 int layer_height = 0;
2046 if(!dt_drawlayer_resolve_layer_geometry(self, NULL, NULL, &layer_width, &layer_height, NULL, NULL))
2047 {
2049 layer_width = geometry.raw_width;
2050 layer_height = geometry.raw_height;
2051 }
2052
2054 dt_drawlayer_layers_append_error(errors, _("failed to commit drawing stroke before renaming"));
2055 else if(!dt_drawlayer_flush_layer_cache(self))
2056 dt_drawlayer_layers_append_error(errors, _("failed to write drawing layer sidecar"));
2057 else
2058 {
2059 char path[DT_PATH_MAX] = { 0 };
2060 if(!dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, path, sizeof(path)))
2061 dt_drawlayer_layers_append_error(errors, _("failed to resolve drawlayer sidecar path"));
2062 else if(!g_file_test(path, G_FILE_TEST_EXISTS))
2063 dt_drawlayer_layers_append_error(errors, _("drawlayer sidecar TIFF is missing"));
2064 else
2065 {
2066 dt_drawlayer_io_layer_info_t info = { 0 };
2068 layer_width, layer_height, &info))
2069 dt_drawlayer_layers_append_error(errors, _("failed to rename drawing layer in sidecar"));
2070 else
2071 {
2072 g_strlcpy(params->layer_name, new_name, sizeof(params->layer_name));
2073 params->layer_order = info.index;
2075 g_strlcpy(g->process.cache_layer_name, params->layer_name, sizeof(g->process.cache_layer_name));
2076 g->process.cache_layer_order = params->layer_order;
2077 renamed = TRUE;
2078 }
2079 }
2080 }
2081
2082 if(renamed)
2083 {
2084 g->session.missing_layer_error[0] = '\0';
2086 if(self->dev) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2087 }
2088 else
2089 {
2091 }
2092
2094 g_string_free(errors, TRUE);
2095 return renamed;
2096}
2097
2098static gboolean _create_new_layer(dt_iop_module_t *self, const char *requested_name)
2099{
2102 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2103
2104 char new_name[DRAWLAYER_NAME_SIZE] = { 0 };
2105 char stripped_requested[DRAWLAYER_NAME_SIZE] = { 0 };
2106 if(!IS_NULL_PTR(requested_name)) g_strlcpy(stripped_requested, requested_name, sizeof(stripped_requested));
2107 g_strstrip(stripped_requested);
2108 if(stripped_requested[0] == '\0') return FALSE;
2109 _sanitize_requested_layer_name(requested_name, new_name, sizeof(new_name));
2110 if(new_name[0] == '\0') return FALSE;
2111
2112 char path[DT_PATH_MAX] = { 0 };
2113 if(dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, path, sizeof(path))
2114 && dt_drawlayer_io_layer_name_exists(path, new_name, -1))
2115 {
2116 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2117 _("A drawing layer with that name already exists."),
2118 _("Choose a different layer name."));
2119 return FALSE;
2120 }
2121
2122 const dt_iop_drawlayer_params_t previous = *params;
2123
2124 if(!dt_drawlayer_commit_dabs(self, FALSE)) return FALSE;
2125 if(!dt_drawlayer_flush_layer_cache(self)) return FALSE;
2126
2127 g_strlcpy(params->layer_name, new_name, sizeof(params->layer_name));
2128 params->layer_order = -1;
2130 memset(params->work_profile, 0, sizeof(params->work_profile));
2131
2133 {
2134 *params = previous;
2135 gui_update(self);
2136 return FALSE;
2137 }
2138
2139 /* Creating a layer must materialize it in the TIFF immediately so the
2140 * shared combobox only ever lists real on-disk layers and drawing can start
2141 * on the same authoritative cache line without a later bootstrap step. */
2142 if(!g->process.cache_valid || IS_NULL_PTR(g->process.base_patch.pixels))
2143 {
2144 *params = previous;
2145 gui_update(self);
2146 return FALSE;
2147 }
2148 g->process.cache_dirty = TRUE;
2150 {
2151 *params = previous;
2152 gui_update(self);
2153 return FALSE;
2154 }
2155
2156 g->session.missing_layer_error[0] = '\0';
2158 if(self->dev)
2159 {
2160 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2162 }
2164 gui_update(self);
2165 return TRUE;
2166}
2167
2168static void _build_pre_module_filter_string(dt_iop_module_t *self, char *filter, const size_t filter_size)
2169{
2170 if(IS_NULL_PTR(filter) || filter_size == 0) return;
2171 filter[0] = '\0';
2172 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(self->dev->pipe)) return;
2173
2174 dt_dev_pixelpipe_iop_t *self_piece = dt_dev_distort_get_iop_pipe(self->dev->pipe, self);
2175 const char *prev_op = NULL;
2176 if(self_piece && self->dev->pipe)
2177 {
2178 for(GList *nodes = self->dev->pipe->nodes; nodes; nodes = g_list_next(nodes))
2179 {
2181 if(piece == self_piece) break;
2182 if(piece->enabled && piece->module && piece->module->op[0]) prev_op = piece->module->op;
2183 }
2184 }
2185 if(!IS_NULL_PTR(prev_op) && prev_op[0]) g_snprintf(filter, filter_size, "pre:%s", prev_op);
2186}
2187
2188static gboolean _background_layer_job_done_idle(gpointer user_data)
2189{
2191 if(IS_NULL_PTR(result)) return G_SOURCE_REMOVE;
2192
2193 dt_control_log("%s", result->message[0] ? result->message : _("background layer job finished"));
2194
2196
2197 gboolean cleared_initiator = FALSE;
2198 if(dev && dev->image_storage.id == result->imgid)
2199 {
2200 for(GList *modules = dev->iop; modules; modules = g_list_next(modules))
2201 {
2202 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
2203 if(!module || g_strcmp0(module->op, "drawlayer")) continue;
2204
2207 if(g && params && !g_strcmp0(params->layer_name, result->initiator_layer_name)
2209 {
2210 g->session.background_job_running = FALSE;
2211 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, TRUE);
2212 cleared_initiator = TRUE;
2213 }
2214
2215 if(result->success && params)
2216 {
2219 }
2220 }
2221 }
2222
2223 if(!cleared_initiator && dev)
2224 {
2225 for(GList *modules = dev->iop; modules; modules = g_list_next(modules))
2226 {
2227 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
2228 if(!module || g_strcmp0(module->op, "drawlayer")) continue;
2230 if(IS_NULL_PTR(g) || !g->session.background_job_running) continue;
2231 g->session.background_job_running = FALSE;
2232 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, TRUE);
2233 }
2234 }
2235
2236 dt_free(result);
2237 return G_SOURCE_REMOVE;
2238}
2239
2241{
2244 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2245 if(g->session.background_job_running) return FALSE;
2247
2248 if(!dt_drawlayer_commit_dabs(self, FALSE)) return FALSE;
2249 if(!dt_drawlayer_ensure_layer_cache(self)) return FALSE;
2250 if(!dt_drawlayer_flush_layer_cache(self)) return FALSE;
2251
2253 int layer_width = 0;
2254 int layer_height = 0;
2255 if(!dt_drawlayer_resolve_layer_geometry(self, self->dev->pipe, piece, &layer_width, &layer_height, NULL, NULL)) return FALSE;
2256
2257 char sidecar_path[DT_PATH_MAX] = { 0 };
2258 if(!dt_drawlayer_io_sidecar_path(self->dev->image_storage.id, sidecar_path, sizeof(sidecar_path))) return FALSE;
2259
2260 drawlayer_dir_info_t current_info = { 0 };
2261 dt_drawlayer_io_layer_info_t io_info = { 0 };
2262 if(!dt_drawlayer_io_find_layer(sidecar_path, params->layer_name, params->layer_order, &io_info)) return FALSE;
2263 current_info.found = io_info.found;
2264 current_info.index = io_info.index;
2265 current_info.count = io_info.count;
2266 current_info.width = io_info.width;
2267 current_info.height = io_info.height;
2268 g_strlcpy(current_info.name, io_info.name, sizeof(current_info.name));
2269 g_strlcpy(current_info.work_profile, io_info.work_profile, sizeof(current_info.work_profile));
2270
2272 job_params->imgid = self->dev->image_storage.id;
2273 job_params->layer_width = layer_width;
2274 job_params->layer_height = layer_height;
2275 job_params->dst_x = 0;
2276 job_params->dst_y = 0;
2277 job_params->insert_after_order = current_info.index;
2278 g_strlcpy(job_params->sidecar_path, sidecar_path, sizeof(job_params->sidecar_path));
2279 g_strlcpy(job_params->work_profile, params->work_profile, sizeof(job_params->work_profile));
2280 g_snprintf(job_params->requested_bg_name, sizeof(job_params->requested_bg_name), "%s-bg", params->layer_name);
2281 g_strlcpy(job_params->initiator_layer_name, params->layer_name, sizeof(job_params->initiator_layer_name));
2284 _build_pre_module_filter_string(self, job_params->filter, sizeof(job_params->filter));
2285
2287 "drawlayer create background layer");
2288 if(IS_NULL_PTR(job))
2289 {
2290 dt_free(job_params);
2291 return FALSE;
2292 }
2293
2294 dt_control_job_set_params(job, job_params, g_free);
2295 dt_control_job_add_progress(job, _("creating background layer"), TRUE);
2296 g->session.background_job_running = TRUE;
2297 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, FALSE);
2299 {
2300 g->session.background_job_running = FALSE;
2301 if(g->controls.create_background) gtk_widget_set_sensitive(g->controls.create_background, TRUE);
2302 return FALSE;
2303 }
2304 return TRUE;
2305}
2306
2307static void _widget_changed(GtkWidget *widget, gpointer user_data)
2308{
2309 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2312
2314
2315 if(widget == g->controls.brush_mode || widget == g->controls.brush_shape) _sync_mode_sensitive_widgets(self);
2316
2317 if(widget == g->controls.size || widget == g->controls.softness || widget == g->controls.brush_shape)
2319
2320 if(widget == g->controls.hdr_exposure)
2321 {
2322 float display_rgb[3] = { 0.0f };
2323 if(dt_drawlayer_widgets_get_display_color(g->ui.widgets, display_rgb))
2324 dt_drawlayer_sync_cached_brush_colors(self, display_rgb);
2325 }
2326
2327 if(widget == g->controls.brush_shape || widget == g->controls.opacity || widget == g->controls.softness || widget == g->controls.sprinkles
2328 || widget == g->controls.sprinkle_size || widget == g->controls.sprinkle_coarseness)
2330}
2331
2334 dt_iop_drawlayer_params_t *params, const char *layer_name,
2335 const int layer_order)
2336{
2337 if(IS_NULL_PTR(self) || IS_NULL_PTR(g) || IS_NULL_PTR(params) || layer_order < 0 || !dt_drawlayer_layer_name_non_empty(layer_name)) return FALSE;
2338
2339 char previous_name[DRAWLAYER_NAME_SIZE] = { 0 };
2340 g_strlcpy(previous_name, params->layer_name, sizeof(previous_name));
2341 const int previous_order = params->layer_order;
2342
2343 g_strlcpy(params->layer_name, layer_name, sizeof(params->layer_name));
2344 params->layer_order = layer_order;
2346 {
2347 g_strlcpy(params->layer_name, previous_name, sizeof(params->layer_name));
2348 params->layer_order = previous_order;
2349 gui_update(self);
2350 return FALSE;
2351 }
2352
2353 g->session.missing_layer_error[0] = '\0';
2355 if(self->dev)
2356 {
2357 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2359 }
2361 return TRUE;
2362}
2363
2364static void _layer_selected(GtkWidget *widget, gpointer user_data)
2365{
2366 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2370
2371 const int active = dt_bauhaus_combobox_get(widget);
2372 if(active < 0) return;
2373
2374 const char *text = dt_bauhaus_combobox_get_text(g->controls.layer_select);
2375 if(IS_NULL_PTR(text)) return;
2376
2378 {
2380 return;
2381 }
2382 _apply_selected_layer_attachment(self, g, params, text, active);
2383}
2384
2385static void _attach_selected_layer_clicked(GtkButton *button, gpointer user_data)
2386{
2387 (void)button;
2388 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2391 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return;
2393
2394 const int active = dt_bauhaus_combobox_get(g->controls.layer_select);
2395 if(active < 0) return;
2396
2397 const char *text = dt_bauhaus_combobox_get_text(g->controls.layer_select);
2398 if(!dt_drawlayer_layer_name_non_empty(text)) return;
2399 _apply_selected_layer_attachment(self, g, params, text, active);
2400}
2401
2402static void _rename_layer_clicked(GtkButton *button, gpointer user_data)
2403{
2404 (void)button;
2405 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2408 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return;
2410
2411 char requested_name[DRAWLAYER_NAME_SIZE] = { 0 };
2412 if(!_prompt_layer_name_dialog(_("Rename drawing layer"),
2413 _("Enter the new name for the current drawing layer."),
2414 params->layer_name, requested_name, sizeof(requested_name)))
2415 return;
2416
2417 if(!_rename_current_layer_from_gui(self, requested_name))
2418 dt_control_log(_("failed to rename drawing layer"));
2419}
2420
2421static void _delete_layer_clicked(GtkButton *button, gpointer user_data)
2422{
2423 (void)button;
2424 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2425 if(IS_NULL_PTR(self->dev)) return;
2426 if(!dt_drawlayer_commit_dabs(self, FALSE)) return;
2427 if(!_confirm_delete_layer(self, FALSE)) return;
2428
2431 if(self->dev) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2433 gui_update(self);
2434}
2435
2436static gboolean _fill_current_layer(dt_iop_module_t *self, const float value)
2437{
2440 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2441
2442 if(!dt_drawlayer_commit_dabs(self, FALSE)) return FALSE;
2444 if(IS_NULL_PTR(g->process.base_patch.pixels)) return FALSE;
2445
2446 const size_t count = (size_t)g->process.base_patch.width * g->process.base_patch.height;
2447 float *const pixels = g->process.base_patch.pixels;
2448 const float gray = _clamp01(value);
2449 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
2450 __OMP_PARALLEL_FOR__( if(count > 4096))
2451 for(size_t k = 0; k < count; k++)
2452 {
2453 float *pixel = pixels + 4 * k;
2454 pixel[0] = gray;
2455 pixel[1] = gray;
2456 pixel[2] = gray;
2457 pixel[3] = 1.0f;
2458 }
2459 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
2460
2461 g->process.cache_dirty = TRUE;
2462 g->process.cache_dirty_rect.valid = TRUE;
2463 g->process.cache_dirty_rect.nw[0] = 0;
2464 g->process.cache_dirty_rect.nw[1] = 0;
2465 g->process.cache_dirty_rect.se[0] = g->process.base_patch.width;
2466 g->process.cache_dirty_rect.se[1] = g->process.base_patch.height;
2469
2470 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2471 return TRUE;
2472}
2473
2475{
2478 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || IS_NULL_PTR(params)) return FALSE;
2479
2480 if(!dt_drawlayer_commit_dabs(self, FALSE)) return FALSE;
2482 if(IS_NULL_PTR(g->process.base_patch.pixels)) return FALSE;
2483
2484 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
2485 dt_drawlayer_cache_clear_transparent_float(g->process.base_patch.pixels,
2486 (size_t)g->process.base_patch.width * g->process.base_patch.height);
2487 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
2488
2489 g->process.cache_dirty = TRUE;
2490 g->process.cache_dirty_rect.valid = TRUE;
2491 g->process.cache_dirty_rect.nw[0] = 0;
2492 g->process.cache_dirty_rect.nw[1] = 0;
2493 g->process.cache_dirty_rect.se[0] = g->process.base_patch.width;
2494 g->process.cache_dirty_rect.se[1] = g->process.base_patch.height;
2497
2498 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2499 return TRUE;
2500}
2501
2502static void _fill_white_clicked(GtkButton *button, gpointer user_data)
2503{
2504 (void)button;
2505 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2506 if(!_fill_current_layer(self, 1.0f)) dt_control_log(_("failed to fill drawing layer"));
2507}
2508
2509static void _fill_black_clicked(GtkButton *button, gpointer user_data)
2510{
2511 (void)button;
2512 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2513 if(!_fill_current_layer(self, 0.0f)) dt_control_log(_("failed to fill drawing layer"));
2514}
2515
2516static void _fill_transparent_clicked(GtkButton *button, gpointer user_data)
2517{
2518 (void)button;
2519 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2520 if(!_clear_current_layer(self)) dt_control_log(_("failed to clear drawing layer"));
2521}
2522
2523static void _save_layer_clicked(GtkButton *button, gpointer user_data)
2524{
2525 (void)button;
2526 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2528 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g)) return;
2529
2530 if(!g->process.cache_valid || IS_NULL_PTR(g->process.base_patch.pixels))
2531 {
2532 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2533 _("No drawing layer is loaded in memory."),
2534 _("The sidecar save was aborted."));
2535 return;
2536 }
2537 if(!dt_drawlayer_layer_name_non_empty(g->process.cache_layer_name))
2538 {
2539 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2540 _("Layer name is empty."),
2541 _("The sidecar save was aborted."));
2543 return;
2544 }
2545
2546 GtkWidget *dialog = gtk_message_dialog_new(
2547 GTK_WINDOW(dt_gui_main_window()), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
2548 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", _("Save the drawing sidecar now?"));
2549 gtk_message_dialog_format_secondary_text(
2550 GTK_MESSAGE_DIALOG(dialog), "%s",
2551 _("This writes the current in-memory drawing layer to the sidecar TIFF immediately."));
2552 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
2553 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Save"), GTK_RESPONSE_ACCEPT);
2554
2555 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
2556 gtk_widget_destroy(dialog);
2557 if(response != GTK_RESPONSE_ACCEPT)
2558 {
2559 return;
2560 }
2561
2563 _("Waiting for the layer rasterization to finish..."));
2564
2565 /* Saving the sidecar is explicit persistence, not a history edit.
2566 * We still finalize any pending stroke first so the flush sees the latest
2567 * authoritative cache state, then write that cache to the TIFF immediately. */
2569 {
2570 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2571 _("Failed to finalize the drawing stroke."),
2572 _("The sidecar was not saved."));
2573 return;
2574 }
2575
2576 dt_drawlayer_rekey_shared_base_patch(&g->process.base_patch, self->dev->image_storage.id,
2577 (const dt_iop_drawlayer_params_t *)self->params);
2578
2580 {
2581 _show_drawlayer_modal_message(GTK_MESSAGE_ERROR,
2582 _("Failed to write the drawing layer sidecar."),
2583 _("The sidecar TIFF could not be updated."));
2584 return;
2585 }
2586
2589
2590 _show_drawlayer_modal_message(GTK_MESSAGE_INFO,
2591 _("Drawing sidecar saved."),
2592 _("The current in-memory layer has been written to the sidecar TIFF."));
2593}
2594
2595static void _create_layer_clicked(GtkButton *button, gpointer user_data)
2596{
2597 (void)button;
2598 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2599 if(IS_NULL_PTR(self)) return;
2600
2601 char requested_name[DRAWLAYER_NAME_SIZE] = { 0 };
2602 if(!_prompt_layer_name_dialog(_("Create drawing layer"),
2603 _("Enter the name of the new drawing layer."),
2604 "", requested_name, sizeof(requested_name)))
2605 return;
2606
2607 if(!_create_new_layer(self, requested_name))
2608 dt_control_log(_("failed to create drawing layer"));
2609}
2610
2611static void _create_background_clicked(GtkButton *button, gpointer user_data)
2612{
2613 (void)button;
2614 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2615 if(!_create_background_layer_from_input(self)) dt_control_log(_("failed to create background layer from input"));
2616}
2617
2618static void _preview_bg_toggled(GtkToggleButton *button, gpointer user_data)
2619{
2620 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2623 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(g) || (dt_gui_get_global() && dt_gui_widgets_suppressed()) || !gtk_toggle_button_get_active(button))
2624 return;
2625
2626 if(GTK_WIDGET(button) == g->controls.preview_bg_white)
2627 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_WHITE;
2628 else if(GTK_WIDGET(button) == g->controls.preview_bg_grey)
2629 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_GREY;
2630 else if(GTK_WIDGET(button) == g->controls.preview_bg_black)
2631 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_BLACK;
2632 else
2633 g->session.preview_bg_mode = DRAWLAYER_PREVIEW_BG_IMAGE;
2634
2637 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2639}
2640
2641static gboolean _build_raw_input_event(dt_iop_module_t *self, const double wx, const double wy, const double pressure,
2642 const dt_drawlayer_paint_stroke_pos_t stroke_pos,
2644{
2646 if(IS_NULL_PTR(g) || IS_NULL_PTR(input)) return FALSE;
2647
2648 dt_control_pointer_input_t pointer_input = { 0 };
2649 dt_control_get_pointer_input(&pointer_input);
2650 const float input_wx = isfinite(pointer_input.x) ? (float)pointer_input.x : (float)wx;
2651 const float input_wy = isfinite(pointer_input.y) ? (float)pointer_input.y : (float)wy;
2652 const float pressure_norm = pointer_input.has_pressure ? _clamp01(pointer_input.pressure) : _clamp01(pressure);
2654 .wx = input_wx,
2655 .wy = input_wy,
2656 .pressure = pressure_norm,
2657 .tilt = (float)_clamp01(pointer_input.tilt),
2658 .acceleration = (float)_clamp01(pointer_input.acceleration),
2659 .event_ts = g_get_monotonic_time(),
2660 .stroke_batch = g->stroke.current_stroke_batch,
2661 .event_index = ++g->stroke.stroke_event_index,
2662 .stroke_pos = stroke_pos,
2663 };
2664 _fill_input_layer_coords(self, input);
2665 _fill_input_brush_settings(self, input);
2666 return TRUE;
2667}
2668
2670{
2672 if(IS_NULL_PTR(self) || IS_NULL_PTR(g)) return;
2673
2674 uint32_t stroke_batch = g->stroke.current_stroke_batch + 1u;
2675 if(stroke_batch == 0u) stroke_batch++;
2676 const uint32_t event_index = first_input ? first_input->event_index : 0u;
2677 if(!IS_NULL_PTR(first_input) && first_input->stroke_batch != 0u) stroke_batch = first_input->stroke_batch;
2678
2680 g->session.pointer_valid = TRUE;
2681 g->stroke.current_stroke_batch = stroke_batch;
2682 if(!dt_drawlayer_worker_active(g->stroke.worker))
2684 g->stroke.finish_commit_pending = FALSE;
2685 g->stroke.stroke_sample_count = 0;
2686 g->stroke.stroke_event_index = event_index;
2687 g->stroke.last_dab_valid = FALSE;
2689 /* Hand the worker its own params blob for this stroke's heartbeats. Nothing may change
2690 * `self->params` while a stroke is live -- every GUI writer of it runs `dt_drawlayer_commit_dabs(self,
2691 * FALSE)` first, which waits for the worker -- so one snapshot here is valid for the whole
2692 * stroke, and the worker never has to read a blob the GUI thread owns. */
2695}
2696
2702
2705 const dt_drawlayer_runtime_event_t event,
2706 const gboolean flush_pending)
2707{
2709 .ok = TRUE,
2710 .raw_input_ok = TRUE,
2711 };
2712 if(IS_NULL_PTR(self) || IS_NULL_PTR(g)) return result;
2713
2714 const drawlayer_runtime_host_context_t runtime_host = {
2715 .runtime = {
2716 .self = self,
2717 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
2718 .gui = g,
2719 .manager = &g->manager,
2720 .process_state = &g->process,
2721 },
2722 };
2723 const dt_drawlayer_runtime_host_t runtime_manager = {
2724 .user_data = (void *)&runtime_host,
2725 };
2727 &g->manager,
2729 .event = event,
2730 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
2731 .flush_pending = flush_pending,
2732 },
2733 &runtime_manager);
2734}
2735
2737 const dt_drawlayer_runtime_feedback_t feedback)
2738{
2739 if(IS_NULL_PTR(g)) return;
2740
2741 switch(feedback)
2742 {
2744 dt_drawlayer_wait_for_rasterization_modal(g, _("Finishing drawing"),
2745 _("Waiting for the drawing rasterization to finish..."));
2746 break;
2747
2750 _("Waiting for the layer rasterization to finish..."));
2751 break;
2752
2754 default:
2755 break;
2756 }
2757}
2758
2760const char *name()
2761{
2762 return C_("modulename", "drawing");
2763}
2764
2766const char **description(struct dt_iop_module_t *self)
2767{
2768 return dt_iop_set_description(self, _("paint premultiplied RGB layers in a TIFF sidecar"), _("creative"),
2769 _("linear, RGB, scene-referred"), _("geometric, RGB"),
2770 _("linear, RGB, scene-referred"));
2771}
2772
2773#ifdef HAVE_OPENCL
2776{
2777 const int program = 3; // blendop.cl, from programs.conf
2778 dt_iop_drawlayer_global_data_t *gd = calloc(1, sizeof(*gd));
2779 if(IS_NULL_PTR(gd)) return;
2780 module->data = gd;
2781 gd->kernel_premult_over = -1;
2782
2783 /* Reuse the existing blendop OpenCL program and add one drawlayer-specific
2784 * kernel to handle premultiplied "over" directly. This avoids the costly
2785 * de-premultiply + mask split that the stock blend kernels would require. */
2786 gd->kernel_premult_over = dt_opencl_create_kernel(program, "blendop_premult_over");
2787}
2788
2791{
2793 if(IS_NULL_PTR(gd)) return;
2795 dt_free(gd);
2796 module->data = NULL;
2797}
2798#endif
2799
2802{
2803 return IOP_GROUP_EFFECTS;
2804}
2805
2808{
2810}
2811
2817
2820{
2821 module->params = calloc(1, sizeof(dt_iop_drawlayer_params_t));
2822 module->default_params = calloc(1, sizeof(dt_iop_drawlayer_params_t));
2823 module->params_size = sizeof(dt_iop_drawlayer_params_t);
2824
2825 if(module->params) ((dt_iop_drawlayer_params_t *)module->params)->layer_order = -1;
2826 if(module->default_params) ((dt_iop_drawlayer_params_t *)module->default_params)->layer_order = -1;
2827}
2828
2830{
2831 /* The aligned family, like every other module: the DEFAULT cleanup_pipe frees
2832 * piece->data with dt_free_align(), so a module that allocates any other way only stays
2833 * correct for as long as it also overrides cleanup_pipe. That is a standing trap on
2834 * Windows -- _aligned_malloc against g_free -- and invisible on Linux, where both end at
2835 * free(). */
2837 if(IS_NULL_PTR(piece->data)) return;
2838 piece->data_size = sizeof(dt_iop_drawlayer_params_t);
2840 data->params.layer_order = -1;
2844 &data->runtime_manager, &data->runtime_process,
2845 &data->runtime_display_pipe);
2846}
2847
2860
2864{
2866 const gboolean display_pipe = self && self->dev && dt_iop_gui_data(self) && pipe
2867 && (pipe == self->dev->pipe || pipe == self->dev->preview_pipe);
2869 dt_drawlayer_runtime_manager_bind_piece(&data->headless_manager, &data->process, g ? &g->manager : NULL,
2870 g ? &g->process : NULL, display_pipe, &data->runtime_manager,
2871 &data->runtime_process, &data->runtime_display_pipe);
2872 memcpy(&data->params, params, sizeof(dt_iop_drawlayer_params_t));
2874
2875 /* Every pipe now warms the same authoritative base-patch snapshot through
2876 * the pixelpipe cache during `commit_params()`. GUI pipes still keep their
2877 * own transformed ROI cache on top, but they attach to the same shared base
2878 * line as headless pipes instead of carrying a private sidecar mirror. */
2879 _refresh_piece_base_cache(self, data, &data->params, pipe, piece);
2880}
2881
2884{
2885 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return;
2886 if(!dt_drawlayer_commit_dabs(self, FALSE)) return;
2887 if(!_confirm_delete_layer(self, FALSE)) return;
2888
2890 params->layer_name[0] = '\0';
2891 params->layer_order = -1;
2893 memset(params->work_profile, 0, sizeof(params->work_profile));
2896 if(!IS_NULL_PTR(g))
2897 {
2898 g->session.missing_layer_error[0] = '\0';
2900 }
2901
2904}
2905
2908{
2909 if(IS_NULL_PTR(self->dev)) return TRUE;
2910 if(!dt_drawlayer_commit_dabs(self, FALSE)) return FALSE;
2912 return _confirm_delete_layer(self, TRUE);
2913}
2914
2915
2916
2917
2926{
2927 g->controls.brush_mode = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
2928 dt_bauhaus_combobox_add(g->controls.brush_mode, _("paint"));
2929 dt_bauhaus_combobox_add(g->controls.brush_mode, _("erase"));
2930 dt_bauhaus_combobox_add(g->controls.brush_mode, _("blur"));
2931 dt_bauhaus_combobox_add(g->controls.brush_mode, _("smudge"));
2932 dt_bauhaus_widget_set_label(g->controls.brush_mode, _("Paint mode"));
2933 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.brush_mode, TRUE, TRUE, 0);
2934
2935 GtkWidget *color_title = gtk_label_new(_("Color"));
2936 gtk_label_set_xalign(GTK_LABEL(color_title), 0.0f);
2937 dt_gui_add_class(color_title, "dt_section_label");
2938 gtk_box_pack_start(GTK_BOX(brush_tab), color_title, TRUE, TRUE, 0);
2939 g->controls.color = gtk_drawing_area_new();
2940 gtk_widget_set_size_request(g->controls.color, -1, DT_DRAWLAYER_COLOR_PICKER_HEIGHT);
2941 gtk_widget_add_events(g->controls.color, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
2942 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.color, TRUE, TRUE, 0);
2943 g->controls.color_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2944 g->controls.color_swatch = gtk_drawing_area_new();
2945 gtk_widget_set_size_request(g->controls.color_swatch, -1, DT_PIXEL_APPLY_DPI(DT_DRAWLAYER_COLOR_HISTORY_HEIGHT));
2946 gtk_widget_add_events(g->controls.color_swatch, GDK_BUTTON_PRESS_MASK);
2947 gtk_box_pack_start(GTK_BOX(g->controls.color_row), g->controls.color_swatch, TRUE, TRUE, 0);
2948 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.color_row, TRUE, TRUE, 0);
2949 GtkWidget *picker_controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2950 g->controls.image_colorpicker = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_POINT_AREA, NULL, IOP_CS_NONE);
2951 g->controls.image_colorpicker_source = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
2952 dt_bauhaus_combobox_add(g->controls.image_colorpicker_source, _("input"));
2953 dt_bauhaus_combobox_add(g->controls.image_colorpicker_source, _("output"));
2954 dt_bauhaus_widget_set_label(g->controls.image_colorpicker_source, _("Pick from"));
2955 gtk_box_pack_start(GTK_BOX(picker_controls), g->controls.image_colorpicker, TRUE, TRUE, 0);
2956 gtk_box_pack_start(GTK_BOX(picker_controls), g->controls.image_colorpicker_source, TRUE, TRUE, 0);
2957 gtk_box_pack_start(GTK_BOX(brush_tab), picker_controls, TRUE, TRUE, 0);
2958
2959 g->controls.hdr_exposure
2960 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 4.0f, 0.1f, 0.0f, 2);
2961 dt_bauhaus_widget_set_label(g->controls.hdr_exposure, _("HDR exposure"));
2962 dt_bauhaus_slider_set_format(g->controls.hdr_exposure, _(" EV"));
2963 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.hdr_exposure, TRUE, TRUE, 0);
2964
2965 GtkWidget *geometry_title = gtk_label_new(_("Geometry"));
2966 gtk_label_set_xalign(GTK_LABEL(geometry_title), 0.0f);
2967 dt_gui_add_class(geometry_title, "dt_section_label");
2968 gtk_box_pack_start(GTK_BOX(brush_tab), geometry_title, TRUE, TRUE, 0);
2969 GtkWidget *brush_shape_title = gtk_label_new(_("Fall-off"));
2970 gtk_label_set_xalign(GTK_LABEL(brush_shape_title), 0.0f);
2971 gtk_box_pack_start(GTK_BOX(brush_tab), brush_shape_title, TRUE, TRUE, 0);
2972 g->controls.brush_shape = gtk_drawing_area_new();
2973 gtk_widget_set_size_request(g->controls.brush_shape, -1, DT_PIXEL_APPLY_DPI(72));
2974 gtk_widget_add_events(g->controls.brush_shape, GDK_BUTTON_PRESS_MASK);
2975 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.brush_shape, TRUE, TRUE, 0);
2976
2977 g->controls.size
2978 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 1.0f, 2048.0f, 1.0f, 64.0f, 0);
2979 dt_bauhaus_widget_set_label(g->controls.size, _("Size"));
2980 dt_bauhaus_slider_set_format(g->controls.size, _(" px"));
2981 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.size, TRUE, TRUE, 0);
2982 g->controls.distance
2983 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 0.0f, 2);
2984 dt_bauhaus_widget_set_label(g->controls.distance, _("Sampling distance"));
2985 dt_bauhaus_slider_set_format(g->controls.distance, "%");
2986 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.distance, TRUE, TRUE, 0);
2987 g->controls.smoothing
2988 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 0.0f, 2);
2989 dt_bauhaus_widget_set_label(g->controls.smoothing, _("Smoothing"));
2990 dt_bauhaus_slider_set_format(g->controls.smoothing, "%");
2991 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.smoothing, TRUE, TRUE, 0);
2992 g->controls.softness
2993 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 1.0f, 0.01f, 0.5f, 2);
2994 dt_bauhaus_widget_set_label(g->controls.softness, _("Hardness"));
2995 dt_bauhaus_slider_set_factor(g->controls.softness, 100.0f);
2996 dt_bauhaus_slider_set_format(g->controls.softness, "%");
2997 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.softness, TRUE, TRUE, 0);
2998
2999 GtkWidget *thickness_title = gtk_label_new(_("Thickness"));
3000 gtk_label_set_xalign(GTK_LABEL(thickness_title), 0.0f);
3001 dt_gui_add_class(thickness_title, "dt_section_label");
3002 gtk_box_pack_start(GTK_BOX(brush_tab), thickness_title, TRUE, TRUE, 0);
3003 g->controls.opacity
3004 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 100.0f, 2);
3005 dt_bauhaus_widget_set_label(g->controls.opacity, _("Opacity"));
3006 dt_bauhaus_slider_set_format(g->controls.opacity, "%");
3007 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.opacity, TRUE, TRUE, 0);
3008 g->controls.flow
3009 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 100.0f, 2);
3010 dt_bauhaus_widget_set_label(g->controls.flow, _("Flow"));
3011 dt_bauhaus_slider_set_format(g->controls.flow, "%");
3012 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.flow, TRUE, TRUE, 0);
3013
3014 GtkWidget *texture_title = gtk_label_new(_("Texture"));
3015 gtk_label_set_xalign(GTK_LABEL(texture_title), 0.0f);
3016 dt_gui_add_class(texture_title, "dt_section_label");
3017 gtk_box_pack_start(GTK_BOX(brush_tab), texture_title, TRUE, TRUE, 0);
3018 g->controls.sprinkles
3019 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 0.0f, 2);
3020 dt_bauhaus_widget_set_label(g->controls.sprinkles, _("Sprinkles"));
3021 dt_bauhaus_slider_set_format(g->controls.sprinkles, "%");
3022 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.sprinkles, TRUE, TRUE, 0);
3023 g->controls.sprinkle_size
3024 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 1.0f, 256.0f, 1.0f, 3.0f, 0);
3025 dt_bauhaus_widget_set_label(g->controls.sprinkle_size, _("Sprinkle size"));
3026 dt_bauhaus_slider_set_format(g->controls.sprinkle_size, _(" px"));
3027 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.sprinkle_size, TRUE, TRUE, 0);
3028 g->controls.sprinkle_coarseness
3029 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 100.0f, 1.0f, 50.0f, 2);
3030 dt_bauhaus_widget_set_label(g->controls.sprinkle_coarseness, _("Coarseness"));
3031 dt_bauhaus_slider_set_format(g->controls.sprinkle_coarseness, "%");
3032 gtk_box_pack_start(GTK_BOX(brush_tab), g->controls.sprinkle_coarseness, TRUE, TRUE, 0);
3033
3034 g_signal_connect(g->controls.brush_shape, "draw", G_CALLBACK(_brush_profile_draw), self);
3035 g_signal_connect(g->controls.brush_shape, "button-press-event", G_CALLBACK(_brush_profile_button_press), self);
3036 g_signal_connect(G_OBJECT(g->controls.brush_mode), "value-changed", G_CALLBACK(_widget_changed), self);
3037 g_signal_connect(g->controls.color, "draw", G_CALLBACK(_color_picker_draw), self);
3038 g_signal_connect(g->controls.color_swatch, "draw", G_CALLBACK(_color_swatch_draw), self);
3039 g_signal_connect(g->controls.color_swatch, "button-press-event", G_CALLBACK(_color_swatch_button_press), self);
3040 g_signal_connect(g->controls.color, "button-press-event", G_CALLBACK(_color_picker_button_press), self);
3041 g_signal_connect(g->controls.color, "button-release-event", G_CALLBACK(_color_picker_button_release), self);
3042 g_signal_connect(g->controls.color, "motion-notify-event", G_CALLBACK(_color_picker_motion), self);
3043 g_signal_connect(G_OBJECT(g->controls.image_colorpicker_source), "value-changed", G_CALLBACK(_widget_changed), self);
3044 g_signal_connect(G_OBJECT(g->controls.size), "value-changed", G_CALLBACK(_widget_changed), self);
3045 g_signal_connect(G_OBJECT(g->controls.distance), "value-changed", G_CALLBACK(_widget_changed), self);
3046 g_signal_connect(G_OBJECT(g->controls.smoothing), "value-changed", G_CALLBACK(_widget_changed), self);
3047 g_signal_connect(G_OBJECT(g->controls.opacity), "value-changed", G_CALLBACK(_widget_changed), self);
3048 g_signal_connect(G_OBJECT(g->controls.flow), "value-changed", G_CALLBACK(_widget_changed), self);
3049 g_signal_connect(G_OBJECT(g->controls.sprinkles), "value-changed", G_CALLBACK(_widget_changed), self);
3050 g_signal_connect(G_OBJECT(g->controls.sprinkle_size), "value-changed", G_CALLBACK(_widget_changed), self);
3051 g_signal_connect(G_OBJECT(g->controls.sprinkle_coarseness), "value-changed", G_CALLBACK(_widget_changed), self);
3052 g_signal_connect(G_OBJECT(g->controls.softness), "value-changed", G_CALLBACK(_widget_changed), self);
3053 g_signal_connect(G_OBJECT(g->controls.hdr_exposure), "value-changed", G_CALLBACK(_widget_changed), self);
3054}
3055
3064{
3065 GtkWidget *preview_title = gtk_label_new(_("Background"));
3066 g->controls.preview_title = preview_title;
3067 gtk_widget_set_halign(preview_title, GTK_ALIGN_START);
3068 GtkWidget *preview_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3069 g->controls.preview_box = preview_box;
3070 GSList *preview_group = NULL;
3071 g->controls.preview_bg_image = gtk_radio_button_new_with_label(preview_group, _("image"));
3072 preview_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(g->controls.preview_bg_image));
3073 g->controls.preview_bg_white = gtk_radio_button_new_with_label(preview_group, _("white"));
3074 preview_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(g->controls.preview_bg_white));
3075 g->controls.preview_bg_grey = gtk_radio_button_new_with_label(preview_group, _("grey"));
3076 preview_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(g->controls.preview_bg_grey));
3077 g->controls.preview_bg_black = gtk_radio_button_new_with_label(preview_group, _("black"));
3078 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_image, TRUE, TRUE, 0);
3079 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_white, TRUE, TRUE, 0);
3080 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_grey, TRUE, TRUE, 0);
3081 gtk_box_pack_start(GTK_BOX(preview_box), g->controls.preview_bg_black, TRUE, TRUE, 0);
3082 gtk_box_pack_start(GTK_BOX(layer_tab), preview_title, FALSE, FALSE, 0);
3083 gtk_box_pack_start(GTK_BOX(layer_tab), preview_box, FALSE, FALSE, 0);
3084
3085 GtkWidget *layer_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3086 GtkWidget *layer_status = gtk_label_new("");
3087 g->controls.layer_status = layer_status;
3088 gtk_widget_set_halign(layer_status, GTK_ALIGN_START);
3089 gtk_label_set_xalign(GTK_LABEL(layer_status), 0.0f);
3090 gtk_label_set_line_wrap(GTK_LABEL(layer_status), TRUE);
3091 GtkWidget *layer_fill_title = gtk_label_new(_("Fill"));
3092 g->controls.layer_fill_title = layer_fill_title;
3093 gtk_widget_set_halign(layer_fill_title, GTK_ALIGN_START);
3094 GtkWidget *layer_action_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3095 g->controls.layer_action_row = layer_action_row;
3096 GtkWidget *layer_fill_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3097 g->controls.layer_fill_row = layer_fill_row;
3098 g->controls.layer_select = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
3099 dt_bauhaus_widget_set_label(g->controls.layer_select, _("Source layer"));
3100 g->controls.delete_layer = gtk_button_new_with_label(_("delete layer"));
3101 g->controls.create_layer = gtk_button_new_with_label(_("create new layer"));
3102 g->controls.rename_layer = gtk_button_new_with_label(_("rename layer"));
3103 g->controls.attach_layer = gtk_button_new_with_label(_("reuse selected layer"));
3104 g->controls.create_background = gtk_button_new_with_label(_("create background from input"));
3105 GtkWidget *fill_white = gtk_button_new_with_label(_("white"));
3106 GtkWidget *fill_black = gtk_button_new_with_label(_("black"));
3107 GtkWidget *fill_transparent = gtk_button_new_with_label(_("transparency"));
3108 gtk_box_pack_start(GTK_BOX(layer_box), layer_status, FALSE, FALSE, 0);
3109 gtk_box_pack_start(GTK_BOX(layer_box), g->controls.layer_select, FALSE, FALSE, 0);
3110 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.create_layer, TRUE, TRUE, 0);
3111 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.rename_layer, TRUE, TRUE, 0);
3112 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.attach_layer, TRUE, TRUE, 0);
3113 gtk_box_pack_start(GTK_BOX(layer_action_row), g->controls.delete_layer, TRUE, TRUE, 0);
3114 gtk_box_pack_start(GTK_BOX(layer_box), layer_action_row, FALSE, FALSE, 0);
3115 gtk_box_pack_start(GTK_BOX(layer_box), layer_fill_title, FALSE, FALSE, 0);
3116 gtk_box_pack_start(GTK_BOX(layer_fill_row), fill_white, TRUE, TRUE, 0);
3117 gtk_box_pack_start(GTK_BOX(layer_fill_row), fill_black, TRUE, TRUE, 0);
3118 gtk_box_pack_start(GTK_BOX(layer_fill_row), fill_transparent, TRUE, TRUE, 0);
3119 gtk_box_pack_start(GTK_BOX(layer_box), layer_fill_row, FALSE, FALSE, 0);
3120 gtk_box_pack_start(GTK_BOX(layer_box), g->controls.create_background, FALSE, FALSE, 0);
3121 gtk_box_pack_start(GTK_BOX(layer_tab), layer_box, FALSE, FALSE, 0);
3122
3123 g_signal_connect(G_OBJECT(g->controls.layer_select), "value-changed", G_CALLBACK(_layer_selected), self);
3124 g_signal_connect(g->controls.preview_bg_image, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3125 g_signal_connect(g->controls.preview_bg_white, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3126 g_signal_connect(g->controls.preview_bg_grey, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3127 g_signal_connect(g->controls.preview_bg_black, "toggled", G_CALLBACK(_preview_bg_toggled), self);
3128 g_signal_connect(g->controls.create_layer, "clicked", G_CALLBACK(_create_layer_clicked), self);
3129 g_signal_connect(g->controls.rename_layer, "clicked", G_CALLBACK(_rename_layer_clicked), self);
3130 g_signal_connect(g->controls.attach_layer, "clicked", G_CALLBACK(_attach_selected_layer_clicked), self);
3131 g_signal_connect(g->controls.create_background, "clicked", G_CALLBACK(_create_background_clicked), self);
3132 g_signal_connect(g->controls.delete_layer, "clicked", G_CALLBACK(_delete_layer_clicked), self);
3133 g_signal_connect(fill_white, "clicked", G_CALLBACK(_fill_white_clicked), self);
3134 g_signal_connect(fill_black, "clicked", G_CALLBACK(_fill_black_clicked), self);
3135 g_signal_connect(fill_transparent, "clicked", G_CALLBACK(_fill_transparent_clicked), self);
3136}
3137
3147{
3148 GtkWidget *mapping_title = gtk_label_new(_("tablet mapping"));
3149 gtk_widget_set_halign(mapping_title, GTK_ALIGN_START);
3150 GtkWidget *grid = gtk_grid_new();
3151 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3152 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3153
3154 const char *labels[4] = { _("size"), _("opacity"), _("flow"), _("hardness") };
3155 const char *rows[3] = { _("pressure"), _("tilt"), _("acceleration") };
3157 dt_drawlayer_mapping_rows(g, mapping);
3158
3159 for(int c = 0; c < 4; c++)
3160 {
3161 GtkWidget *label = gtk_label_new(labels[c]);
3162 gtk_label_set_angle(GTK_LABEL(label), 90.0);
3163 gtk_grid_attach(GTK_GRID(grid), label, c + 1, 0, 1, 1);
3164 }
3165 gtk_grid_attach(GTK_GRID(grid), gtk_label_new(_("profile")), 5, 0, 1, 1);
3166
3167 for(int r = 0; r < 3; r++)
3168 {
3169 gtk_grid_attach(GTK_GRID(grid), gtk_label_new(rows[r]), 0, r + 1, 1, 1);
3170 for(int c = 0; c < 4; c++)
3171 {
3172 *mapping[r].targets[c].widget = gtk_check_button_new();
3173 gtk_grid_attach(GTK_GRID(grid), *mapping[r].targets[c].widget, c + 1, r + 1, 1, 1);
3174 }
3176 dt_bauhaus_combobox_add(*mapping[r].profile.widget, _("linear"));
3177 dt_bauhaus_combobox_add(*mapping[r].profile.widget, _("quadratic"));
3178 dt_bauhaus_combobox_add(*mapping[r].profile.widget, _("square root"));
3179 dt_bauhaus_combobox_add(*mapping[r].profile.widget, _("inverse linear"));
3180 dt_bauhaus_combobox_add(*mapping[r].profile.widget, _("inverse square root"));
3181 dt_bauhaus_combobox_add(*mapping[r].profile.widget, _("inverse quadratic"));
3182 gtk_grid_attach(GTK_GRID(grid), *mapping[r].profile.widget, 5, r + 1, 1, 1);
3183 }
3184
3185 gtk_box_pack_start(GTK_BOX(input_tab), mapping_title, FALSE, FALSE, 0);
3186 gtk_box_pack_start(GTK_BOX(input_tab), grid, FALSE, FALSE, 0);
3187
3188 for(int r = 0; r < 3; r++)
3189 {
3190 for(int c = 0; c < DT_DRAWLAYER_MAPPING_TARGETS; c++) g_signal_connect(*mapping[r].targets[c].widget, "toggled", G_CALLBACK(_widget_changed), self);
3191 g_signal_connect(G_OBJECT(*mapping[r].profile.widget), "value-changed", G_CALLBACK(_widget_changed), self);
3192 }
3193}
3196{
3197 IOP_GUI_ALLOC(drawlayer);
3201 g->ui.widgets = dt_drawlayer_widgets_init();
3206
3207 dt_drawlayer_worker_init(self, &g->stroke.worker, &g->manager.painting_active,
3208 &g->stroke.finish_commit_pending, &g->stroke.stroke_sample_count,
3209 &g->stroke.current_stroke_batch);
3210 g->session.background_job_running = FALSE;
3211 g->session.last_view_x = 0.0f;
3212 g->session.last_view_y = 0.0f;
3213 g->session.last_view_scale = 1.0f;
3214 if(self->dev)
3215 {
3216 g->session.last_view_x = dt_dev_viewport_center_x(self->dev);
3217 g->session.last_view_y = dt_dev_viewport_center_y(self->dev);
3218 g->session.last_view_scale = dt_dev_viewport_scaling(self->dev);
3219 }
3220
3221 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3222 if(self->gui->reset_button) gtk_widget_hide(self->gui->reset_button);
3223
3224 GtkWidget *history_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3225 g->controls.save_layer = gtk_button_new_with_label(_("save sidecar"));
3226 gtk_box_pack_start(GTK_BOX(history_box), g->controls.save_layer, TRUE, TRUE, 0);
3227 gtk_box_pack_start(GTK_BOX(self->gui->widget), history_box, FALSE, FALSE, 0);
3228
3229 GtkWidget *notebook = gtk_notebook_new();
3230 g->controls.notebook = notebook;
3231 gtk_widget_set_hexpand(notebook, TRUE);
3232 gtk_box_pack_start(GTK_BOX(self->gui->widget), notebook, FALSE, FALSE, 0);
3233 // image_colorpicker lives on the "Brush" tab; reset it if still active once the
3234 // user switches away from that tab.
3235 dt_ui_notebook_set_picker_owner(GTK_NOTEBOOK(notebook), self);
3236
3237 GtkWidget *brush_tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3238 GtkWidget *layer_tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3239 GtkWidget *input_tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3240 g->controls.brush_tab = brush_tab;
3241 g->controls.layer_tab = layer_tab;
3242 g->controls.input_tab = input_tab;
3243
3244 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), brush_tab, gtk_label_new(_("Brush")));
3245 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), layer_tab, gtk_label_new(_("Layer")));
3246 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), input_tab, gtk_label_new(_("Input")));
3247 gtk_container_child_set(GTK_CONTAINER(notebook), brush_tab, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
3248 gtk_container_child_set(GTK_CONTAINER(notebook), layer_tab, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
3249 gtk_container_child_set(GTK_CONTAINER(notebook), input_tab, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
3250
3251 _build_brush_tab(self, g, brush_tab);
3252
3253 _build_layer_tab(self, g, layer_tab);
3254
3255 _build_input_tab(self, g, input_tab);
3256
3257 g_signal_connect(g->controls.save_layer, "clicked", G_CALLBACK(_save_layer_clicked), self);
3258
3260 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
3261
3262 if(self->dev)
3263 {
3265 }
3266}
3267
3270{
3273 if(IS_NULL_PTR(g)) return;
3274
3275 /* This function drives conf -> widgets, so nothing it writes may be read back and sent the
3276 * other way. dt_iop_gui_update() already brackets its call to us in a freeze, but seven
3277 * places call gui_update(self) directly; on those the twelve raw
3278 * gtk_toggle_button_set_active() calls below each wake _widget_changed, whose first act is
3279 * to read ALL twenty-six widgets and write them to conf -- from a panel this function has
3280 * only half refilled. Bauhaus suppresses its own emissions; plain GTK toggles do not, so
3281 * the bracket has to be here. The freeze is depth-counted, so nesting inside
3282 * dt_iop_gui_update()'s own costs nothing. */
3284
3285 /* Belt and braces beside the three enumerated writers: any refresh of the panel refills
3286 * the resolved brush on the next pointer event, so a conf path nobody found cannot leave
3287 * the cache stale for longer than one non-motion event. */
3289
3291
3294 dt_bauhaus_slider_set(g->controls.distance, dt_drawlayer_conf_distance());
3295 dt_bauhaus_slider_set(g->controls.smoothing, dt_drawlayer_conf_smoothing());
3298 dt_bauhaus_slider_set(g->controls.sprinkles, dt_drawlayer_conf_sprinkles());
3299 dt_bauhaus_slider_set(g->controls.sprinkle_size, dt_drawlayer_conf_sprinkle_size());
3300 dt_bauhaus_slider_set(g->controls.sprinkle_coarseness, dt_drawlayer_conf_sprinkle_coarseness());
3301 dt_bauhaus_slider_set(g->controls.softness, dt_drawlayer_conf_hardness());
3302 if(g->controls.image_colorpicker_source) dt_bauhaus_combobox_set(g->controls.image_colorpicker_source, dt_drawlayer_conf_pick_source());
3303 dt_bauhaus_slider_set(g->controls.hdr_exposure, dt_drawlayer_conf_hdr_exposure());
3304
3307 if(g->controls.color) gtk_widget_queue_draw(g->controls.color);
3308
3309 /* The cached display and pipeline brush colours depend on the HDR exposure refilled above,
3310 * and `brush_color_valid` is set once and never cleared, so nothing rebuilds them lazily.
3311 * They used to be refreshed only because setting that slider woke _widget_changed on the
3312 * unfrozen paths -- an accident, and one the frozen path never had. Asked for directly,
3313 * every caller gets it. */
3314 {
3315 float display_rgb[3] = { 0.0f };
3316 if(dt_drawlayer_widgets_get_display_color(g->ui.widgets, display_rgb))
3317 dt_drawlayer_sync_cached_brush_colors(self, display_rgb);
3318 }
3319
3321 dt_drawlayer_mapping_rows(g, mapping);
3322 for(int r = 0; r < DT_DRAWLAYER_MAPPING_ROWS; r++)
3323 {
3324 for(int c = 0; c < DT_DRAWLAYER_MAPPING_TARGETS; c++)
3325 {
3326 GtkWidget *target = *mapping[r].targets[c].widget;
3327 if(GTK_IS_TOGGLE_BUTTON(target))
3328 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(target), dt_conf_get_bool(mapping[r].targets[c].conf_key));
3329 }
3330 if(*mapping[r].profile.widget)
3331 dt_bauhaus_combobox_set(*mapping[r].profile.widget,
3332 dt_drawlayer_conf_mapping_profile(mapping[r].profile.conf_key));
3333 }
3334
3338
3339 if(self->dev)
3340 {
3342 }
3343}
3344
3347{
3348 if(dt_iop_gui_data(self))
3349 {
3351 g->session.missing_layer_error[0] = '\0';
3353 }
3354}
3355
3357void gui_focus(dt_iop_module_t *self, gboolean in)
3358{
3359 if(!in)
3360 {
3363 const int pending_samples = g ? (int)g->stroke.stroke_sample_count : 0;
3364 const gboolean had_pending_edits
3365 = (g && (g->process.cache_dirty || g->stroke.stroke_sample_count > 0));
3368 if(had_pending_edits && params)
3369 dt_drawlayer_touch_stroke_commit_hash(params, pending_samples, g->stroke.last_dab_valid, g->stroke.last_dab_x,
3370 g->stroke.last_dab_y, 0u);
3371 if(had_pending_edits && self->dev && !IS_NULL_PTR(params))
3372 {
3379 if(self->post_history_commit) self->post_history_commit(self);
3382 }
3383 }
3384 else if(dt_iop_gui_data(self))
3385 {
3388 }
3389}
3390
3393{
3394 /* Darkroom is leaving and is about to tear down the pixelpipe nodes and history. The paint worker
3395 * runs stroke commits off the GUI thread, and a commit performs a full pipeline history resync
3396 * (dt_dev_pixelpipe_update_history_all) that reads/commits every pipe piece. If that runs while
3397 * leave() frees the nodes, it faults on freed piece->data. Stop and join the worker now, while the
3398 * pipe is still alive, so any in-flight commit completes against valid nodes and no further commit
3399 * can start during teardown. */
3401 if(IS_NULL_PTR(g)) return;
3402 dt_drawlayer_worker_stop(self, g->stroke.worker);
3403}
3404
3425
3436
3439{
3440 if(IS_NULL_PTR(state)) return;
3441
3442 const float pressure_norm
3443 = _clamp01((pointer_input && pointer_input->has_pressure) ? pointer_input->pressure : 1.0f);
3444 const float tilt_norm = _clamp01((pointer_input && pointer_input->has_tilt) ? pointer_input->tilt : 0.0f);
3445 const float accel_norm = _clamp01(pointer_input ? pointer_input->acceleration : 0.0f);
3449 const float pressure_coeff = _mapping_profile_value(pressure_profile, pressure_norm);
3450 const float tilt_coeff = _mapping_profile_value(tilt_profile, tilt_norm);
3451 const float accel_coeff = _mapping_profile_value(accel_profile, accel_norm);
3452
3453 float radius = dt_drawlayer_conf_size();
3454 float opacity = dt_drawlayer_conf_opacity() / 100.0f;
3455 float flow = dt_drawlayer_conf_flow() / 100.0f;
3456 float hardness = dt_drawlayer_conf_hardness();
3457
3458 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_SIZE)) radius *= pressure_coeff;
3459 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_OPACITY)) opacity *= pressure_coeff;
3460 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_FLOW)) flow *= pressure_coeff;
3461 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_PRESSURE_SOFTNESS)) hardness *= pressure_coeff;
3462
3463 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_SIZE)) radius *= tilt_coeff;
3464 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_OPACITY)) opacity *= tilt_coeff;
3465 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_FLOW)) flow *= tilt_coeff;
3466 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_TILT_SOFTNESS)) hardness *= tilt_coeff;
3467
3468 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_SIZE)) radius *= accel_coeff;
3469 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_OPACITY)) opacity *= accel_coeff;
3470 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_FLOW)) flow *= accel_coeff;
3471 if(dt_conf_get_bool(DRAWLAYER_CONF_MAP_ACCEL_SOFTNESS)) hardness *= accel_coeff;
3472
3473 state->pressure = pressure_norm;
3474 state->tilt = tilt_norm;
3475 state->acceleration = accel_norm;
3476 state->radius = fmaxf(0.5f, radius);
3477 state->opacity = _clamp01(opacity);
3478 state->flow = _clamp01(flow);
3479 state->hardness = _clamp01(hardness);
3480}
3481
3482static void _draw_brush_hud(cairo_t *cr, const drawlayer_hud_brush_state_t *state)
3483{
3484 if(IS_NULL_PTR(cr) || IS_NULL_PTR(state)) return;
3485
3486 char lines[3][128] = { { 0 } };
3487 g_snprintf(lines[0], sizeof(lines[0]), _("size %.1f px hardness %.2f%%"), state->radius * 2.0f,
3488 state->hardness * 100.0f);
3489 g_snprintf(lines[1], sizeof(lines[1]), _("opacity %.2f%% flow %.2f%%"), state->opacity * 100.0f,
3490 state->flow * 100.0f);
3491 g_snprintf(lines[2], sizeof(lines[2]), _("pressure %.2f%% tilt %.2f%% acceleration %.2f%%"),
3492 state->pressure * 100.0f, state->tilt * 100.0f, state->acceleration * 100.0f);
3493
3494 const double pad = DT_PIXEL_APPLY_DPI(6.0);
3495 const double line_h = DT_PIXEL_APPLY_DPI(13.0);
3496 const double fs = DT_PIXEL_APPLY_DPI(12.0);
3497 double max_w = 0.0;
3498
3499 cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
3500 cairo_set_font_size(cr, fs);
3501 for(int i = 0; i < 3; i++)
3502 {
3503 cairo_text_extents_t ext = { 0 };
3504 cairo_text_extents(cr, lines[i], &ext);
3505 max_w = fmax(max_w, ext.x_advance);
3506 }
3507
3508 const double box_w = max_w + 2.0 * pad;
3509 const double box_h = 3.0 * line_h + 2.0 * pad;
3510 const double x = DT_PIXEL_APPLY_DPI(10.0);
3511 const double y = DT_PIXEL_APPLY_DPI(10.0);
3512
3513 cairo_save(cr);
3514 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.55);
3515 cairo_rectangle(cr, x, y, box_w, box_h);
3516 cairo_fill(cr);
3517
3518 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.92);
3519 for(int i = 0; i < 3; i++)
3520 {
3521 cairo_move_to(cr, x + pad, y + pad + (i + 1) * line_h - DT_PIXEL_APPLY_DPI(2.0));
3522 cairo_show_text(cr, lines[i]);
3523 }
3524 cairo_restore(cr);
3525}
3526
3528void gui_post_expose(dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx,
3529 int32_t pointery)
3530{
3532 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return;
3533
3534 cairo_save(cr);
3535 cairo_set_line_width(cr, 1.0);
3536 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.8);
3537
3538 if(g->session.pointer_valid)
3539 {
3540 dt_control_pointer_input_t pointer_input = { 0 };
3541 dt_control_get_pointer_input(&pointer_input);
3542 const float widget_x = isfinite(pointer_input.x)
3543 ? (float)pointer_input.x
3544 : ((pointerx >= 0 && pointery >= 0) ? (float)pointerx : -1.0f);
3545 const float widget_y = isfinite(pointer_input.y)
3546 ? (float)pointer_input.y
3547 : ((pointerx >= 0 && pointery >= 0) ? (float)pointery : -1.0f);
3548 if(widget_x < 0.0f || widget_y < 0.0f)
3549 {
3550 cairo_restore(cr);
3551 return;
3552 }
3553 drawlayer_hud_brush_state_t hud = { 0 };
3554 _compute_hud_brush_state(&pointer_input, &hud);
3555
3556 float radius = hud.radius;
3557 const gboolean show_paint_fill = _brush_paints_color();
3558
3559 float draw_x = widget_x;
3560 float draw_y = widget_y;
3561 float lx = 0.0f;
3562 float ly = 0.0f;
3563 float widget_radius = radius * dt_dev_get_overlay_scale(self->dev);
3564 if(dt_drawlayer_widget_to_layer_coords(self, widget_x, widget_y, &lx, &ly))
3565 {
3567 .x = lx,
3568 .y = ly,
3569 .radius = radius,
3570 };
3571 if(!dt_drawlayer_layer_to_widget_coords(self, lx, ly, &draw_x, &draw_y))
3572 {
3573 draw_x = widget_x;
3574 draw_y = widget_y;
3575 }
3576 widget_radius = dt_drawlayer_widget_brush_radius(self, &dab, widget_radius);
3577 }
3578
3579 // Draw the brush mipmap
3580 const float draw_radius = fmaxf(0.5f, widget_radius);
3581 if(show_paint_fill)
3582 {
3583 _ensure_cursor_stamp_surface(self, draw_radius, hud.opacity, hud.hardness);
3584 if(g->ui.cursor_surface)
3585 {
3586 const float surface_half_extent = 0.5f * (float)g->ui.cursor_surface_size / (float)g->ui.cursor_surface_ppd;
3587 cairo_set_source_surface(cr, g->ui.cursor_surface, draw_x - surface_half_extent, draw_y - surface_half_extent);
3588 cairo_paint(cr);
3589 }
3590 }
3591
3592 // Draw the outer circle in case we are lost
3593 cairo_set_source_rgba(cr, 0., 0., 0., 0.5);
3594 cairo_set_line_width(cr, 2.5);
3595 cairo_arc(cr, draw_x, draw_y, draw_radius + 1.0f, 0.0, 2.0 * M_PI);
3596 cairo_stroke(cr);
3597
3598 cairo_set_source_rgba(cr, 1., 1., 1., 0.5);
3599 cairo_set_line_width(cr, 1.0);
3600 cairo_arc(cr, draw_x, draw_y, draw_radius, 0.0, 2.0 * M_PI);
3601 cairo_stroke(cr);
3602
3603 if(self->dev->gui_module == self)
3604 {
3605 _draw_brush_hud(cr, &hud);
3606 }
3607 }
3608
3609 cairo_restore(cr);
3610}
3611
3620
3622int mouse_moved(dt_iop_module_t *self, double x, double y, double pressure, int which)
3623{
3625 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev)) return 0;
3626
3628 {
3629 /* When the standard image color picker is active, drawlayer must stop
3630 * capturing the pointer entirely so darkroom can drive the picker overlay
3631 * and sampling path without competing cursor state from the brush tool. */
3632 if(g->session.pointer_valid)
3635 return 0;
3636 }
3637
3638 if(!g->session.pointer_valid)
3639 {
3641 .runtime = {
3642 .self = self,
3643 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3644 .gui = g,
3645 .manager = &g->manager,
3646 .process_state = &g->process,
3647 },
3648 };
3649 const dt_drawlayer_runtime_host_t enter_manager = {
3650 .user_data = &enter_host,
3651 };
3653 &g->manager,
3655 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_MOUSE_ENTER,
3656 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3657 },
3658 &enter_manager);
3659 }
3660
3661 if(g->manager.painting_active)
3662 {
3663 dt_drawlayer_paint_raw_input_t input = { 0 };
3664 drawlayer_runtime_host_context_t runtime_host = {
3665 .runtime = {
3666 .self = self,
3667 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3668 .gui = g,
3669 .manager = &g->manager,
3670 .process_state = &g->process,
3671 },
3672 .raw_input = &input,
3673 };
3674 const dt_drawlayer_runtime_host_t runtime_manager = {
3675 .user_data = &runtime_host,
3676 };
3679 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_SAMPLE,
3680 };
3681 const dt_drawlayer_runtime_result_t dispatch
3682 = _build_raw_input_event(self, x, y, pressure, DT_DRAWLAYER_PAINT_STROKE_MIDDLE, &input)
3683 ? dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager)
3684 : (dt_drawlayer_runtime_result_t){ .ok = FALSE, .raw_input_ok = FALSE };
3685 if(!dispatch.ok || !dispatch.raw_input_ok)
3686 {
3687 /* Queue overflow or enqueue failure aborts the current stroke so GUI and
3688 * worker stay in sync on stroke boundaries. */
3689 dt_print(DT_DEBUG_PERF, "[drawlayer] stroke abort from mouse_moved: dispatch.ok=%d raw_input_ok=%d\n",
3690 dispatch.ok, dispatch.raw_input_ok);
3692 &g->manager,
3694 .event = DT_DRAWLAYER_RUNTIME_EVENT_GUI_STROKE_ABORT,
3695 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3696 },
3697 &runtime_manager);
3698 }
3699 }
3700 else
3701 {
3702 drawlayer_runtime_host_context_t runtime_host = {
3703 .runtime = {
3704 .self = self,
3705 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3706 .gui = g,
3707 .manager = &g->manager,
3708 .process_state = &g->process,
3709 },
3710 };
3711 const dt_drawlayer_runtime_host_t runtime_manager = {
3712 .user_data = &runtime_host,
3713 };
3716 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_SAMPLE,
3717 };
3718 dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3719 }
3720
3721 gtk_widget_queue_draw(dt_gui_center_widget());
3722 return g->manager.painting_active ? 1 : 0;
3723}
3724
3726int button_pressed(dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
3727{
3730 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev) || which != 1) return 0;
3732
3733 if(self->dev->gui_module != self)
3734 {
3736 return 1;
3737 }
3738
3739 dt_control_pointer_input_t pointer_input = { 0 };
3740 dt_control_get_pointer_input(&pointer_input);
3741 const float input_wx = isfinite(pointer_input.x) ? (float)pointer_input.x : (float)x;
3742 const float input_wy = isfinite(pointer_input.y) ? (float)pointer_input.y : (float)y;
3743 const float pressure_norm = pointer_input.has_pressure ? _clamp01(pointer_input.pressure) : _clamp01(pressure);
3744 uint32_t stroke_batch = g->stroke.current_stroke_batch + 1u;
3745 if(stroke_batch == 0u) stroke_batch++;
3747 .wx = input_wx,
3748 .wy = input_wy,
3749 .pressure = pressure_norm,
3750 .tilt = (float)_clamp01(pointer_input.tilt),
3751 .acceleration = (float)_clamp01(pointer_input.acceleration),
3752 .event_ts = g_get_monotonic_time(),
3753 .stroke_batch = stroke_batch,
3754 .event_index = 1u,
3755 .stroke_pos = DT_DRAWLAYER_PAINT_STROKE_FIRST,
3756 };
3757 _fill_input_layer_coords(self, &first);
3758 _fill_input_brush_settings(self, &first);
3759 drawlayer_runtime_host_context_t runtime_host = {
3760 .runtime = {
3761 .self = self,
3762 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3763 .gui = g,
3764 .manager = &g->manager,
3765 .process_state = &g->process,
3766 },
3767 .raw_input = &first,
3768 };
3769 const dt_drawlayer_runtime_host_t runtime_manager = {
3770 .user_data = &runtime_host,
3771 };
3775 };
3776 const dt_drawlayer_runtime_result_t dispatch
3777 = dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3778 if(!dispatch.ok)
3779 return 0;
3780 if(!dispatch.raw_input_ok)
3781 dt_control_log(_("failed to queue live drawing stroke"));
3784 return 1;
3785}
3786
3788int button_released(dt_iop_module_t *self, double x, double y, int which, uint32_t state)
3789{
3791 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev) || which != 1) return 0;
3792
3793 if(g->manager.painting_active)
3794 {
3795 dt_control_pointer_input_t pointer_input = { 0 };
3796 dt_control_get_pointer_input(&pointer_input);
3797 const float input_wx = isfinite(pointer_input.x) ? (float)pointer_input.x : (float)x;
3798 const float input_wy = isfinite(pointer_input.y) ? (float)pointer_input.y : (float)y;
3800 .wx = input_wx,
3801 .wy = input_wy,
3802 .pressure = pointer_input.has_pressure ? _clamp01(pointer_input.pressure) : 1.0f,
3803 .tilt = (float)_clamp01(pointer_input.tilt),
3804 .acceleration = (float)_clamp01(pointer_input.acceleration),
3805 .event_ts = g_get_monotonic_time(),
3806 .stroke_batch = g->stroke.current_stroke_batch,
3807 .event_index = ++g->stroke.stroke_event_index,
3808 .stroke_pos = DT_DRAWLAYER_PAINT_STROKE_END,
3809 };
3810 _fill_input_layer_coords(self, &end);
3811 _fill_input_brush_settings(self, &end);
3812 drawlayer_runtime_host_context_t runtime_host = {
3813 .runtime = {
3814 .self = self,
3815 .runtime_params = (const dt_iop_drawlayer_params_t *)self->params,
3816 .gui = g,
3817 .manager = &g->manager,
3818 .process_state = &g->process,
3819 },
3820 .raw_input = &end,
3821 };
3822 const dt_drawlayer_runtime_host_t runtime_manager = {
3823 .user_data = &runtime_host,
3824 };
3828 };
3829 const dt_drawlayer_runtime_result_t dispatch
3830 = dt_drawlayer_runtime_manager_update(&g->manager, &update, &runtime_manager);
3831 if(!dispatch.ok || !dispatch.raw_input_ok)
3832 dt_control_log(_("failed to queue drawing stroke end"));
3834 return 1;
3835 }
3836
3837 return 0;
3838}
3839
3841int scrolled(dt_iop_module_t *self, double x, double y, int up, uint32_t state)
3842{
3843 if(IS_NULL_PTR(self->dev) || self->dev->gui_module != self) return 0;
3844
3845 // A drawn blend mask being edited (this module's own blending options, not its own painting
3846 // state below, which never goes through dev->forms) must not leak scroll into the brush-size
3847 // change here.
3848 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)) return 0;
3849
3850 const gboolean increase = dt_mask_scroll_increases(up);
3851 const float factor = increase ? 1.1f : 0.9f;
3852 const float new_size = CLAMP(dt_drawlayer_conf_size() * factor, 1.0f, 2048.0f);
3853 /* The wheel changes the brush mid-stroke, which is exactly the case a per-event cache
3854 * must not miss. */
3857
3858 if(dt_iop_gui_data(self))
3859 {
3861 dt_bauhaus_slider_set(g->controls.size, new_size);
3863 }
3864 return 1;
3865}
3866
3885 const dt_dev_pixelpipe_t *pipe,
3888 const gboolean have_gui,
3889 const dt_iop_roi_t *const roi_in,
3890 const dt_iop_roi_t *const roi_out,
3891 const gboolean use_opencl)
3892{
3893 const gboolean display_pipe = have_gui && (pipe == self->dev->pipe || pipe == self->dev->preview_pipe);
3895 dt_drawlayer_runtime_manager_bind_piece(&data->headless_manager, &data->process, gui ? &gui->manager : NULL,
3896 gui ? &gui->process : NULL, display_pipe, &data->runtime_manager,
3897 &data->runtime_process, &data->runtime_display_pipe);
3898
3899 const dt_iop_drawlayer_params_t *runtime_params = &data->params;
3900 if(runtime_params->layer_name[0] != '\0')
3901 _refresh_piece_base_cache(self, data, runtime_params, (dt_dev_pixelpipe_t *)pipe,
3903
3905 .self = self,
3906 .pipe = pipe,
3907 .piece = (dt_dev_pixelpipe_iop_t *)piece,
3908 .runtime_params = runtime_params,
3909 .gui = gui,
3910 .manager = data->runtime_manager,
3911 .process_state = data->runtime_process,
3912 .display_pipe = data->runtime_display_pipe,
3913 .roi_in = roi_in,
3914 .roi_out = roi_out,
3915 .use_opencl = use_opencl,
3916 };
3917}
3918
3919#ifdef HAVE_OPENCL
3921int 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)
3922{
3923 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3924 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3925 const gint64 process_t0 = g_get_monotonic_time();
3928 const gboolean have_gui = (!IS_NULL_PTR(gui));
3929 const drawlayer_runtime_request_t runtime_request
3930 = _process_bind_runtime(self, pipe, piece, gui, have_gui, roi_in, roi_out, TRUE);
3931 dt_iop_drawlayer_data_t *runtime_data = (dt_iop_drawlayer_data_t *)piece->data;
3932 const dt_iop_drawlayer_params_t *runtime_params = &runtime_data->params;
3933 drawlayer_runtime_host_context_t runtime_host = {
3934 .runtime = runtime_request,
3935 };
3936 const dt_drawlayer_runtime_host_t runtime_manager = {
3937 .user_data = &runtime_host,
3938 };
3939 const dt_drawlayer_runtime_update_request_t process_pre = {
3941 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3942 };
3945 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
3946 };
3947 dt_drawlayer_runtime_manager_t *const manager = runtime_request.manager;
3948 dt_drawlayer_runtime_manager_update(manager, &process_pre, &runtime_manager);
3949 if(IS_NULL_PTR(global) || global->kernel_premult_over < 0)
3950 {
3951 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
3952 return FALSE;
3953 }
3954 gboolean fallback = (runtime_params->layer_name[0] == '\0');
3955
3956 const drawlayer_preview_background_t preview_bg = _resolve_preview_background(self, gui);
3958 if(IS_NULL_PTR(scratch))
3959 {
3960 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
3961 return FALSE;
3962 }
3963
3964 dt_drawlayer_runtime_source_t source = { 0 };
3965 if(!fallback) fallback = !_update_runtime_state(&runtime_request, &source);
3966 /* Everything above -- bind_piece, _refresh_piece_base_cache, the runtime state machine and
3967 * _update_runtime_state's source resolution -- is prologue. The pipeline reports `Drawing'
3968 * at 53 ms a frame while the composite inside it measures 15, so ~38 ms is somewhere else:
3969 * either here, or in the blend op the pipe runs AFTER process_cl returns. Splitting the two
3970 * is the difference between optimising drawlayer and optimising blend.c. */
3971 const gint64 prologue_end = g_get_monotonic_time();
3972 if(!fallback)
3973 {
3974 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
3975 const gboolean reuse_device_buffers = realtime;
3976 dt_iop_roi_t target_roi = source.target_roi;
3977 dt_iop_roi_t source_roi = source.source_roi;
3978 int source_width = source.width;
3979 int source_height = source.height;
3980 const float *source_pixels = source.pixels;
3981 dt_pixel_cache_entry_t *source_entry = source.cache_entry;
3982
3983 if(fallback)
3984 goto process_cl_fallback;
3985
3986 /* The realtime output cacheline is reused in place while only the layer host
3987 * pixels change, so when the same device buffer comes back for the same layer
3988 * and geometry it still holds the previous full composite and only the painted
3989 * sub-rect needs refreshing. The node's global_hash is NOT stable across a
3990 * stroke (the heartbeat bumps stroke_commit_hash every frame), so we key on
3991 * the stable base-patch layer identity instead. Validate here; the blend
3992 * records the new state on success. */
3993 dt_drawlayer_process_state_t *const pstate = runtime_request.process_state;
3994 const uint64_t layer_hash = pstate ? pstate->base_patch.cache_hash : 0;
3995 const gboolean g_valid = pstate && pstate->last_composite_valid;
3996 const gboolean g_devout = pstate && pstate->last_composite_dev_out == (void *)dev_out;
3997 const gboolean g_hash = pstate && layer_hash != 0 && pstate->last_composite_layer_hash == layer_hash;
3998 const gboolean g_roi = pstate && !memcmp(&pstate->last_composite_target_roi, &target_roi, sizeof(dt_iop_roi_t));
3999 const gboolean allow_partial = realtime && g_valid && g_devout && g_hash && g_roi;
4000 /* Once per composite, not per dab, so it belongs under -d perf rather than -d verbose:
4001 * asking for verbose to find out why a frame was slow also turns on a per-dab print, and
4002 * at the default 1 px spacing that is thousands of lines a second drowning the answer.
4003 * Which of the four conditions declined is the whole diagnosis when the full path runs
4004 * every frame. */
4005 if(realtime && pstate && !allow_partial)
4007 "[drawlayer] partial gate declined: valid=%d devout=%d hash=%d roi=%d "
4008 "(cache_on_ram=%d bypass=%d)\n",
4009 g_valid, g_devout, g_hash, g_roi, piece->cache_output_on_ram ? 1 : 0,
4010 piece->bypass_cache ? 1 : 0);
4011
4012 const drawlayer_blend_cl_request_t blend = {
4013 .devid = pipe->devid,
4014 .kernel_premult_over = global->kernel_premult_over,
4015 .dev_out = dev_out,
4016 .dev_in = dev_in,
4017 .scratch = scratch,
4018 .layer_pixels = source_pixels,
4019 .source_entry = source_entry,
4020 .source_w = source_width,
4021 .source_h = source_height,
4022 .process = runtime_request.process_state,
4023 .target_roi = &target_roi,
4024 .source_roi = &source_roi,
4025 .use_preview_bg = preview_bg.enabled,
4026 .preview_bg = preview_bg.value,
4027 .realtime_reuse = reuse_device_buffers,
4028 .allow_partial = allow_partial,
4029 };
4030 gboolean ok = _blend_layer_over_input_cl(&blend);
4031
4032 if(pstate)
4033 {
4034 if(ok && realtime && !preview_bg.enabled && layer_hash != 0)
4035 {
4036 /* dev_out now holds a valid full composite for this layer/geometry,
4037 * whether produced by the full or the partial path. */
4038 pstate->last_composite_dev_out = (void *)dev_out;
4039 pstate->last_composite_layer_hash = layer_hash;
4040 pstate->last_composite_target_roi = target_roi;
4041 pstate->last_composite_valid = TRUE;
4042 }
4043 else
4044 {
4045 pstate->last_composite_valid = FALSE;
4046 pstate->last_composite_dev_out = NULL;
4047 }
4048 }
4049
4050 process_post.release = (dt_drawlayer_runtime_release_t){
4051 .process = runtime_request.process_state,
4052 .source = &source,
4053 };
4056 "[drawlayer] process_cl prologue=%.2f ms composite+epilogue=%.2f ms total=%.2f ms ok=%d\n",
4057 (prologue_end - process_t0) / 1000.0,
4058 (g_get_monotonic_time() - prologue_end) / 1000.0,
4059 (g_get_monotonic_time() - process_t0) / 1000.0, ok ? 1 : 0);
4060 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4061 return ok;
4062 }
4063
4064process_cl_fallback:
4066 dt_print(DT_DEBUG_PERF, "[drawlayer] process_cl step=no-cache-pass-through total=%.3f\n",
4067 (g_get_monotonic_time() - process_t0) / 1000.0);
4068 const gboolean ok = dt_iop_clip_and_zoom_roi_cl(pipe->devid, dev_out, dev_in, roi_out, roi_in)
4069 == CL_SUCCESS;
4070 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4071 return ok;
4072}
4073#endif
4074
4078 const void *const ivoid, void *const ovoid)
4079{
4080 const dt_iop_roi_t *const roi_in = &piece->roi_in;
4081 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4083 const gboolean have_gui = (!IS_NULL_PTR(gui));
4084 const drawlayer_runtime_request_t runtime_request
4085 = _process_bind_runtime(self, pipe, piece, gui, have_gui, roi_in, roi_out, FALSE);
4086 dt_iop_drawlayer_data_t *runtime_data = (dt_iop_drawlayer_data_t *)piece->data;
4087 const dt_iop_drawlayer_params_t *runtime_params = &runtime_data->params;
4088 drawlayer_runtime_host_context_t runtime_host = {
4089 .runtime = runtime_request,
4090 };
4091 const dt_drawlayer_runtime_host_t runtime_manager = {
4092 .user_data = &runtime_host,
4093 };
4094 const dt_drawlayer_runtime_update_request_t process_pre = {
4096 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
4097 };
4100 .raw_input_kind = DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE,
4101 };
4102 const float *input = (const float *)ivoid;
4103 float *output = (float *)ovoid;
4104 const size_t pixels = (size_t)roi_out->width * roi_out->height;
4105 const gint64 process_t0 = g_get_monotonic_time();
4106 dt_drawlayer_runtime_manager_t *const manager = runtime_request.manager;
4107 dt_drawlayer_runtime_manager_update(manager, &process_pre, &runtime_manager);
4108
4109 /* `process()` keeps the pipeline contract simple:
4110 * - the in-memory cache and stroke math stay in float32,
4111 * - TIFF I/O converts to/from half-float only at the file boundary. */
4112 gboolean fallback = (runtime_params->layer_name[0] == '\0');
4113 const drawlayer_preview_background_t preview_bg = _resolve_preview_background(self, gui);
4114
4115 dt_drawlayer_runtime_source_t source = { 0 };
4116 if(!fallback) fallback = !_update_runtime_state(&runtime_request, &source);
4117 if(!fallback)
4118 {
4119 const float *layer_pixels = source.pixels;
4121 if(IS_NULL_PTR(scratch)) fallback = TRUE;
4122
4123 float *layerbuf = NULL;
4124 if(!fallback)
4125 layerbuf = dt_drawlayer_cache_ensure_scratch_buffer(&scratch->layerbuf, &scratch->layerbuf_pixels, pixels,
4126 "drawlayer process scratch");
4127 if(!fallback && IS_NULL_PTR(layerbuf)) fallback = TRUE;
4128 if(fallback)
4129 {
4130 process_post.release = (dt_drawlayer_runtime_release_t){
4131 .process = runtime_request.process_state,
4132 .source = &source,
4133 };
4134 goto fallback_pass_through;
4135 }
4136 /* Bilinear (not the user pref) for the layer matte — see the OpenCL path in
4137 * _drawlayer_copy_or_resample_layer_roi: no negative lobes => no overshoot,
4138 * so no edge halos / out-of-range premultiplied alpha. */
4139 {
4141 dt_interpolation_resample(itor, layerbuf, &source.target_roi, source.pixels, &source.source_roi);
4142 }
4143 layer_pixels = layerbuf;
4144
4145 _blend_layer_over_input(output, input, layer_pixels, pixels, preview_bg.enabled, preview_bg.value);
4147 dt_print(DT_DEBUG_PERF, "[drawlayer] process step=blend-base total=%.3f\n",
4148 (g_get_monotonic_time() - process_t0) / 1000.0);
4149
4150 process_post.release = (dt_drawlayer_runtime_release_t){
4151 .process = runtime_request.process_state,
4152 .source = &source,
4153 };
4154 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4155 return 0;
4156 }
4157
4158 /* The sidecar is intentionally managed outside of `process()`. Once a layer
4159 * is loaded, the in-memory caches are authoritative until module-level flush
4160 * points write them back. If we do not have a usable in-memory cache here,
4161 * the correct backend behavior is therefore a no-op pass-through rather than
4162 * reopening/scanning/loading the TIFF in the hot process path. */
4163fallback_pass_through:
4165 dt_print(DT_DEBUG_PERF, "[drawlayer] process step=no-cache-pass-through total=%.3f\n",
4166 (g_get_monotonic_time() - process_t0) / 1000.0);
4167 dt_iop_image_copy_by_size(output, input, roi_out->width, roi_out->height, 4);
4168 dt_drawlayer_runtime_manager_update(manager, &process_post, &runtime_manager);
4169 return 0;
4170}
GtkWidget * dt_gui_main_window(void)
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2139
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1966
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1973
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1635
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1701
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1827
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3426
Dab-level brush rasterization API for drawlayer.
@ DT_DRAWLAYER_BRUSH_MODE_PAINT
Definition brush.h:50
@ DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN
Definition brush.h:42
@ DT_DRAWLAYER_BRUSH_SHAPE_LINEAR
Definition brush.h:41
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
@ IOP_CS_NONE
GtkWidget * dt_color_picker_new_with_cst(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w, const dt_iop_colorspace_type_t cst)
@ DT_COLOR_PICKER_POINT_AREA
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
int dt_conf_get_bool(const char *name)
void dt_conf_set_float(const char *name, float val)
void dt_control_get_pointer_input(dt_control_pointer_input_t *input)
Definition control.c:83
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
void dt_control_mouse_is_painting(gboolean state)
Definition control.c:437
#define dt_control_set_cursor_visible(visible)
Definition control.h:153
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:654
gboolean dt_drawlayer_raw_placement_for_frame(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *const frame, dt_drawlayer_raw_placement_t *placement)
float dt_drawlayer_widget_brush_radius(dt_iop_module_t *self, const dt_drawlayer_brush_dab_t *dab, const float fallback)
gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy, float *lx, float *ly)
gboolean dt_drawlayer_layer_to_widget_coords(dt_iop_module_t *self, const float x, const float y, float *wx, float *wy)
Shared coordinate transforms and geometry computations for drawlayer.
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:531
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
gboolean dt_dev_add_history_item_ext(dt_develop_t *dev, struct dt_iop_module_t *module, gboolean enable, gboolean force_new_item) REQUIRES(dev -> history_mutex)
Append or update a history item for a module.
uint64_t dt_dev_history_compute_hash(dt_develop_t *dev) REQUIRES_SHARED(dev -> history_mutex)
Get the integrity checksum of the whole history stack. This should be done ONLY when history is chang...
void dt_dev_write_history(dt_develop_t *dev, gboolean async)
Thread-safe wrapper around dt_dev_write_history_ext() for dev->image_storage.id.
void dt_dev_history_notify_change(dt_develop_t *dev, const int32_t imgid)
Notify the rest of the app that history changes were written.
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_update_history_all(dev)
float dt_dev_viewport_center_y(const dt_develop_t *dev)
float dt_dev_viewport_center_x(const dt_develop_t *dev)
float dt_dev_viewport_scaling(const dt_develop_t *dev)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
__DT_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
dt_dev_pixelpipe_iop_t * dt_dev_distort_get_iop_pipe(struct dt_dev_pixelpipe_t *pipe, struct dt_iop_module_t *module)
Definition develop.c:1900
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:2026
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1921
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1932
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:499
GtkWidget * geometry
its size, under the preview
void init(dt_iop_module_t *module)
Allocate and initialize module parameter blocks.
Definition drawlayer.c:2819
static void _sanitize_requested_layer_name(const char *requested, char *name, size_t name_size)
Definition drawlayer.c:1588
static gboolean _brush_paints_color(void)
Does the brush currently lay down the chosen colour?
Definition drawlayer.c:1490
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:1703
const char ** description(struct dt_iop_module_t *self)
Module description strings used by UI/help.
Definition drawlayer.c:2766
int default_group()
Return default iop group for drawlayer module.
Definition drawlayer.c:2801
gboolean dt_drawlayer_flush_layer_cache(dt_iop_module_t *self)
Definition layers.c:364
static void _draw_brush_hud(cairo_t *cr, const drawlayer_hud_brush_state_t *state)
Definition drawlayer.c:3482
void gui_reset(dt_iop_module_t *self)
Reset GUI/session state for current drawlayer instance.
Definition drawlayer.c:2883
static gboolean _confirm_delete_layer(dt_iop_module_t *self, const gboolean removing_module)
Definition drawlayer.c:1987
void dt_drawlayer_invalidate_brush_settings_cache(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:243
static gboolean _background_layer_job_done_idle(gpointer user_data)
Definition drawlayer.c:2188
static drawlayer_process_scratch_t * _get_process_scratch(void)
Definition drawlayer.c:388
void dt_drawlayer_show_runtime_feedback(const dt_iop_drawlayer_gui_data_t *g, const dt_drawlayer_runtime_feedback_t feedback)
Definition drawlayer.c:2736
static void _preview_bg_toggled(GtkToggleButton *button, gpointer user_data)
Definition drawlayer.c:2618
int mouse_leave(dt_iop_module_t *self)
Mouse leave handler.
Definition drawlayer.c:3613
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:2333
void dt_drawlayer_sanitize_params(dt_iop_module_t *self, dt_iop_drawlayer_params_t *params)
Definition drawlayer.c:1546
void dt_drawlayer_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:185
static gboolean _fill_current_layer(dt_iop_module_t *self, const float value)
Definition drawlayer.c:2436
gboolean dt_drawlayer_sync_widget_cache(dt_iop_module_t *self)
Definition layers.c:474
void gui_focus(dt_iop_module_t *self, gboolean in)
Focus transition hook (enter/leave) for drawlayer GUI mode.
Definition drawlayer.c:3357
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:850
static gboolean _color_picker_set_from_position(dt_iop_module_t *self, const float x, const float y)
Definition drawlayer.c:1369
static gboolean _drawlayer_acquire_layer_image(const int devid, dt_pixel_cache_entry_t *resolved_entry, const gboolean realtime_reuse, 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:816
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition drawlayer.c:3269
static void _attach_selected_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2385
__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:4077
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:2703
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:3726
static gboolean _color_picker_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition drawlayer.c:1537
static drawlayer_runtime_request_t _process_bind_runtime(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_iop_drawlayer_gui_data_t *gui, const gboolean have_gui, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const gboolean use_opencl)
Everything process() and process_cl() must do identically before they diverge.
Definition drawlayer.c:3884
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:1331
static void _fill_transparent_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2516
void dt_drawlayer_begin_gui_stroke_capture(dt_iop_module_t *self, const dt_drawlayer_paint_raw_input_t *first_input)
Definition drawlayer.c:2669
static gboolean _drawlayer_acquire_source_image(const int devid, const float *layer_pixels, dt_pixel_cache_entry_t *resolved_entry, 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:721
static void _show_drawlayer_modal_message(const GtkMessageType type, const char *primary, const char *secondary)
Definition drawlayer.c:1318
static void _destroy_process_scratch(gpointer data)
Definition drawlayer.c:377
static void _create_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2595
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition drawlayer.c:2829
void dt_drawlayer_release_all_base_patch_extra_refs(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:466
static void _save_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2523
static void _layer_selected(GtkWidget *widget, gpointer user_data)
Definition drawlayer.c:2364
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:686
static gboolean _brush_profile_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition drawlayer.c:1420
static void _build_pre_module_filter_string(dt_iop_module_t *self, char *filter, const size_t filter_size)
Definition drawlayer.c:2168
const char * name()
Module display name.
Definition drawlayer.c:2760
static void _sync_brush_profile_preview_widget(dt_iop_module_t *self)
Definition drawlayer.c:1409
static void _widget_changed(GtkWidget *widget, gpointer user_data)
Definition drawlayer.c:2307
static int _blend_layer_over_input_cl(const drawlayer_blend_cl_request_t *const req)
Definition drawlayer.c:946
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:152
static gboolean _clear_current_layer(dt_iop_module_t *self)
Definition drawlayer.c:2474
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:3841
gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, gboolean record_history)
Definition drawlayer.c:1804
void quiesce(dt_iop_module_t *self)
Destroy GUI resources and stop background worker.
Definition drawlayer.c:3392
void gui_init(dt_iop_module_t *self)
Build GUI widgets and initialize worker/caches.
Definition drawlayer.c:3195
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:785
static gboolean _create_background_layer_from_input(dt_iop_module_t *self)
Definition drawlayer.c:2240
void dt_drawlayer_set_pipeline_realtime_mode(dt_iop_module_t *self, gboolean state)
Definition layers.c:456
static drawlayer_wait_dialog_t _show_drawlayer_wait_dialog(const char *title, const char *message)
Definition drawlayer.c:1234
int64_t dt_drawlayer_sidecar_timestamp_from_path(const char *path)
Definition drawlayer.c:1169
static void _rename_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2402
static gboolean _color_picker_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1522
static gboolean _color_swatch_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1397
gboolean dt_drawlayer_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:354
static gboolean _color_picker_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1514
void gui_cleanup(dt_iop_module_t *self)
Definition drawlayer.c:3405
static gboolean _drawlayer_modal_wait_tick(gpointer user_data)
Definition drawlayer.c:1280
static void _sync_preview_bg_buttons(dt_iop_module_t *self)
Definition drawlayer.c:1785
static gboolean _delete_current_layer(dt_iop_module_t *self)
Definition drawlayer.c:1926
void cleanup_global(dt_iop_module_so_t *module)
Release global OpenCL resources for drawlayer.
Definition drawlayer.c:2790
gboolean module_will_remove(dt_iop_module_t *self)
Hook called before module removal from history stack.
Definition drawlayer.c:2907
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:881
int mouse_moved(dt_iop_module_t *self, double x, double y, double pressure, int which)
Mouse motion handler.
Definition drawlayer.c:3622
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:2813
static gboolean _color_swatch_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition drawlayer.c:1389
int flags()
Return module capability flags.
Definition drawlayer.c:2807
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:1446
static gboolean _profile_key_is_sane(const char *value)
Definition drawlayer.c:1153
static gboolean _color_picker_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition drawlayer.c:1380
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:3788
void dt_drawlayer_retain_base_patch_loaded_ref(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:452
static gboolean _create_new_layer(dt_iop_module_t *self, const char *requested_name)
Definition drawlayer.c:2098
void dt_drawlayer_refresh_layer_widgets(dt_iop_module_t *self)
Definition drawlayer.c:1727
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:2862
static void _build_brush_tab(dt_iop_module_t *self, dt_iop_drawlayer_gui_data_t *g, GtkWidget *brush_tab)
Build the Brush tab: mode, colour, geometry, thickness and texture.
Definition drawlayer.c:2925
void change_image(dt_iop_module_t *self)
Invalidate module state when active image changes.
Definition drawlayer.c:3346
static void _build_layer_tab(dt_iop_module_t *self, dt_iop_drawlayer_gui_data_t *g, GtkWidget *layer_tab)
Build the Layer tab: the Background choice, the layer list and its actions.
Definition drawlayer.c:3063
static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
Definition drawlayer.c:1902
static void _refresh_brush_settings_cache(dt_iop_drawlayer_gui_data_t *g)
Re-resolve the brush from conf into the GUI-side cache.
Definition drawlayer.c:206
static void _create_background_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2611
void dt_drawlayer_wait_for_rasterization_modal(const dt_iop_drawlayer_gui_data_t *g, const char *title, const char *message)
Definition drawlayer.c:1293
dt_drawlayer_runtime_context_t drawlayer_runtime_host_context_t
Definition drawlayer.c:125
static gboolean _brush_profile_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition drawlayer.c:1429
gboolean dt_drawlayer_layer_name_non_empty(const char *name)
Definition drawlayer.c:345
static GPrivate _drawlayer_process_scratch_key
Definition drawlayer.c:386
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:648
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:2849
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:3528
static void _fill_black_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2509
dt_drawlayer_runtime_request_t drawlayer_runtime_request_t
Definition drawlayer.c:124
void dt_drawlayer_end_gui_stroke_capture(dt_iop_module_t *self)
Definition drawlayer.c:2697
static void _sync_layer_controls(dt_iop_module_t *self)
Definition drawlayer.c:1738
static void _fill_input_layer_coords(dt_iop_module_t *self, dt_drawlayer_paint_raw_input_t *input)
Definition drawlayer.c:321
static void _fill_input_brush_settings(dt_iop_module_t *self, dt_drawlayer_paint_raw_input_t *input)
Definition drawlayer.c:248
void init_global(dt_iop_module_so_t *module)
Initialize global OpenCL resources for drawlayer kernels.
Definition drawlayer.c:2775
gboolean dt_drawlayer_rekey_shared_base_patch(drawlayer_patch_t *patch, const int32_t imgid, const dt_iop_drawlayer_params_t *params)
Definition drawlayer.c:404
static gboolean _rename_current_layer_from_gui(dt_iop_module_t *self, const char *requested_name)
Definition drawlayer.c:2025
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:3921
#define DRAWLAYER_RESAMPLE_DAMAGE_MARGIN
Definition drawlayer.c:874
static void _compute_hud_brush_state(const dt_control_pointer_input_t *pointer_input, drawlayer_hud_brush_state_t *state)
Definition drawlayer.c:3437
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:1496
static void _sync_mode_sensitive_widgets(dt_iop_module_t *self)
Definition drawlayer.c:1911
static void _fill_white_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2502
static void _ensure_cursor_stamp_surface(dt_iop_module_t *self, const float widget_radius, const float opacity, const float hardness)
Definition drawlayer.c:1178
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:2641
static void _retain_base_patch_stroke_ref(dt_iop_drawlayer_gui_data_t *g)
Definition drawlayer.c:459
static void _delete_layer_clicked(GtkButton *button, gpointer user_data)
Definition drawlayer.c:2421
static void _build_input_tab(dt_iop_module_t *self, dt_iop_drawlayer_gui_data_t *g, GtkWidget *input_tab)
Build the Input tab: the tablet-mapping grid and its profile combos.
Definition drawlayer.c:3146
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
#define DT_GUI_MODULE(x)
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
int bpp
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1909
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:215
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
gboolean dt_mask_scroll_increases(int up)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
int dt_iop_clip_and_zoom_roi_cl(int devid, cl_mem dev_out, cl_mem dev_in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in)
void *const ovoid
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
void dt_interpolation_resample(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
int dt_interpolation_resample_cl(const struct dt_interpolation *itor, const int devid, cl_mem dev_out, const dt_iop_roi_t *const roi_out, cl_mem dev_in, const dt_iop_roi_t *const roi_in)
@ DT_INTERPOLATION_BILINEAR
int32_t dt_drawlayer_io_background_layer_job_run(dt_job_t *job)
Worker entrypoint for async "create background from input" sidecar jobs.
Definition io.c:1015
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:615
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:655
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:644
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:897
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:669
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:927
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.
void dt_drawlayer_conf_remember_display_color(dt_iop_module_t *self, const float display_rgb[3])
Push current display color to history and trigger swatch redraw.
void dt_drawlayer_conf_sync_params_from_gui(dt_iop_module_t *self, const gboolean record_history)
Sync active GUI widget values back into persistent config keys.
float dt_drawlayer_conf_hardness(void)
Derive hardness as complementary value of softness.
drawlayer_pick_source_t dt_drawlayer_conf_pick_source(void)
Read and clamp color picker source selector.
float dt_drawlayer_conf_sprinkle_coarseness(void)
Read and clamp configured sprinkle octave mix (%).
float dt_drawlayer_conf_smoothing(void)
Read and clamp configured smoothing parameter (%).
drawlayer_mapping_profile_t dt_drawlayer_conf_mapping_profile(const char *key)
Read and clamp one mapping-profile enum key.
void dt_drawlayer_conf_ensure_defaults(void)
Ensure all drawlayer GUI config keys exist with sane defaults.
void dt_drawlayer_conf_display_color(float rgb[3])
Read configured display RGB brush color.
float dt_drawlayer_conf_hdr_exposure(void)
Read and clamp HDR picker exposure compensation (EV).
dt_iop_drawlayer_brush_shape_t dt_drawlayer_conf_brush_shape(void)
Read and clamp configured brush shape.
dt_iop_drawlayer_brush_mode_t dt_drawlayer_conf_brush_mode(void)
Read and clamp configured brush blend mode.
float dt_drawlayer_conf_sprinkles(void)
Read and clamp configured sprinkles amount (%).
void dt_drawlayer_conf_load_color_history(dt_iop_drawlayer_gui_data_t *g)
Load persisted color-history stack from config into widgets state.
void dt_drawlayer_conf_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.
float dt_drawlayer_conf_opacity(void)
Read and clamp configured stroke opacity (%).
float dt_drawlayer_conf_size(void)
Read and clamp configured brush size (px).
void dt_drawlayer_conf_sync_color_picker(dt_iop_module_t *self)
Refresh picker widgets from persisted config color.
float dt_drawlayer_conf_flow(void)
Read and clamp configured flow (%).
float dt_drawlayer_conf_distance(void)
Read and clamp configured distance/sampling parameter (%).
void dt_drawlayer_mapping_rows(dt_iop_drawlayer_gui_data_t *g, dt_drawlayer_mapping_row_t rows[DT_DRAWLAYER_MAPPING_ROWS])
float dt_drawlayer_conf_sprinkle_size(void)
Read and clamp configured sprinkle feature size (px).
The brush settings the drawlayer module keeps in the application configuration.
#define DRAWLAYER_CONF_MAP_TILT_SIZE
#define DRAWLAYER_CONF_MAP_PRESSURE_SIZE
#define DRAWLAYER_CONF_ACCEL_PROFILE
#define DRAWLAYER_CONF_MAP_PRESSURE_SOFTNESS
#define DRAWLAYER_CONF_PRESSURE_PROFILE
#define DRAWLAYER_CONF_MAP_TILT_FLOW
#define DRAWLAYER_CONF_MAP_ACCEL_FLOW
#define DRAWLAYER_CONF_MAP_ACCEL_SIZE
#define DRAWLAYER_CONF_MAP_PRESSURE_OPACITY
#define DRAWLAYER_CONF_SIZE
#define DRAWLAYER_CONF_MAP_TILT_SOFTNESS
#define DRAWLAYER_CONF_TILT_PROFILE
#define DRAWLAYER_CONF_MAP_ACCEL_OPACITY
#define DRAWLAYER_CONF_MAP_PRESSURE_FLOW
#define DRAWLAYER_CONF_MAP_TILT_OPACITY
#define DRAWLAYER_CONF_MAP_ACCEL_SOFTNESS
Private drawlayer module types and lightweight shared helpers.
static float _mapping_profile_value(const drawlayer_mapping_profile_t profile, const float x)
@ DRAWLAYER_INPUT_MAP_ACCEL_SIZE
@ DRAWLAYER_INPUT_MAP_ACCEL_FLOW
@ DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_OPACITY
@ DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_SIZE
@ DRAWLAYER_INPUT_MAP_PRESSURE_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS
@ DRAWLAYER_INPUT_MAP_PRESSURE_SIZE
drawlayer_mapping_profile_t
@ DRAWLAYER_PREVIEW_BG_WHITE
@ DRAWLAYER_PREVIEW_BG_IMAGE
@ DRAWLAYER_PREVIEW_BG_BLACK
@ DRAWLAYER_PREVIEW_BG_GREY
drawlayer_pick_source_t
@ DRAWLAYER_PICK_SOURCE_OUTPUT
void dt_drawlayer_paint_runtime_state_reset(dt_drawlayer_damaged_rect_t *state)
Reset stroke-damage accumulator to empty/invalid.
Stroke-level path sampling and runtime-state API for drawlayer.
dt_drawlayer_paint_stroke_pos_t
Position of a raw input event inside a stroke lifecycle.
@ DT_DRAWLAYER_PAINT_STROKE_MIDDLE
@ DT_DRAWLAYER_PAINT_STROKE_FIRST
@ DT_DRAWLAYER_PAINT_STROKE_END
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:138
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:419
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:687
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:115
@ DT_JOB_QUEUE_USER_BG
Definition jobs.h:57
void dt_drawlayer_layers_log_errors(GString *errors)
Definition layers.c:42
void dt_drawlayer_layers_populate_list(dt_iop_module_t *self)
Definition layers.c:48
void dt_drawlayer_layers_reset_stroke_session(dt_iop_drawlayer_gui_data_t *g)
Definition layers.c:94
void dt_drawlayer_layers_append_error(GString *errors, const char *message)
Definition layers.c:35
The drawlayer's layer store: the layer cache and its sidecar synchronisation.
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
hash
Definition layers.h:90
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t int int * layer_height
Definition layers.h:51
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t int * layer_width
Definition layers.h:50
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_INPUT
Definition logging.h:64
@ DT_DEBUG_OPENCL
Definition logging.h:57
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_VERBOSE
Definition logging.h:78
int32_t dt_get_debug_flags(void)
Definition darktable.c:2094
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
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:2975
gboolean dt_opencl_is_pinned_memory(cl_mem mem)
Definition opencl.c:238
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2952
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
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:2737
int dt_opencl_unmap_mem_object(const int devid, cl_mem mem_object, void *mapped_ptr)
Definition opencl.c:2910
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
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:2890
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:2603
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1712
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2823
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:2725
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
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:2692
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const float factor
Definition pdf.h:91
void * dt_dev_pixelpipe_cache_get_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid, int width, int height, int bpp, int flags, gboolean *out_reused)
Acquire a pinned OpenCL image for a host buffer tracked by the pixelpipe cache.
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Take or release one reference on a cache line. A referenced line is never evicted,...
void dt_dev_pixelpipe_cache_release_cl_buffer(void **cl_mem_buffer, dt_pixel_cache_entry_t *cache_entry, void *host_ptr, const gboolean cache_device)
Release or cache an OpenCL image associated with a host cache line.
void dt_dev_pixelpipe_cache_put_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, void **mem)
Release or cache a pinned OpenCL image acquired with dt_dev_pixelpipe_cache_get_pinned_image().
void * dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *const host_ptr, const dt_iop_roi_t *roi, const size_t bpp, dt_iop_module_t *module, const char *message, dt_pixel_cache_entry_t *cache_entry, gboolean *out_reused, void *keep)
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
int dt_dev_pixelpipe_cache_rekey(const uint64_t old_hash, const uint64_t new_hash, dt_pixel_cache_entry_t *entry)
Change the hash/key of an existing cache line in place, without freeing, reallocating or invalidating...
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_ref_entry_for_host_ptr(void *host_ptr)
Resolve and retain the cache entry owning a host pointer.
Pixelpipe cache for storing intermediate results in the pixelpipe.
gboolean dt_dev_pixelpipe_get_realtime(const dt_dev_pixelpipe_t *pipe)
int main()
Definition prova.c:47
void dt_drawlayer_runtime_manager_bind_piece(dt_drawlayer_runtime_manager_t *headless_manager, dt_drawlayer_process_state_t *headless_process, dt_drawlayer_runtime_manager_t *gui_manager, dt_drawlayer_process_state_t *gui_process, const gboolean display_pipe, dt_drawlayer_runtime_manager_t **runtime_manager, dt_drawlayer_process_state_t **runtime_process, gboolean *runtime_display_pipe)
Definition runtime.c:885
void dt_drawlayer_runtime_manager_cleanup(dt_drawlayer_runtime_manager_t *state)
Definition runtime.c:85
void dt_drawlayer_runtime_manager_init(dt_drawlayer_runtime_manager_t *state)
Definition runtime.c:77
dt_drawlayer_runtime_result_t dt_drawlayer_runtime_manager_update(dt_drawlayer_runtime_manager_t *state, const dt_drawlayer_runtime_update_request_t *request, const dt_drawlayer_runtime_host_t *host)
Definition runtime.c:659
void dt_drawlayer_process_state_init(dt_drawlayer_process_state_t *state)
Definition runtime.c:899
void dt_drawlayer_ui_cursor_clear(dt_drawlayer_ui_state_t *state)
Definition runtime.c:958
void dt_drawlayer_process_state_cleanup(dt_drawlayer_process_state_t *state)
Definition runtime.c:907
void dt_drawlayer_runtime_manager_note_buffer_lock(dt_drawlayer_runtime_manager_t *state, const dt_drawlayer_runtime_buffer_t buffer, const dt_drawlayer_runtime_actor_t actor, const gboolean write_lock, const gboolean acquire)
Definition runtime.c:96
void dt_drawlayer_process_state_invalidate(dt_drawlayer_process_state_t *state)
Definition runtime.c:924
Private runtime state/helpers shared by drawlayer module entrypoints.
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_NONE
Definition runtime.h:232
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_STROKE_BEGIN
Definition runtime.h:234
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_STROKE_END
Definition runtime.h:235
@ DT_DRAWLAYER_RUNTIME_RAW_INPUT_SAMPLE
Definition runtime.h:233
#define DT_DRAWLAYER_MAPPING_ROWS
How many input axes the tablet mapping offers (pressure, tilt, acceleration).
Definition runtime.h:392
@ DT_DRAWLAYER_SOURCE_BASE_PATCH
Definition runtime.h:335
@ DT_DRAWLAYER_RUNTIME_ACTOR_PIPELINE_CPU
Definition runtime.h:190
@ DT_DRAWLAYER_RUNTIME_ACTOR_PIPELINE_CL
Definition runtime.h:191
@ DT_DRAWLAYER_RUNTIME_BUFFER_BASE_PATCH
Definition runtime.h:199
dt_drawlayer_runtime_event_t
Definition runtime.h:205
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_FOCUS_GAIN
Definition runtime.h:207
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_RESYNC
Definition runtime.h:214
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CPU_AFTER
Definition runtime.h:219
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_SCROLL
Definition runtime.h:211
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CL_BEFORE
Definition runtime.h:220
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_PIPE_FINISHED
Definition runtime.h:215
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CL_AFTER
Definition runtime.h:221
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_CHANGE_IMAGE
Definition runtime.h:213
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_RAW_INPUT
Definition runtime.h:217
@ DT_DRAWLAYER_RUNTIME_EVENT_PROCESS_CPU_BEFORE
Definition runtime.h:218
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_MOUSE_LEAVE
Definition runtime.h:210
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_STROKE_ABORT
Definition runtime.h:216
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_FOCUS_LOSS
Definition runtime.h:208
@ DT_DRAWLAYER_RUNTIME_EVENT_GUI_SYNC_TEMP_BUFFERS
Definition runtime.h:212
#define DT_DRAWLAYER_MAPPING_TARGETS
How many brush properties each axis can drive (size, opacity, flow, hardness).
Definition runtime.h:394
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ 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:183
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
float * dt_drawlayer_cache_ensure_scratch_buffer(float **buffer, size_t *capacity_pixels, const size_t needed_pixels, const char *name)
Ensure scratch RGBA float capacity in pixels.
void dt_drawlayer_cache_patch_wrunlock(const dt_drawlayer_cache_patch_t *patch)
Release write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_wrlock(const dt_drawlayer_cache_patch_t *patch)
Acquire write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_clear(dt_drawlayer_cache_patch_t *patch, const char *external_alloc_name)
Release patch storage and reset patch metadata.
gboolean dt_drawlayer_cache_patch_alloc_shared(dt_drawlayer_cache_patch_t *patch, const uint64_t hash, const size_t pixel_count, const int width, const int height, const char *name, int *created_out)
Allocate patch storage from shared pixel cache entry.
void dt_drawlayer_cache_free_temp_buffer(void **buffer, const char *name)
Free temporary aligned cache buffer.
void dt_drawlayer_cache_patch_rdlock(const dt_drawlayer_cache_patch_t *patch)
Acquire read lock on shared patch cache entry.
void dt_drawlayer_cache_patch_rdunlock(const dt_drawlayer_cache_patch_t *patch)
Release read lock on shared patch cache entry.
void dt_drawlayer_cache_clear_transparent_float(float *pixels, const size_t pixel_count)
Fill float RGBA buffer with transparent black.
Shared drawlayer runtime helpers used across module/runtime files.
#define DRAWLAYER_NAME_SIZE
dt_drawlayer_runtime_feedback_t
@ DT_DRAWLAYER_RUNTIME_FEEDBACK_NONE
@ DT_DRAWLAYER_RUNTIME_FEEDBACK_FOCUS_LOSS_WAIT
@ DT_DRAWLAYER_RUNTIME_FEEDBACK_SAVE_WAIT
#define DRAWLAYER_PROFILE_SIZE
gboolean dt_drawlayer_ensure_layer_cache(dt_iop_module_t *self)
Definition layers.c:131
const float uint32_t state[4]
const float r
unsigned __int64 uint64_t
Definition strptime.c:75
Everything the GPU layer-over-input blend needs, in one place.
Definition drawlayer.c:927
const dt_iop_roi_t * target_roi
Definition drawlayer.c:938
drawlayer_process_scratch_t * scratch
Definition drawlayer.c:932
const dt_iop_roi_t * source_roi
Definition drawlayer.c:939
dt_drawlayer_process_state_t * process
Definition drawlayer.c:937
dt_pixel_cache_entry_t * source_entry
Definition drawlayer.c:934
What one sidecar directory scan found about a layer.
Definition layers.h:34
char work_profile[DRAWLAYER_PROFILE_SIZE]
Definition layers.h:41
char name[DRAWLAYER_NAME_SIZE]
Definition layers.h:40
uint32_t height
Definition layers.h:39
const dt_iop_drawlayer_gui_data_t * g
Definition drawlayer.c:1277
Objective facts about the image a dev is working on.
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:167
dt_image_t image_storage
Definition develop.h:225
GList * iop
Definition develop.h:269
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
Fully resolved input dab descriptor.
Definition brush.h:66
The brush as the input path needs it, resolved from conf once per change.
Definition runtime.h:93
Generic float RGBA patch stored either in malloc memory or pixel cache.
dt_pixel_cache_entry_t * cache_entry
Integer axis-aligned rectangle in buffer coordinates.
Parameters owned by the async "create background from input" job.
Definition io.h:61
char sidecar_path[DT_PATH_MAX]
Definition io.h:68
Result posted back to the UI after background-layer creation.
Definition io.h:79
Metadata returned when probing one layer directory in sidecar TIFF.
Definition io.h:47
char work_profile[256]
Definition io.h:54
Float RGBA patch used by drawlayer I/O routines.
Definition io.h:37
One input axis: the four properties it can drive, and its response curve.
Definition runtime.h:405
dt_drawlayer_mapping_cell_t targets[4]
Definition runtime.h:406
dt_drawlayer_mapping_cell_t profile
Definition runtime.h:407
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
Where the raw-anchored layer canvas sits in this module's own frame.
Definition coordinates.h:51
dt_drawlayer_runtime_request_t runtime
Definition runtime.h:323
What the runtime manager is given to reach its caller's state.
Definition runtime.h:292
dt_drawlayer_process_state_t * process
Definition runtime.h:355
const dt_iop_drawlayer_params_t * runtime_params
Definition runtime.h:311
dt_iop_module_t * self
Definition runtime.h:308
const dt_iop_roi_t * roi_in
Definition runtime.h:316
const dt_dev_pixelpipe_t * pipe
Definition runtime.h:309
dt_drawlayer_process_state_t * process_state
Definition runtime.h:314
const dt_iop_roi_t * roi_out
Definition runtime.h:317
dt_dev_pixelpipe_iop_t * piece
Definition runtime.h:310
dt_drawlayer_runtime_manager_t * manager
Definition runtime.h:313
dt_drawlayer_runtime_buffer_t tracked_buffer
Definition runtime.h:348
dt_drawlayer_runtime_source_kind_t kind
Definition runtime.h:340
dt_pixel_cache_entry_t * cache_entry
Definition runtime.h:343
dt_drawlayer_runtime_actor_t tracked_actor
Definition runtime.h:349
dt_drawlayer_runtime_event_t event
Definition runtime.h:361
dt_drawlayer_runtime_release_t release
Definition runtime.h:365
int32_t id
Definition image.h:410
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:299
dt_drawlayer_session_state_t session
Definition runtime.h:298
dt_drawlayer_runtime_manager_t manager
Definition runtime.h:301
GtkWidget * reset_button
Definition imageop_gui.h:60
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:272
dt_iop_params_t * default_params
Definition imageop.h:333
dt_aligned_pixel_t picked_output_color_min
Definition imageop.h:289
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_aligned_pixel_t picked_output_color_max
Definition imageop.h:289
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_output_color
Definition imageop.h:289
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
dt_iop_color_intent_t intent
Rendering intent, passed to cmsCreateTransform on the lcms2 fallback only. The matrix path ignores it...
dt_colorspaces_color_profile_type_t type
Profile identity, half of the memo key. DT_COLORSPACE_NONE means "unset" and makes every apply functi...
char filename[DT_IOP_COLOR_ICC_LEN]
ICC file name, the other half of the memo key; "" for every built-in. Compared with strcmp,...
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_widget_freeze()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
gboolean dt_drawlayer_widgets_finish_picker_drag(dt_drawlayer_widgets_t *widgets, float display_rgb[3])
End picker drag mode and optionally output current color.
Definition widgets.c:495
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:510
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:436
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:690
gboolean dt_drawlayer_widgets_is_picker_dragging(const dt_drawlayer_widgets_t *widgets)
Tell whether picker drag interaction is currently active.
Definition widgets.c:504
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:833
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:533
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:656
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:728
Public color-picker/history widget API for drawlayer GUI.
#define DT_DRAWLAYER_COLOR_HISTORY_HEIGHT
Definition widgets.h:32
#define DT_DRAWLAYER_COLOR_PICKER_HEIGHT
Definition widgets.h:28
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:1732
void dt_drawlayer_worker_cleanup(dt_drawlayer_worker_t **worker)
Public worker cleanup entry point.
Definition worker.c:1710
void dt_drawlayer_worker_reset_backend_path(dt_drawlayer_worker_t *worker)
Reset worker-owned backend damage accumulator.
Definition worker.c:1784
gboolean dt_drawlayer_worker_wait_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Wait until worker queue is drained and not busy.
Definition worker.c:1415
void dt_drawlayer_worker_snapshot_params(dt_drawlayer_worker_t *worker, const dt_iop_drawlayer_params_t *params)
Seed the worker's private params blob for the stroke about to start. GUI thread only.
Definition worker.c:1770
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:1702
gboolean dt_drawlayer_worker_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when worker has pending activity.
Definition worker.c:1716
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:389
void dt_drawlayer_worker_request_commit(dt_drawlayer_worker_t *worker)
Public commit request helper.
Definition worker.c:1753
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:1722
void dt_drawlayer_worker_reset_live_publish(dt_drawlayer_worker_t *worker)
Reset worker-owned transient live-publish state.
Definition worker.c:1792
void dt_drawlayer_worker_seal_for_commit(dt_drawlayer_worker_t *worker)
Seal current stroke for synchronous commit.
Definition worker.c:1779
void dt_drawlayer_worker_cancel_async_commit(dt_drawlayer_worker_t *rt)
Cancel deferred commit request state if any.
Definition worker.c:1560
void dt_drawlayer_worker_reset_stroke(dt_drawlayer_worker_t *worker)
Clear preserved stroke runtime/history after commit completed.
Definition worker.c:1801
Background stroke worker API for drawlayer realtime painting.