Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
iop/drawlayer/paint.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/*
20 * drawlayer public paint API implementation
21 *
22 * Stroke-level processing:
23 * raw input events -> smoothed/resampled dab stream.
24 */
25
26#include "iop/drawlayer/paint.h"
27#include "system/simd.h"
28#include "iop/drawlayer/cache.h"
30
31#include "system/macros.h"
32#include "system/mem_alloc.h"
33#include "common/logging.h"
34#include "common/times.h"
35
36#include <math.h>
37#include <string.h>
38
44static inline float _clamp01(const float v)
45{
46 return fminf(fmaxf(v, 0.0f), 1.0f);
47}
48
50static inline float _lerpf(const float a, const float b, const float t)
51{
52 return a + (b - a) * t;
53}
54
56static inline float _paint_voronoi_strip_angle_measure(const float rho, const float strip_ratio)
57{
58 if(strip_ratio <= 0.0f) return 0.0f;
59 if(rho <= strip_ratio + 1e-6f) return 2.0f * (float)G_PI;
60 return 4.0f * asinf(_clamp01(strip_ratio / fmaxf(rho, 1e-6f)));
61}
62
64static inline float _paint_dab_sample_spacing(const dt_drawlayer_brush_dab_t *dab, const float distance_percent)
65{
66 if(IS_NULL_PTR(dab)) return 1.0f;
67 const float radius = fmaxf(0.5f, dab->radius);
68 const float diameter = 2.0f * radius;
69 return _lerpf(1.0f, diameter, _clamp01(distance_percent));
70}
71
73static inline float _paint_segment_sample_spacing(const dt_drawlayer_brush_dab_t *dabs, const int count,
74 const float distance_percent)
75{
76 if(count <= 0) return 1.0f;
77 if(count == 1) return _paint_dab_sample_spacing(&dabs[0], distance_percent);
78 const dt_drawlayer_brush_dab_t *p_start = &dabs[count - 2];
79 const dt_drawlayer_brush_dab_t *p_end = &dabs[count - 1];
80 const float min_radius = fmaxf(0.5f, fminf(p_start->radius, p_end->radius));
81 const dt_drawlayer_brush_dab_t tmp = { .radius = min_radius };
82 return _paint_dab_sample_spacing(&tmp, distance_percent);
83}
84
86static inline float _paint_cubic_hermitef(const float p0, const float p1, const float m0, const float m1, const float t)
87{
88 const float t2 = t * t;
89 const float t3 = t2 * t;
90 return (2.0f * t3 - 3.0f * t2 + 1.0f) * p0 + (t3 - 2.0f * t2 + t) * m0
91 + (-2.0f * t3 + 3.0f * t2) * p1 + (t3 - t2) * m1;
92}
93
99 const int count, const float t)
100{
101 const dt_drawlayer_brush_dab_t *p_prev = (count >= 3) ? &dabs[count - 3] : &dabs[count - 2];
102 const dt_drawlayer_brush_dab_t *p_start = &dabs[count - 2];
103 const dt_drawlayer_brush_dab_t *p_end = &dabs[count - 1];
104
105 const float seg_dx = p_end->x - p_start->x;
106 const float seg_dy = p_end->y - p_start->y;
107 const float seg_len = hypotf(seg_dx, seg_dy);
108 const float dir_x = (seg_len > 1e-6f) ? (seg_dx / seg_len) : p_start->dir_x;
109 const float dir_y = (seg_len > 1e-6f) ? (seg_dy / seg_len) : p_start->dir_y;
110 const float m1x = (count >= 3) ? 0.5f * (p_end->x - p_prev->x) : (p_end->x - p_start->x);
111 const float m1y = (count >= 3) ? 0.5f * (p_end->y - p_prev->y) : (p_end->y - p_start->y);
112 const float m2x = p_end->x - p_start->x;
113 const float m2y = p_end->y - p_start->y;
114 const dt_aligned_pixel_simd_t t4 = dt_simd_set1(t);
115 const dt_aligned_pixel_simd_t color_start = dt_load_simd(p_start->color);
116 const dt_aligned_pixel_simd_t color_end = dt_load_simd(p_end->color);
117
119 .x = _paint_cubic_hermitef(p_start->x, p_end->x, m1x, m2x, t),
120 .y = _paint_cubic_hermitef(p_start->y, p_end->y, m1y, m2y, t),
121 .wx = _lerpf(p_start->wx, p_end->wx, t),
122 .wy = _lerpf(p_start->wy, p_end->wy, t),
123 .radius = fmaxf(0.5f, _lerpf(p_start->radius, p_end->radius, t)),
124 .dir_x = dir_x,
125 .dir_y = dir_y,
126 .opacity = _clamp01(_lerpf(p_start->opacity, p_end->opacity, t)),
127 .flow = _clamp01(_lerpf(p_start->flow, p_end->flow, t)),
128 .sprinkles = _clamp01(_lerpf(p_start->sprinkles, p_end->sprinkles, t)),
129 .sprinkle_size = fmaxf(0.0f, _lerpf(p_start->sprinkle_size, p_end->sprinkle_size, t)),
130 .sprinkle_coarseness = _clamp01(_lerpf(p_start->sprinkle_coarseness, p_end->sprinkle_coarseness, t)),
131 .hardness = _clamp01(_lerpf(p_start->hardness, p_end->hardness, t)),
132 .color = { 0.0f, 0.0f, 0.0f, 0.0f },
133 .display_color = {
134 _lerpf(p_start->display_color[0], p_end->display_color[0], t),
135 _lerpf(p_start->display_color[1], p_end->display_color[1], t),
136 _lerpf(p_start->display_color[2], p_end->display_color[2], t),
137 },
138 .shape = (t < 0.5f) ? p_start->shape : p_end->shape,
139 .mode = (t < 0.5f) ? p_start->mode : p_end->mode,
140 };
141 dt_store_simd(dab.color, color_start + (color_end - color_start) * t4);
142 return dab;
143}
144
150 const float sample_step)
151{
152 /* Estimate the fraction of a dab support area covered by one spacing strip.
153 * This is used by brush flow logic to normalize per-sample opacity at
154 * different sampling distances. */
155 if(IS_NULL_PTR(dab) || !isfinite(sample_step)) return 1.0f;
156 const float support_radius = fmaxf(dab->radius, 0.5f);
157 const float overlap_span = 2.0f * support_radius;
158 if(sample_step <= 1e-6f || overlap_span <= 1e-6f || sample_step >= overlap_span - 1e-6f) return 1.0f;
159
160 const float half_strip = 0.5f * sample_step;
161 if(dab->shape != DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN && _clamp01(dab->hardness) >= 1.0f - 1e-6f)
162 {
163 const float clamped_half_strip = fminf(half_strip, support_radius);
164 const float chord_half = sqrtf(fmaxf(support_radius * support_radius
165 - clamped_half_strip * clamped_half_strip, 0.0f));
166 const float strip_area = sample_step * chord_half
167 + 2.0f * support_radius * support_radius
168 * asinf(_clamp01(clamped_half_strip / support_radius));
169 const float full_area = (float)G_PI * support_radius * support_radius;
170 return _clamp01(strip_area / fmaxf(full_area, 1e-6f));
171 }
172
173 const float strip_ratio = _clamp01(half_strip / support_radius);
174 const float full_mass = 2.0f * (float)G_PI * dt_drawlayer_brush_mass_primitive_eval(dab, 1.0f);
175 if(!isfinite(full_mass) || full_mass <= 1e-6f) return 1.0f;
176
177 const float weight_samples = 32.f;
178 const float dr = 1.0f / weight_samples;
179 float strip_mass = 0.0f;
180 for(int ir = 0; ir < weight_samples; ir++)
181 {
182 const float rho = ((float)ir + 0.5f) * dr;
183 const float profile = dt_drawlayer_brush_profile_eval(dab, rho * rho);
184 if(!isfinite(profile) || profile <= 0.0f) continue;
185 const float angle = _paint_voronoi_strip_angle_measure(rho, strip_ratio);
186 if(!isfinite(angle) || angle <= 0.0f) continue;
187 strip_mass += angle * profile * rho * dr;
188 }
189 const float scale = strip_mass / full_mass;
190 return isfinite(scale) ? _clamp01(scale) : 1.0f;
191}
192
195{
196 /* Lazily allocate queue storage so idle modules keep a tiny footprint. */
197 if(IS_NULL_PTR(state)) return FALSE;
198 if(state->raw_inputs) return TRUE;
199 state->raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
200 return !IS_NULL_PTR(state->raw_inputs);
201}
202
205{
206 if(IS_NULL_PTR(state)) return FALSE;
207 if(state->pending_dabs) return TRUE;
208 state->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
209 return !IS_NULL_PTR(state->pending_dabs);
210}
211
214 const dt_drawlayer_brush_dab_t *segment_start,
215 const dt_drawlayer_brush_dab_t *segment_end,
216 const float t)
217{
219 int count = 2;
220 if(!IS_NULL_PTR(state) && state->have_prev_raw_dab)
221 {
222 window[0] = state->prev_raw_dab;
223 window[1] = *segment_start;
224 window[2] = *segment_end;
225 count = 3;
226 }
227 else
228 {
229 window[0] = *segment_start;
230 window[1] = *segment_end;
231 }
233}
234
240 const dt_drawlayer_brush_dab_t *segment_start,
241 const dt_drawlayer_brush_dab_t *segment_end,
242 float *cumulative, const int segments, float *total_len)
243{
244 /* Build a short arc-length LUT for cubic interpolation, then sample by
245 * distance instead of parameter t to keep spacing uniform at high speed. */
246 if(IS_NULL_PTR(cumulative) || segments <= 0 || IS_NULL_PTR(total_len)) return;
247 cumulative[0] = 0.0f;
248 *total_len = 0.0f;
249 dt_drawlayer_brush_dab_t prev = _sample_raw_segment_cubic_param(state, segment_start, segment_end, 0.0f);
250 for(int i = 1; i <= segments; i++)
251 {
252 const float t = (float)i / (float)segments;
253 const dt_drawlayer_brush_dab_t cur = _sample_raw_segment_cubic_param(state, segment_start, segment_end, t);
254 *total_len += hypotf(cur.x - prev.x, cur.y - prev.y);
255 cumulative[i] = *total_len;
256 prev = cur;
257 }
258}
259
262 const dt_drawlayer_brush_dab_t *segment_start,
263 const dt_drawlayer_brush_dab_t *segment_end,
264 const float target_norm,
265 const float *cumulative,
266 const int segments,
267 const float total_len)
268{
269 /* Invert normalized arc length against the LUT (piecewise linear in t). */
270 if(IS_NULL_PTR(cumulative) || segments <= 0 || total_len <= 1e-6f)
271 return _sample_raw_segment_cubic_param(state, segment_start, segment_end, target_norm);
272
273 const float target_len = _clamp01(target_norm) * total_len;
274 int k = 0;
275 while(k < segments && cumulative[k + 1] < target_len) k++;
276
277 const float l0 = cumulative[k];
278 const float l1 = cumulative[MIN(k + 1, segments)];
279 const float span = fmaxf(l1 - l0, 1e-6f);
280 const float local = _clamp01((target_len - l0) / span);
281 const float t0 = (float)k / (float)segments;
282 const float t1 = (float)MIN(k + 1, segments) / (float)segments;
283 return _sample_raw_segment_cubic_param(state, segment_start, segment_end, _lerpf(t0, t1, local));
284}
285
292 const float sample_spacing,
293 const float smoothing_percent,
294 const dt_drawlayer_paint_layer_to_widget_cb layer_to_widget,
295 void *user_data)
296{
297 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || smoothing_percent <= 0.0f) return;
298 if(!state->history || state->history->len < 3) return;
299 const float real_x = dab->x;
300 const float real_y = dab->y;
301
302 const dt_drawlayer_brush_dab_t *h = (const dt_drawlayer_brush_dab_t *)state->history->data;
303 const int n = (int)state->history->len;
304 const dt_drawlayer_brush_dab_t *p0 = &h[n - 3];
305 const dt_drawlayer_brush_dab_t *p1 = &h[n - 2];
306 const dt_drawlayer_brush_dab_t *p2 = &h[n - 1];
307 const float real_radius = dab->radius;
308 const float real_opacity = dab->opacity;
309 const float real_flow = dab->flow;
310 const float real_hardness = dab->hardness;
311 const float real_sprinkles = dab->sprinkles;
312 const float real_sprinkle_size = dab->sprinkle_size;
313 const float real_sprinkle_coarseness = dab->sprinkle_coarseness;
314 const float real_color[4] = { dab->color[0], dab->color[1], dab->color[2], dab->color[3] };
315 const float real_display_color[3] = { dab->display_color[0], dab->display_color[1], dab->display_color[2] };
316
317 /* Quadratic extrapolation from previous 3 dabs, then enforce exactly one
318 * sample-spacing ahead from p2 so smoothing honors the sampling constraint. */
319 const float qx = 3.0f * p2->x - 3.0f * p1->x + p0->x;
320 const float qy = 3.0f * p2->y - 3.0f * p1->y + p0->y;
321 float dvx = qx - p2->x;
322 float dvy = qy - p2->y;
323 float dlen = hypotf(dvx, dvy);
324 if(dlen <= 1e-6f)
325 {
326 dvx = real_x - p2->x;
327 dvy = real_y - p2->y;
328 dlen = hypotf(dvx, dvy);
329 }
330 const float step = fmaxf(sample_spacing, 1e-6f);
331 const float pred_x = (dlen > 1e-6f) ? (p2->x + dvx * (step / dlen)) : real_x;
332 const float pred_y = (dlen > 1e-6f) ? (p2->y + dvy * (step / dlen)) : real_y;
333 const float blend = 0.5f * _clamp01(smoothing_percent);
334 const float pred_radius = fmaxf(0.5f, 3.0f * p2->radius - 3.0f * p1->radius + p0->radius);
335 const float pred_opacity = _clamp01(3.0f * p2->opacity - 3.0f * p1->opacity + p0->opacity);
336 const float pred_flow = _clamp01(3.0f * p2->flow - 3.0f * p1->flow + p0->flow);
337 const float pred_hardness = _clamp01(3.0f * p2->hardness - 3.0f * p1->hardness + p0->hardness);
338 const float pred_sprinkles = _clamp01(3.0f * p2->sprinkles - 3.0f * p1->sprinkles + p0->sprinkles);
339 const float pred_sprinkle_size = fmaxf(0.0f, 3.0f * p2->sprinkle_size - 3.0f * p1->sprinkle_size + p0->sprinkle_size);
340 const float pred_sprinkle_coarseness
342 dab->x = _lerpf(dab->x, pred_x, blend);
343 dab->y = _lerpf(dab->y, pred_y, blend);
344 dab->radius = _lerpf(real_radius, pred_radius, blend);
345 dab->opacity = _lerpf(real_opacity, pred_opacity, blend);
346 dab->flow = _lerpf(real_flow, pred_flow, blend);
347 dab->hardness = _lerpf(real_hardness, pred_hardness, blend);
348 dab->sprinkles = _lerpf(real_sprinkles, pred_sprinkles, blend);
349 dab->sprinkle_size = _lerpf(real_sprinkle_size, pred_sprinkle_size, blend);
350 dab->sprinkle_coarseness = _lerpf(real_sprinkle_coarseness, pred_sprinkle_coarseness, blend);
351 dab->color[0] = _lerpf(real_color[0], 3.0f * p2->color[0] - 3.0f * p1->color[0] + p0->color[0], blend);
352 dab->color[1] = _lerpf(real_color[1], 3.0f * p2->color[1] - 3.0f * p1->color[1] + p0->color[1], blend);
353 dab->color[2] = _lerpf(real_color[2], 3.0f * p2->color[2] - 3.0f * p1->color[2] + p0->color[2], blend);
354 dab->color[3] = _lerpf(real_color[3], 3.0f * p2->color[3] - 3.0f * p1->color[3] + p0->color[3], blend);
355 dab->display_color[0]
356 = _lerpf(real_display_color[0], 3.0f * p2->display_color[0] - 3.0f * p1->display_color[0] + p0->display_color[0], blend);
357 dab->display_color[1]
358 = _lerpf(real_display_color[1], 3.0f * p2->display_color[1] - 3.0f * p1->display_color[1] + p0->display_color[1], blend);
359 dab->display_color[2]
360 = _lerpf(real_display_color[2], 3.0f * p2->display_color[2] - 3.0f * p1->display_color[2] + p0->display_color[2], blend);
361
362 const dt_drawlayer_brush_dab_t *prev = &h[n - 1];
363 const float rvx = real_x - prev->x;
364 const float rvy = real_y - prev->y;
365 const float svx = dab->x - prev->x;
366 const float svy = dab->y - prev->y;
367 const float real_dist = hypotf(rvx, rvy);
368 const float smooth_dist = hypotf(svx, svy);
369 const float min_safe = 0.5f * fmaxf(sample_spacing, 1e-6f);
370 const float dot = rvx * svx + rvy * svy;
371 if((smooth_dist < min_safe && real_dist > smooth_dist) || dot <= 0.0f)
372 {
373 dab->x = real_x;
374 dab->y = real_y;
375 }
376
377 if(layer_to_widget) layer_to_widget(user_data, dab->x, dab->y, &dab->wx, &dab->wy);
378}
379
382{
383 /* Emit one dab and keep a full emitted history for smoothing/prediction. */
384 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || IS_NULL_PTR(state->pending_dabs)) return;
385 if(state->history && state->history->len > 0)
386 {
387 const dt_drawlayer_brush_dab_t *prev
388 = &g_array_index(state->history, dt_drawlayer_brush_dab_t, state->history->len - 1);
389 const float dx = dab->x - prev->x;
390 const float dy = dab->y - prev->y;
391 const float len = hypotf(dx, dy);
392 if(len > 1e-6f)
393 {
394 dab->dir_x = dx / len;
395 dab->dir_y = dy / len;
396 }
397 }
398 g_array_append_val(state->history, *dab);
399 g_array_append_val(state->pending_dabs, *dab);
400}
401
403static inline void _freeze_emitted_dab_raster_state(dt_drawlayer_brush_dab_t *dab, const float sample_spacing)
404{
405 if(IS_NULL_PTR(dab)) return;
406 dab->sample_spacing = fmaxf(sample_spacing, 1e-6f);
408}
409
413 const float sample_spacing,
414 const dt_drawlayer_paint_layer_to_widget_cb layer_to_widget,
415 void *user_data)
416{
417 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || state->history->len == 0) return;
418 const dt_drawlayer_brush_dab_t *prev
419 = &g_array_index(state->history, dt_drawlayer_brush_dab_t, state->history->len - 1);
420
421 const float target = fmaxf(sample_spacing, 1e-6f);
422 float dx = dab->x - prev->x;
423 float dy = dab->y - prev->y;
424 float d = hypotf(dx, dy);
425 if(!(d > 1e-6f))
426 {
427 float dir_x = dab->dir_x;
428 float dir_y = dab->dir_y;
429 float dir_len = hypotf(dir_x, dir_y);
430 if(dir_len <= 1e-6f)
431 {
432 dir_x = prev->dir_x;
433 dir_y = prev->dir_y;
434 dir_len = hypotf(dir_x, dir_y);
435 }
436 if(dir_len <= 1e-6f)
437 {
438 dir_x = 1.0f;
439 dir_y = 0.0f;
440 dir_len = 1.0f;
441 }
442 dx = dir_x / dir_len;
443 dy = dir_y / dir_len;
444 }
445 else
446 {
447 dx /= d;
448 dy /= d;
449 }
450
451 dab->x = prev->x + dx * target;
452 dab->y = prev->y + dy * target;
453 dab->dir_x = dx;
454 dab->dir_y = dy;
455 if(layer_to_widget) layer_to_widget(user_data, dab->x, dab->y, &dab->wx, &dab->wy);
456}
457
460{
461 /* Reset only per-stroke path generation state.
462 * Queue storage and reusable allocations are kept by the owner. */
463 if(IS_NULL_PTR(state)) return;
464 if(state->history) g_array_set_size(state->history, 0);
465 if(state->pending_dabs) g_array_set_size(state->pending_dabs, 0);
466 if(state->dab_window) g_array_set_size(state->dab_window, 0);
467 state->last_input_dab = (dt_drawlayer_brush_dab_t){ 0 };
468 state->have_last_input_dab = FALSE;
469 state->prev_raw_dab = (dt_drawlayer_brush_dab_t){ 0 };
470 state->have_prev_raw_dab = FALSE;
471 state->stroke_arc_length = 0.0f;
472 state->sampled_arc_length = 0.0f;
473 state->distance_percent = 0.0f;
474 state->stroke_seed = 0u;
475}
476
479{
480 /* Full stroke reset: pending raw events + generated path state. */
481 if(IS_NULL_PTR(state)) return;
482 if(state->raw_inputs) g_array_set_size(state->raw_inputs, 0);
483 state->raw_input_cursor = 0;
485}
486
490{
491 if(IS_NULL_PTR(state) || IS_NULL_PTR(input)) return FALSE;
493 || (state->have_last_input_dab && input->stroke_batch != 0u
494 && state->last_input_dab.stroke_batch != 0u
495 && input->stroke_batch != state->last_input_dab.stroke_batch);
496}
497
500{
501 if(IS_NULL_PTR(input)) return 0u;
502 const uint64_t qx = (uint64_t)(int64_t)llrintf((double)input->wx * 256.0);
503 const uint64_t qy = (uint64_t)(int64_t)llrintf((double)input->wy * 256.0);
504 return ((uint64_t)input->stroke_batch << 32)
505 ^ (uint64_t)input->event_ts
506 ^ (qx * 0x9e3779b185ebca87ull)
507 ^ (qy * 0xc2b2ae3d27d4eb4full);
508}
509
512 const dt_drawlayer_brush_dab_t *dab)
513{
514 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || !_ensure_pending_dabs(state)) return;
515 state->last_input_dab = *dab;
516 state->have_last_input_dab = TRUE;
517
518 dt_drawlayer_brush_dab_t first = *dab;
521 _emit_dab(state, &first);
522 state->sampled_arc_length = 0.0f;
523}
524
530{
531 if(IS_NULL_PTR(state) || !state->have_last_input_dab || !state->history || !_ensure_pending_dabs(state)) return;
532 if(!state->history || state->history->len > 0) return;
533
534 dt_drawlayer_brush_dab_t dab = state->last_input_dab;
537 _emit_dab(state, &dab);
538 state->sampled_arc_length = 0.0f;
539}
540
543 const dt_drawlayer_brush_dab_t *dab)
544{
545 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || state->history->len != 0) return;
546
547 const float dx = dab->x - state->last_input_dab.x;
548 const float dy = dab->y - state->last_input_dab.y;
549 const float dir_len = hypotf(dx, dy);
550 if(dir_len > 1e-6f)
551 {
552 state->last_input_dab.dir_x = dx / dir_len;
553 state->last_input_dab.dir_y = dy / dir_len;
554 }
555
557}
558
565 const dt_drawlayer_paint_callbacks_t *callbacks,
566 void *user_data)
567{
568 /* Raw event -> one segment update:
569 * - build input dab from callbacks,
570 * - update cumulative arc length,
571 * - emit uniformly spaced samples using arc-length interpolation,
572 * - apply optional smoothing and spacing enforcement. */
573 if(IS_NULL_PTR(state) || IS_NULL_PTR(input) || IS_NULL_PTR(callbacks) || IS_NULL_PTR(callbacks->build_dab) || !_ensure_pending_dabs(state)) return;
574
575 const float distance_percent = _clamp01(input->distance_percent);
576 state->distance_percent = distance_percent;
577 const float smoothing_percent = _clamp01(input->smoothing_percent);
578
580 {
582 state->stroke_seed = _paint_make_stroke_seed(input);
583 if(callbacks->on_stroke_seed) callbacks->on_stroke_seed(user_data, state->stroke_seed);
584 }
585
586 dt_drawlayer_brush_dab_t dab = { 0 };
587 if(!callbacks->build_dab(user_data, state, input, &dab)) return;
588
589 if(!state->have_last_input_dab)
590 {
592 return;
593 }
594
595 const dt_drawlayer_brush_dab_t segment_start = state->last_input_dab;
596 const float seg_len = hypotf(dab.x - segment_start.x, dab.y - segment_start.y);
597 const float prev_arc = state->stroke_arc_length;
598 enum { arc_lut_segments = 24 };
599 float arc_lut[arc_lut_segments + 1] = { 0.0f };
600 float arc_total = 0.0f;
601 _build_raw_segment_cubic_arclen_lut(state, &segment_start, &dab,
602 arc_lut, arc_lut_segments, &arc_total);
603 const float seg_arc = (arc_total > 1e-6f) ? arc_total : seg_len;
604 state->stroke_arc_length += seg_arc;
606
607 if(seg_arc > 1e-6f)
608 {
609 const dt_drawlayer_brush_dab_t spacing_ref_segment[2] = { segment_start, dab };
610 const float segment_spacing = _paint_segment_sample_spacing(spacing_ref_segment, 2, distance_percent);
611 while(TRUE)
612 {
613 const float sample_spacing = segment_spacing;
614 const float target_arc = state->sampled_arc_length + sample_spacing;
615 if(target_arc > state->stroke_arc_length + 1e-6f) break;
616
617 if(target_arc <= prev_arc + 1e-6f)
618 {
619 state->sampled_arc_length = target_arc;
620 continue;
621 }
622
623 const float t = _clamp01((target_arc - prev_arc) / seg_arc);
625 = _sample_raw_segment_cubic_arclen(state, &segment_start, &dab, t,
626 arc_lut, arc_lut_segments, arc_total);
627 sample.stroke_batch = input->stroke_batch;
629 _apply_quadratic_dab_smoothing(state, &sample, sample_spacing, smoothing_percent,
630 callbacks->layer_to_widget, user_data);
631 _enforce_dab_center_spacing(state, &sample, sample_spacing,
632 callbacks->layer_to_widget, user_data);
633 _freeze_emitted_dab_raster_state(&sample, sample_spacing);
634 _emit_dab(state, &sample);
635 state->sampled_arc_length = target_arc;
636 }
637 }
638
639 state->prev_raw_dab = segment_start;
640 state->have_prev_raw_dab = TRUE;
641 state->last_input_dab = dab;
642}
643
646{
647 if(IS_NULL_PTR(state) || IS_NULL_PTR(input) || !_ensure_raw_inputs(state)) return FALSE;
648 g_array_append_val(state->raw_inputs, *input);
649 return TRUE;
650}
651
653{
654 /* FIFO compaction after processing to avoid unbounded queue growth. */
655 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->raw_inputs)) return;
656 if(state->raw_input_cursor == 0) return;
657
658 const guint processed = state->raw_input_cursor;
659 const guint len = state->raw_inputs->len;
660 if(processed >= len)
661 g_array_set_size(state->raw_inputs, 0);
662 else
663 g_array_remove_range(state->raw_inputs, 0, processed);
664
665 state->raw_input_cursor = 0;
666}
667
669 const dt_drawlayer_paint_callbacks_t *callbacks,
670 void *user_data)
671{
672 /* Drain all queued raw inputs in FIFO order. No coalescing here. */
673 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->raw_inputs) || IS_NULL_PTR(callbacks)) return;
674
675 while(state->raw_input_cursor < state->raw_inputs->len)
676 {
678 = &g_array_index(state->raw_inputs, dt_drawlayer_paint_raw_input_t, state->raw_input_cursor);
679 _paint_process_one_raw_input(state, input, callbacks, user_data);
680 state->raw_input_cursor++;
681 }
682
684}
685
687 const dt_drawlayer_brush_dab_t *current,
688 const dt_drawlayer_brush_dab_t *previous)
689{
690 /* Smudge pickup follows stroke motion with a damped response. */
691 if(IS_NULL_PTR(state) || IS_NULL_PTR(current)) return;
692
694 float pickup_x = 0.0f;
695 float pickup_y = 0.0f;
697
698 if(!have_pickup)
699 {
701 return;
702 }
703
704 const float dx = previous ? (current->x - previous->x) : 0.0f;
705 const float dy = previous ? (current->y - previous->y) : 0.0f;
706 const float travel = hypotf(dx, dy);
707 if(travel <= 1e-6f) return;
708
709 const float radius = fmaxf(current->radius, 0.5f);
710 const float response = 1.0f - expf(-0.5f * travel / radius);
711 pickup_x = _lerpf(pickup_x, current->x, response);
712 pickup_y = _lerpf(pickup_y, current->y, response);
714}
715
717 const float distance_percent,
718 const dt_drawlayer_cache_patch_t *sample_patch,
720 const float scale,
721 dt_drawlayer_cache_patch_t *stroke_mask,
722 dt_drawlayer_damaged_rect_t *runtime_state,
723 dt_drawlayer_paint_stroke_t *runtime_private)
724{
725 /* Backend helper for raster path replay:
726 * keep a tiny dab window, compute spacing normalization, rasterize one sample. */
727 if(IS_NULL_PTR(dab) || !runtime_private || !runtime_private->dab_window) return FALSE;
728
729 GArray *const history = runtime_private->dab_window;
730 if(dab->stroke_pos == DT_DRAWLAYER_PAINT_STROKE_FIRST) g_array_set_size(history, 0);
731 g_array_append_val(history, *dab);
732
733 const int total = (int)history->len;
734 const int count = MIN(total, 3);
736 memcpy(window, ((dt_drawlayer_brush_dab_t *)history->data) + (total - count),
737 (size_t)count * sizeof(dt_drawlayer_brush_dab_t));
738
739 const dt_drawlayer_brush_dab_t *sample = &window[count - 1];
740 const dt_drawlayer_brush_dab_t *previous_sample = (count > 1) ? &window[count - 2] : NULL;
741 const float spacing = (sample->sample_spacing > 1e-6f)
742 ? sample->sample_spacing
743 : _paint_dab_sample_spacing(sample, _clamp01(distance_percent));
744 const float sample_opacity_scale = (sample->sample_opacity_scale > 1e-6f)
745 ? sample->sample_opacity_scale
746 : _paint_stroke_sample_opacity_scale(sample, spacing);
747
748 if(count == 1 && runtime_private)
749 dt_drawlayer_paint_runtime_set_smudge_pickup(runtime_private, 0.0f, 0.0f, FALSE);
750
751 if(!IS_NULL_PTR(runtime_private))
752 {
754 {
755 if(!IS_NULL_PTR(previous_sample)) _advance_smudge_pickup_state(runtime_private, sample, previous_sample);
756 }
757 else
758 dt_drawlayer_paint_runtime_set_smudge_pickup(runtime_private, 0.0f, 0.0f, FALSE);
759 }
760
761 const double t0 = dt_get_wtime();
762 const gboolean rasterized
763 = dt_drawlayer_brush_rasterize(sample_patch, patch, scale, sample, sample_opacity_scale, stroke_mask,
764 runtime_private);
765 const double t1 = dt_get_wtime();
766 if(rasterized && !IS_NULL_PTR(runtime_state) && !IS_NULL_PTR(runtime_private) && runtime_private->bounds.valid)
767 dt_drawlayer_paint_runtime_note_dab_damage(runtime_state, &runtime_private->bounds);
768
770 {
771 if(!IS_NULL_PTR(runtime_private) && runtime_private->bounds.valid)
772 {
773 const int bounds_w = runtime_private->bounds.se[0] - runtime_private->bounds.nw[0];
774 const int bounds_h = runtime_private->bounds.se[1] - runtime_private->bounds.nw[1];
776 "[drawlayer] paint raster mode=%d pos=%d spacing=%.3f alpha_scale=%.4f area=%dx%d ms=%.3f\n",
777 sample->mode, sample->stroke_pos, spacing, sample_opacity_scale, bounds_w, bounds_h,
778 1000.0 * (t1 - t0));
779 }
780 else
782 "[drawlayer] paint raster mode=%d pos=%d spacing=%.3f alpha_scale=%.4f area=0x0 ms=%.3f\n",
783 sample->mode, sample->stroke_pos, spacing, sample_opacity_scale, 1000.0 * (t1 - t0));
784 }
785
786 if(history->len > 3) g_array_remove_range(history, 0, history->len - 3);
787 return TRUE;
788}
789
797
803
805{
806 if(IS_NULL_PTR(state)) return;
807 state->valid = FALSE;
808 state->nw[0] = 0;
809 state->nw[1] = 0;
810 state->se[0] = 0;
811 state->se[1] = 0;
812}
813
821
823{
824 if(IS_NULL_PTR(state) || !*state) return;
825 if((*state)->raw_inputs) g_array_free((*state)->raw_inputs, TRUE);
826 dt_free((*state)->smudge_pixels);
827 dt_free(*state);
828}
829
831{
832 /* Reset per-stroke transient payload while preserving reusable allocations. */
833 if(IS_NULL_PTR(state)) return;
834 state->smudge_pickup_x = 0.0f;
835 state->smudge_pickup_y = 0.0f;
836 state->have_smudge_pickup = FALSE;
837 if(state->smudge_pixels && state->smudge_width > 0 && state->smudge_height > 0)
838 memset(state->smudge_pixels, 0, (size_t)state->smudge_width * state->smudge_height * 4 * sizeof(float));
839}
840
842{
843 if(IS_NULL_PTR(state)) return;
844 state->stroke_seed = seed;
845}
846
851
853 const int height)
854{
855 if(IS_NULL_PTR(state) || width <= 0 || height <= 0) return FALSE;
856 if(state->smudge_width == width && state->smudge_height == height && state->smudge_pixels) return TRUE;
857
858 float *pixels = g_realloc(state->smudge_pixels, (size_t)width * height * 4 * sizeof(float));
859 if(IS_NULL_PTR(pixels)) return FALSE;
860 state->smudge_pixels = pixels;
861 state->smudge_width = width;
862 state->smudge_height = height;
863 memset(state->smudge_pixels, 0, (size_t)width * height * 4 * sizeof(float));
864 return TRUE;
865}
866
868{
869 return state ? state->smudge_pixels : NULL;
870}
871
873{
874 return state ? state->smudge_width : 0;
875}
876
878{
879 return state ? state->smudge_height : 0;
880}
881
883{
884 return state && state->have_smudge_pickup;
885}
886
888 float *x, float *y)
889{
890 if(!IS_NULL_PTR(x)) *x = state ? state->smudge_pickup_x : 0.0f;
891 if(!IS_NULL_PTR(y)) *y = state ? state->smudge_pickup_y : 0.0f;
892}
893
895 const float x, const float y, const gboolean have_pickup)
896{
897 if(IS_NULL_PTR(state)) return;
898 state->smudge_pickup_x = x;
899 state->smudge_pickup_y = y;
900 state->have_smudge_pickup = have_pickup;
901}
902
904 const dt_drawlayer_brush_dab_t *dab,
905 const int width, const int height,
906 const int origin_x, const int origin_y,
907 const float scale)
908{
909 /* Compute current dab footprint in target buffer coordinates. */
910 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || dab->radius <= 0.0f || dab->opacity <= 0.0f || scale <= 0.0f) return FALSE;
911 const float support_radius = dab->radius;
912
913 state->bounds.valid = TRUE;
914 state->bounds.nw[0] = MAX(0, (int)floorf((dab->x - support_radius) * scale) - origin_x);
915 state->bounds.nw[1] = MAX(0, (int)floorf((dab->y - support_radius) * scale) - origin_y);
916 state->bounds.se[0] = MIN(width, (int)ceilf((dab->x + support_radius) * scale) - origin_x + 1);
917 state->bounds.se[1] = MIN(height, (int)ceilf((dab->y + support_radius) * scale) - origin_y + 1);
918 if(state->bounds.se[0] <= state->bounds.nw[0]
919 || state->bounds.se[1] <= state->bounds.nw[1])
920 return FALSE;
921 return TRUE;
922}
923
925 const dt_drawlayer_damaged_rect_t *dab_rect)
926{
927 if(IS_NULL_PTR(state)) return;
928 if(IS_NULL_PTR(dab_rect) || !dab_rect->valid) return;
929 if(dab_rect->se[0] <= dab_rect->nw[0] || dab_rect->se[1] <= dab_rect->nw[1]) return;
930 if(!state->valid)
931 {
932 state->valid = TRUE;
933 *state = *dab_rect;
934 return;
935 }
936 state->nw[0] = MIN(state->nw[0], dab_rect->nw[0]);
937 state->nw[1] = MIN(state->nw[1], dab_rect->nw[1]);
938 state->se[0] = MAX(state->se[0], dab_rect->se[0]);
939 state->se[1] = MAX(state->se[1], dab_rect->se[1]);
940}
941
944{
945 if(IS_NULL_PTR(state) || !state->valid) return FALSE;
946 if(!IS_NULL_PTR(out_rect)) *out_rect = *state;
947 return TRUE;
948}
949
951 const dt_drawlayer_damaged_rect_t *add_rect)
952{
953 if(IS_NULL_PTR(rect)) return;
954 if(IS_NULL_PTR(add_rect) || !add_rect->valid) return;
955 if(add_rect->se[0] <= add_rect->nw[0] || add_rect->se[1] <= add_rect->nw[1]) return;
956
957 if(!rect->valid)
958 {
959 *rect = *add_rect;
960 return;
961 }
962
963 rect->nw[0] = MIN(rect->nw[0], add_rect->nw[0]);
964 rect->nw[1] = MIN(rect->nw[1], add_rect->nw[1]);
965 rect->se[0] = MAX(rect->se[0], add_rect->se[0]);
966 rect->se[1] = MAX(rect->se[1], add_rect->se[1]);
967}
968
970 dt_drawlayer_damaged_rect_t *target_rect)
971{
972 if(IS_NULL_PTR(path_state) || IS_NULL_PTR(target_rect)) return FALSE;
973
974 dt_drawlayer_damaged_rect_t add = { 0 };
975 const gboolean have_damage = dt_drawlayer_paint_runtime_get_stroke_damage(path_state, &add);
977 if(!have_damage) return FALSE;
978
979 _paint_union_damage_rect(target_rect, &add);
980 return TRUE;
981}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
Definition brush.h:52
@ DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN
Definition brush.h:41
Inline brush profile and mass primitives shared by paint/brush code.
static float dt_drawlayer_brush_profile_eval(const dt_drawlayer_brush_dab_t *dab, const float norm2)
Evaluate normalized brush profile at squared normalized radius.
static float dt_drawlayer_brush_mass_primitive_eval(const dt_drawlayer_brush_dab_t *dab, const float u_in)
Evaluate radial mass primitive from center to normalized radius u_in.
return vector dt_simd_set1(valid ?(scaling+NORM_MIN) :NORM_MIN)
static const float x
const int t
const float l1
const float v
GtkWidget * window
gboolean dt_drawlayer_brush_rasterize(const dt_drawlayer_cache_patch_t *sample_patch, dt_drawlayer_cache_patch_t *patch, const float scale, const dt_drawlayer_brush_dab_t *dab, const float sample_opacity_scale, dt_drawlayer_cache_patch_t *stroke_mask, dt_drawlayer_paint_stroke_t *runtime_private)
Public dab rasterization entry point.
Patch/cache helpers for drawlayer process and preview buffers.
void dt_drawlayer_paint_path_state_reset(dt_drawlayer_paint_stroke_t *state)
Reset full stroke state including queued raw input events.
void dt_drawlayer_paint_runtime_state_destroy(dt_drawlayer_damaged_rect_t **state)
Destroy stroke-damage accumulator state and null pointer.
static float _paint_cubic_hermitef(const float p0, const float p1, const float m0, const float m1, const float t)
Cubic Hermite scalar interpolation helper.
static void _paint_process_one_raw_input(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input, const dt_drawlayer_paint_callbacks_t *callbacks, void *user_data)
Process one raw input event into zero or more emitted dabs.
gboolean dt_drawlayer_paint_queue_raw_input(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input)
Queue one raw input event (FIFO).
void dt_drawlayer_paint_runtime_private_destroy(dt_drawlayer_paint_stroke_t **state)
Destroy stroke runtime payload and null pointer.
static dt_drawlayer_brush_dab_t _sample_raw_segment_cubic_arclen(const dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *segment_start, const dt_drawlayer_brush_dab_t *segment_end, const float target_norm, const float *cumulative, const int segments, const float total_len)
Sample a cubic segment at normalized arc length using LUT inversion.
static void _apply_quadratic_dab_smoothing(dt_drawlayer_paint_stroke_t *state, dt_drawlayer_brush_dab_t *dab, const float sample_spacing, const float smoothing_percent, const dt_drawlayer_paint_layer_to_widget_cb layer_to_widget, void *user_data)
Apply optional quadratic smoothing to one emitted dab.
static float _paint_segment_sample_spacing(const dt_drawlayer_brush_dab_t *dabs, const int count, const float distance_percent)
Resolve one segment spacing target from edge dab radii.
uint64_t dt_drawlayer_paint_runtime_get_stroke_seed(const dt_drawlayer_paint_stroke_t *state)
Get current deterministic stroke seed.
static void _enforce_dab_center_spacing(dt_drawlayer_paint_stroke_t *state, dt_drawlayer_brush_dab_t *dab, const float sample_spacing, const dt_drawlayer_paint_layer_to_widget_cb layer_to_widget, void *user_data)
Re-project current dab center to exact target spacing from previous dab.
void dt_drawlayer_paint_interpolate_path(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_callbacks_t *callbacks, void *user_data)
Drain queued raw input events and append evenly spaced dabs to state->pending_dabs.
static void _flush_pending_initial_if_needed(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *dab)
Flush deferred initial dab before regular segment emission starts.
static void _paint_union_damage_rect(dt_drawlayer_damaged_rect_t *rect, const dt_drawlayer_damaged_rect_t *add_rect)
static gboolean _paint_input_starts_new_stroke(const dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input)
Test if current input denotes a new stroke boundary.
void dt_drawlayer_paint_runtime_set_stroke_seed(dt_drawlayer_paint_stroke_t *state, const uint64_t seed)
Set deterministic stroke seed for noise-derived effects.
float * dt_drawlayer_paint_runtime_smudge_pixels(dt_drawlayer_paint_stroke_t *state)
Get smudge carry buffer pointer (RGBA float).
void dt_drawlayer_paint_finalize_path(dt_drawlayer_paint_stroke_t *state)
Finalize stroke by force-emitting the pending first sample if needed.
static void _paint_reset_path_runtime_state(dt_drawlayer_paint_stroke_t *state)
Reset only path-generation state while keeping reusable allocations.
static float _clamp01(const float v)
Clamp scalar value to [0, 1].
static void _build_raw_segment_cubic_arclen_lut(const dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *segment_start, const dt_drawlayer_brush_dab_t *segment_end, float *cumulative, const int segments, float *total_len)
Build arc-length lookup for the current cubic segment.
int dt_drawlayer_paint_runtime_smudge_height(const dt_drawlayer_paint_stroke_t *state)
Get smudge carry buffer height.
gboolean dt_drawlayer_paint_runtime_have_smudge_pickup(const dt_drawlayer_paint_stroke_t *state)
Query whether smudge pickup coordinates are initialized.
void dt_drawlayer_paint_runtime_private_reset(dt_drawlayer_paint_stroke_t *state)
Reset transient stroke runtime payload between strokes.
void dt_drawlayer_paint_runtime_set_smudge_pickup(dt_drawlayer_paint_stroke_t *state, const float x, const float y, const gboolean have_pickup)
Write smudge pickup coordinates and validity flag.
static gboolean _ensure_raw_inputs(dt_drawlayer_paint_stroke_t *state)
Lazily allocate raw-input queue storage for one stroke state.
static void _paint_compact_raw_input_queue(dt_drawlayer_paint_stroke_t *state)
void dt_drawlayer_paint_runtime_get_smudge_pickup(const dt_drawlayer_paint_stroke_t *state, float *x, float *y)
Read smudge pickup coordinates.
static void _emit_dab(dt_drawlayer_paint_stroke_t *state, dt_drawlayer_brush_dab_t *dab)
Emit one dab and append it to emitted-history tracking.
static float _lerpf(const float a, const float b, const float t)
Linear interpolation helper.
int dt_drawlayer_paint_runtime_smudge_width(const dt_drawlayer_paint_stroke_t *state)
Get smudge carry buffer width.
gboolean dt_drawlayer_paint_runtime_get_stroke_damage(const dt_drawlayer_damaged_rect_t *state, dt_drawlayer_damaged_rect_t *out_rect)
Read accumulated stroke damage rectangle.
static void _advance_smudge_pickup_state(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *current, const dt_drawlayer_brush_dab_t *previous)
gboolean dt_drawlayer_paint_rasterize_segment_to_buffer(const dt_drawlayer_brush_dab_t *dab, const float distance_percent, const dt_drawlayer_cache_patch_t *sample_patch, dt_drawlayer_cache_patch_t *patch, const float scale, dt_drawlayer_cache_patch_t *stroke_mask, dt_drawlayer_damaged_rect_t *runtime_state, dt_drawlayer_paint_stroke_t *runtime_private)
Replay one emitted dab segment into a float buffer through brush API.
static float _paint_stroke_sample_opacity_scale(const dt_drawlayer_brush_dab_t *dab, const float sample_step)
Compute per-sample opacity normalization from spacing.
static dt_drawlayer_brush_dab_t _paint_build_segment_window_sample(const dt_drawlayer_brush_dab_t *dabs, const int count, const float t)
Build one interpolated dab sample in the current segment window.
static uint64_t _paint_make_stroke_seed(const dt_drawlayer_paint_raw_input_t *input)
Build deterministic stroke seed from batch/time/coordinates.
void dt_drawlayer_paint_runtime_state_reset(dt_drawlayer_damaged_rect_t *state)
Reset stroke-damage accumulator to empty/invalid.
static float _paint_dab_sample_spacing(const dt_drawlayer_brush_dab_t *dab, const float distance_percent)
Resolve dab-to-dab center spacing from radius and distance percentage.
static void _emit_first_sample_if_needed(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *dab)
Optionally emit first sample immediately when stroke starts.
gboolean dt_drawlayer_paint_runtime_prepare_dab_context(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *dab, const int width, const int height, const int origin_x, const int origin_y, const float scale)
Compute current dab footprint bounds in target buffer coordinates.
static gboolean _ensure_pending_dabs(dt_drawlayer_paint_stroke_t *state)
Lazily allocate pending-dab batch storage for one stroke state.
void dt_drawlayer_paint_runtime_note_dab_damage(dt_drawlayer_damaged_rect_t *state, const dt_drawlayer_damaged_rect_t *dab_rect)
Merge one dab rectangle into an accumulator rectangle.
static void _freeze_emitted_dab_raster_state(dt_drawlayer_brush_dab_t *dab, const float sample_spacing)
Freeze raster-time normalization into one emitted dab record.
dt_drawlayer_damaged_rect_t * dt_drawlayer_paint_runtime_state_create(void)
Allocate zero-initialized stroke-damage accumulator state.
static float _paint_voronoi_strip_angle_measure(const float rho, const float strip_ratio)
Compute angular measure used by strip-based profile integration.
gboolean dt_drawlayer_paint_runtime_ensure_smudge_pixels(dt_drawlayer_paint_stroke_t *state, const int width, const int height)
Ensure smudge carry buffer allocation for given footprint dimensions.
static dt_drawlayer_brush_dab_t _sample_raw_segment_cubic_param(const dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_brush_dab_t *segment_start, const dt_drawlayer_brush_dab_t *segment_end, const float t)
Sample the current raw segment at parametric position t.
gboolean dt_drawlayer_paint_merge_runtime_stroke_damage(dt_drawlayer_damaged_rect_t *path_state, dt_drawlayer_damaged_rect_t *target_rect)
Merge path-state damage into target rectangle and clear path-state accumulator.
dt_drawlayer_paint_stroke_t * dt_drawlayer_paint_runtime_private_create(void)
Allocate stroke runtime payload object used by paint+brush internals.
Stroke-level path sampling and runtime-state API for drawlayer.
@ DT_DRAWLAYER_PAINT_STROKE_MIDDLE
@ DT_DRAWLAYER_PAINT_STROKE_FIRST
gboolean(* dt_drawlayer_paint_layer_to_widget_cb)(void *user_data, float lx, float ly, float *wx, float *wy)
Convert layer-space coordinates back to widget-space (for HUD/preview alignment).
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_VERBOSE
Definition logging.h:78
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
dt_store_simd(out, value)
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
Fully resolved input dab descriptor.
Definition brush.h:65
float display_color[3]
Definition brush.h:82
uint32_t stroke_batch
Definition brush.h:85
Generic float RGBA patch stored either in malloc memory or pixel cache.
Integer axis-aligned rectangle in buffer coordinates.
Callback bundle used by stroke processing entry points.
dt_drawlayer_paint_build_dab_cb build_dab
dt_drawlayer_paint_stroke_seed_cb on_stroke_seed
dt_drawlayer_paint_layer_to_widget_cb layer_to_widget
One raw pointer event queued to stroke processing.
Mutable stroke runtime state owned by worker/backend code.
dt_drawlayer_damaged_rect_t bounds
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43