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 "iop/drawlayer/cache.h"
29
30#include "common/darktable.h"
31#include "common/math.h"
33
34#include <math.h>
35#include <string.h>
36
42static inline float _clamp01(const float v)
43{
44 return fminf(fmaxf(v, 0.0f), 1.0f);
45}
46
48static inline float _lerpf(const float a, const float b, const float t)
49{
50 return a + (b - a) * t;
51}
52
54static inline float _paint_voronoi_strip_angle_measure(const float rho, const float strip_ratio)
55{
56 if(strip_ratio <= 0.0f) return 0.0f;
57 if(rho <= strip_ratio + 1e-6f) return 2.0f * (float)G_PI;
58 return 4.0f * asinf(_clamp01(strip_ratio / fmaxf(rho, 1e-6f)));
59}
60
62static inline float _paint_dab_sample_spacing(const dt_drawlayer_brush_dab_t *dab, const float distance_percent)
63{
64 if(IS_NULL_PTR(dab)) return 1.0f;
65 const float radius = fmaxf(0.5f, dab->radius);
66 const float diameter = 2.0f * radius;
67 return _lerpf(1.0f, diameter, _clamp01(distance_percent));
68}
69
71static inline float _paint_segment_sample_spacing(const dt_drawlayer_brush_dab_t *dabs, const int count,
72 const float distance_percent)
73{
74 if(count <= 0) return 1.0f;
75 if(count == 1) return _paint_dab_sample_spacing(&dabs[0], distance_percent);
76 const dt_drawlayer_brush_dab_t *p_start = &dabs[count - 2];
77 const dt_drawlayer_brush_dab_t *p_end = &dabs[count - 1];
78 const float min_radius = fmaxf(0.5f, fminf(p_start->radius, p_end->radius));
79 const dt_drawlayer_brush_dab_t tmp = { .radius = min_radius };
80 return _paint_dab_sample_spacing(&tmp, distance_percent);
81}
82
84static inline float _paint_cubic_hermitef(const float p0, const float p1, const float m0, const float m1, const float t)
85{
86 const float t2 = t * t;
87 const float t3 = t2 * t;
88 return (2.0f * t3 - 3.0f * t2 + 1.0f) * p0 + (t3 - 2.0f * t2 + t) * m0
89 + (-2.0f * t3 + 3.0f * t2) * p1 + (t3 - t2) * m1;
90}
91
97 const int count, const float t)
98{
99 const dt_drawlayer_brush_dab_t *p_prev = (count >= 3) ? &dabs[count - 3] : &dabs[count - 2];
100 const dt_drawlayer_brush_dab_t *p_start = &dabs[count - 2];
101 const dt_drawlayer_brush_dab_t *p_end = &dabs[count - 1];
102
103 const float seg_dx = p_end->x - p_start->x;
104 const float seg_dy = p_end->y - p_start->y;
105 const float seg_len = hypotf(seg_dx, seg_dy);
106 const float dir_x = (seg_len > 1e-6f) ? (seg_dx / seg_len) : p_start->dir_x;
107 const float dir_y = (seg_len > 1e-6f) ? (seg_dy / seg_len) : p_start->dir_y;
108 const float m1x = (count >= 3) ? 0.5f * (p_end->x - p_prev->x) : (p_end->x - p_start->x);
109 const float m1y = (count >= 3) ? 0.5f * (p_end->y - p_prev->y) : (p_end->y - p_start->y);
110 const float m2x = p_end->x - p_start->x;
111 const float m2y = p_end->y - p_start->y;
112 const dt_aligned_pixel_simd_t t4 = dt_simd_set1(t);
113 const dt_aligned_pixel_simd_t color_start = dt_load_simd(p_start->color);
114 const dt_aligned_pixel_simd_t color_end = dt_load_simd(p_end->color);
115
117 .x = _paint_cubic_hermitef(p_start->x, p_end->x, m1x, m2x, t),
118 .y = _paint_cubic_hermitef(p_start->y, p_end->y, m1y, m2y, t),
119 .wx = _lerpf(p_start->wx, p_end->wx, t),
120 .wy = _lerpf(p_start->wy, p_end->wy, t),
121 .radius = fmaxf(0.5f, _lerpf(p_start->radius, p_end->radius, t)),
122 .dir_x = dir_x,
123 .dir_y = dir_y,
124 .opacity = _clamp01(_lerpf(p_start->opacity, p_end->opacity, t)),
125 .flow = _clamp01(_lerpf(p_start->flow, p_end->flow, t)),
126 .sprinkles = _clamp01(_lerpf(p_start->sprinkles, p_end->sprinkles, t)),
127 .sprinkle_size = fmaxf(0.0f, _lerpf(p_start->sprinkle_size, p_end->sprinkle_size, t)),
128 .sprinkle_coarseness = _clamp01(_lerpf(p_start->sprinkle_coarseness, p_end->sprinkle_coarseness, t)),
129 .hardness = _clamp01(_lerpf(p_start->hardness, p_end->hardness, t)),
130 .color = { 0.0f, 0.0f, 0.0f, 0.0f },
131 .display_color = {
132 _lerpf(p_start->display_color[0], p_end->display_color[0], t),
133 _lerpf(p_start->display_color[1], p_end->display_color[1], t),
134 _lerpf(p_start->display_color[2], p_end->display_color[2], t),
135 },
136 .shape = (t < 0.5f) ? p_start->shape : p_end->shape,
137 .mode = (t < 0.5f) ? p_start->mode : p_end->mode,
138 };
139 dt_store_simd(dab.color, color_start + (color_end - color_start) * t4);
140 return dab;
141}
142
148 const float sample_step)
149{
150 /* Estimate the fraction of a dab support area covered by one spacing strip.
151 * This is used by brush flow logic to normalize per-sample opacity at
152 * different sampling distances. */
153 if(IS_NULL_PTR(dab) || !isfinite(sample_step)) return 1.0f;
154 const float support_radius = fmaxf(dab->radius, 0.5f);
155 const float overlap_span = 2.0f * support_radius;
156 if(sample_step <= 1e-6f || overlap_span <= 1e-6f || sample_step >= overlap_span - 1e-6f) return 1.0f;
157
158 const float half_strip = 0.5f * sample_step;
159 if(dab->shape != DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN && _clamp01(dab->hardness) >= 1.0f - 1e-6f)
160 {
161 const float clamped_half_strip = fminf(half_strip, support_radius);
162 const float chord_half = sqrtf(fmaxf(support_radius * support_radius
163 - clamped_half_strip * clamped_half_strip, 0.0f));
164 const float strip_area = sample_step * chord_half
165 + 2.0f * support_radius * support_radius
166 * asinf(_clamp01(clamped_half_strip / support_radius));
167 const float full_area = (float)G_PI * support_radius * support_radius;
168 return _clamp01(strip_area / fmaxf(full_area, 1e-6f));
169 }
170
171 const float strip_ratio = _clamp01(half_strip / support_radius);
172 const float full_mass = 2.0f * (float)G_PI * dt_drawlayer_brush_mass_primitive_eval(dab, 1.0f);
173 if(!isfinite(full_mass) || full_mass <= 1e-6f) return 1.0f;
174
175 const float weight_samples = 32.f;
176 const float dr = 1.0f / weight_samples;
177 float strip_mass = 0.0f;
178 for(int ir = 0; ir < weight_samples; ir++)
179 {
180 const float rho = ((float)ir + 0.5f) * dr;
181 const float profile = dt_drawlayer_brush_profile_eval(dab, rho * rho);
182 if(!isfinite(profile) || profile <= 0.0f) continue;
183 const float angle = _paint_voronoi_strip_angle_measure(rho, strip_ratio);
184 if(!isfinite(angle) || angle <= 0.0f) continue;
185 strip_mass += angle * profile * rho * dr;
186 }
187 const float scale = strip_mass / full_mass;
188 return isfinite(scale) ? _clamp01(scale) : 1.0f;
189}
190
193{
194 /* Lazily allocate queue storage so idle modules keep a tiny footprint. */
195 if(IS_NULL_PTR(state)) return FALSE;
196 if(state->raw_inputs) return TRUE;
197 state->raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
198 return !IS_NULL_PTR(state->raw_inputs);
199}
200
203{
204 if(IS_NULL_PTR(state)) return FALSE;
205 if(state->pending_dabs) return TRUE;
206 state->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
207 return !IS_NULL_PTR(state->pending_dabs);
208}
209
212 const dt_drawlayer_brush_dab_t *segment_start,
213 const dt_drawlayer_brush_dab_t *segment_end,
214 const float t)
215{
216 dt_drawlayer_brush_dab_t window[3] = { 0 };
217 int count = 2;
218 if(!IS_NULL_PTR(state) && state->have_prev_raw_dab)
219 {
220 window[0] = state->prev_raw_dab;
221 window[1] = *segment_start;
222 window[2] = *segment_end;
223 count = 3;
224 }
225 else
226 {
227 window[0] = *segment_start;
228 window[1] = *segment_end;
229 }
230 return _paint_build_segment_window_sample(window, count, _clamp01(t));
231}
232
238 const dt_drawlayer_brush_dab_t *segment_start,
239 const dt_drawlayer_brush_dab_t *segment_end,
240 float *cumulative, const int segments, float *total_len)
241{
242 /* Build a short arc-length LUT for cubic interpolation, then sample by
243 * distance instead of parameter t to keep spacing uniform at high speed. */
244 if(IS_NULL_PTR(cumulative) || segments <= 0 || IS_NULL_PTR(total_len)) return;
245 cumulative[0] = 0.0f;
246 *total_len = 0.0f;
247 dt_drawlayer_brush_dab_t prev = _sample_raw_segment_cubic_param(state, segment_start, segment_end, 0.0f);
248 for(int i = 1; i <= segments; i++)
249 {
250 const float t = (float)i / (float)segments;
251 const dt_drawlayer_brush_dab_t cur = _sample_raw_segment_cubic_param(state, segment_start, segment_end, t);
252 *total_len += hypotf(cur.x - prev.x, cur.y - prev.y);
253 cumulative[i] = *total_len;
254 prev = cur;
255 }
256}
257
260 const dt_drawlayer_brush_dab_t *segment_start,
261 const dt_drawlayer_brush_dab_t *segment_end,
262 const float target_norm,
263 const float *cumulative,
264 const int segments,
265 const float total_len)
266{
267 /* Invert normalized arc length against the LUT (piecewise linear in t). */
268 if(IS_NULL_PTR(cumulative) || segments <= 0 || total_len <= 1e-6f)
269 return _sample_raw_segment_cubic_param(state, segment_start, segment_end, target_norm);
270
271 const float target_len = _clamp01(target_norm) * total_len;
272 int k = 0;
273 while(k < segments && cumulative[k + 1] < target_len) k++;
274
275 const float l0 = cumulative[k];
276 const float l1 = cumulative[MIN(k + 1, segments)];
277 const float span = fmaxf(l1 - l0, 1e-6f);
278 const float local = _clamp01((target_len - l0) / span);
279 const float t0 = (float)k / (float)segments;
280 const float t1 = (float)MIN(k + 1, segments) / (float)segments;
281 return _sample_raw_segment_cubic_param(state, segment_start, segment_end, _lerpf(t0, t1, local));
282}
283
290 const float sample_spacing,
291 const float smoothing_percent,
292 const dt_drawlayer_paint_layer_to_widget_cb layer_to_widget,
293 void *user_data)
294{
295 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || smoothing_percent <= 0.0f) return;
296 if(!state->history || state->history->len < 3) return;
297 const float real_x = dab->x;
298 const float real_y = dab->y;
299
300 const dt_drawlayer_brush_dab_t *h = (const dt_drawlayer_brush_dab_t *)state->history->data;
301 const int n = (int)state->history->len;
302 const dt_drawlayer_brush_dab_t *p0 = &h[n - 3];
303 const dt_drawlayer_brush_dab_t *p1 = &h[n - 2];
304 const dt_drawlayer_brush_dab_t *p2 = &h[n - 1];
305 const float real_radius = dab->radius;
306 const float real_opacity = dab->opacity;
307 const float real_flow = dab->flow;
308 const float real_hardness = dab->hardness;
309 const float real_sprinkles = dab->sprinkles;
310 const float real_sprinkle_size = dab->sprinkle_size;
311 const float real_sprinkle_coarseness = dab->sprinkle_coarseness;
312 const float real_color[4] = { dab->color[0], dab->color[1], dab->color[2], dab->color[3] };
313 const float real_display_color[3] = { dab->display_color[0], dab->display_color[1], dab->display_color[2] };
314
315 /* Quadratic extrapolation from previous 3 dabs, then enforce exactly one
316 * sample-spacing ahead from p2 so smoothing honors the sampling constraint. */
317 const float qx = 3.0f * p2->x - 3.0f * p1->x + p0->x;
318 const float qy = 3.0f * p2->y - 3.0f * p1->y + p0->y;
319 float dvx = qx - p2->x;
320 float dvy = qy - p2->y;
321 float dlen = hypotf(dvx, dvy);
322 if(dlen <= 1e-6f)
323 {
324 dvx = real_x - p2->x;
325 dvy = real_y - p2->y;
326 dlen = hypotf(dvx, dvy);
327 }
328 const float step = fmaxf(sample_spacing, 1e-6f);
329 const float pred_x = (dlen > 1e-6f) ? (p2->x + dvx * (step / dlen)) : real_x;
330 const float pred_y = (dlen > 1e-6f) ? (p2->y + dvy * (step / dlen)) : real_y;
331 const float blend = 0.5f * _clamp01(smoothing_percent);
332 const float pred_radius = fmaxf(0.5f, 3.0f * p2->radius - 3.0f * p1->radius + p0->radius);
333 const float pred_opacity = _clamp01(3.0f * p2->opacity - 3.0f * p1->opacity + p0->opacity);
334 const float pred_flow = _clamp01(3.0f * p2->flow - 3.0f * p1->flow + p0->flow);
335 const float pred_hardness = _clamp01(3.0f * p2->hardness - 3.0f * p1->hardness + p0->hardness);
336 const float pred_sprinkles = _clamp01(3.0f * p2->sprinkles - 3.0f * p1->sprinkles + p0->sprinkles);
337 const float pred_sprinkle_size = fmaxf(0.0f, 3.0f * p2->sprinkle_size - 3.0f * p1->sprinkle_size + p0->sprinkle_size);
338 const float pred_sprinkle_coarseness
340 dab->x = _lerpf(dab->x, pred_x, blend);
341 dab->y = _lerpf(dab->y, pred_y, blend);
342 dab->radius = _lerpf(real_radius, pred_radius, blend);
343 dab->opacity = _lerpf(real_opacity, pred_opacity, blend);
344 dab->flow = _lerpf(real_flow, pred_flow, blend);
345 dab->hardness = _lerpf(real_hardness, pred_hardness, blend);
346 dab->sprinkles = _lerpf(real_sprinkles, pred_sprinkles, blend);
347 dab->sprinkle_size = _lerpf(real_sprinkle_size, pred_sprinkle_size, blend);
348 dab->sprinkle_coarseness = _lerpf(real_sprinkle_coarseness, pred_sprinkle_coarseness, blend);
349 dab->color[0] = _lerpf(real_color[0], 3.0f * p2->color[0] - 3.0f * p1->color[0] + p0->color[0], blend);
350 dab->color[1] = _lerpf(real_color[1], 3.0f * p2->color[1] - 3.0f * p1->color[1] + p0->color[1], blend);
351 dab->color[2] = _lerpf(real_color[2], 3.0f * p2->color[2] - 3.0f * p1->color[2] + p0->color[2], blend);
352 dab->color[3] = _lerpf(real_color[3], 3.0f * p2->color[3] - 3.0f * p1->color[3] + p0->color[3], blend);
353 dab->display_color[0]
354 = _lerpf(real_display_color[0], 3.0f * p2->display_color[0] - 3.0f * p1->display_color[0] + p0->display_color[0], blend);
355 dab->display_color[1]
356 = _lerpf(real_display_color[1], 3.0f * p2->display_color[1] - 3.0f * p1->display_color[1] + p0->display_color[1], blend);
357 dab->display_color[2]
358 = _lerpf(real_display_color[2], 3.0f * p2->display_color[2] - 3.0f * p1->display_color[2] + p0->display_color[2], blend);
359
360 const dt_drawlayer_brush_dab_t *prev = &h[n - 1];
361 const float rvx = real_x - prev->x;
362 const float rvy = real_y - prev->y;
363 const float svx = dab->x - prev->x;
364 const float svy = dab->y - prev->y;
365 const float real_dist = hypotf(rvx, rvy);
366 const float smooth_dist = hypotf(svx, svy);
367 const float min_safe = 0.5f * fmaxf(sample_spacing, 1e-6f);
368 const float dot = rvx * svx + rvy * svy;
369 if((smooth_dist < min_safe && real_dist > smooth_dist) || dot <= 0.0f)
370 {
371 dab->x = real_x;
372 dab->y = real_y;
373 }
374
375 if(layer_to_widget) layer_to_widget(user_data, dab->x, dab->y, &dab->wx, &dab->wy);
376}
377
380{
381 /* Emit one dab and keep a full emitted history for smoothing/prediction. */
382 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || IS_NULL_PTR(state->pending_dabs)) return;
383 if(state->history && state->history->len > 0)
384 {
385 const dt_drawlayer_brush_dab_t *prev
386 = &g_array_index(state->history, dt_drawlayer_brush_dab_t, state->history->len - 1);
387 const float dx = dab->x - prev->x;
388 const float dy = dab->y - prev->y;
389 const float len = hypotf(dx, dy);
390 if(len > 1e-6f)
391 {
392 dab->dir_x = dx / len;
393 dab->dir_y = dy / len;
394 }
395 }
396 g_array_append_val(state->history, *dab);
397 g_array_append_val(state->pending_dabs, *dab);
398}
399
401static inline void _freeze_emitted_dab_raster_state(dt_drawlayer_brush_dab_t *dab, const float sample_spacing)
402{
403 if(IS_NULL_PTR(dab)) return;
404 dab->sample_spacing = fmaxf(sample_spacing, 1e-6f);
406}
407
411 const float sample_spacing,
412 const dt_drawlayer_paint_layer_to_widget_cb layer_to_widget,
413 void *user_data)
414{
415 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || state->history->len == 0) return;
416 const dt_drawlayer_brush_dab_t *prev
417 = &g_array_index(state->history, dt_drawlayer_brush_dab_t, state->history->len - 1);
418
419 const float target = fmaxf(sample_spacing, 1e-6f);
420 float dx = dab->x - prev->x;
421 float dy = dab->y - prev->y;
422 float d = hypotf(dx, dy);
423 if(!(d > 1e-6f))
424 {
425 float dir_x = dab->dir_x;
426 float dir_y = dab->dir_y;
427 float dir_len = hypotf(dir_x, dir_y);
428 if(dir_len <= 1e-6f)
429 {
430 dir_x = prev->dir_x;
431 dir_y = prev->dir_y;
432 dir_len = hypotf(dir_x, dir_y);
433 }
434 if(dir_len <= 1e-6f)
435 {
436 dir_x = 1.0f;
437 dir_y = 0.0f;
438 dir_len = 1.0f;
439 }
440 dx = dir_x / dir_len;
441 dy = dir_y / dir_len;
442 }
443 else
444 {
445 dx /= d;
446 dy /= d;
447 }
448
449 dab->x = prev->x + dx * target;
450 dab->y = prev->y + dy * target;
451 dab->dir_x = dx;
452 dab->dir_y = dy;
453 if(layer_to_widget) layer_to_widget(user_data, dab->x, dab->y, &dab->wx, &dab->wy);
454}
455
458{
459 /* Reset only per-stroke path generation state.
460 * Queue storage and reusable allocations are kept by the owner. */
461 if(IS_NULL_PTR(state)) return;
462 if(state->history) g_array_set_size(state->history, 0);
463 if(state->pending_dabs) g_array_set_size(state->pending_dabs, 0);
464 if(state->dab_window) g_array_set_size(state->dab_window, 0);
465 state->last_input_dab = (dt_drawlayer_brush_dab_t){ 0 };
466 state->have_last_input_dab = FALSE;
467 state->prev_raw_dab = (dt_drawlayer_brush_dab_t){ 0 };
468 state->have_prev_raw_dab = FALSE;
469 state->stroke_arc_length = 0.0f;
470 state->sampled_arc_length = 0.0f;
471 state->distance_percent = 0.0f;
472 state->stroke_seed = 0u;
473}
474
477{
478 /* Full stroke reset: pending raw events + generated path state. */
479 if(IS_NULL_PTR(state)) return;
480 if(state->raw_inputs) g_array_set_size(state->raw_inputs, 0);
481 state->raw_input_cursor = 0;
483}
484
488{
489 if(IS_NULL_PTR(state) || IS_NULL_PTR(input)) return FALSE;
491 || (state->have_last_input_dab && input->stroke_batch != 0u
492 && state->last_input_dab.stroke_batch != 0u
493 && input->stroke_batch != state->last_input_dab.stroke_batch);
494}
495
498{
499 if(IS_NULL_PTR(input)) return 0u;
500 const uint64_t qx = (uint64_t)(int64_t)llrintf((double)input->wx * 256.0);
501 const uint64_t qy = (uint64_t)(int64_t)llrintf((double)input->wy * 256.0);
502 return ((uint64_t)input->stroke_batch << 32)
503 ^ (uint64_t)input->event_ts
504 ^ (qx * 0x9e3779b185ebca87ull)
505 ^ (qy * 0xc2b2ae3d27d4eb4full);
506}
507
510 const dt_drawlayer_brush_dab_t *dab)
511{
512 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || !_ensure_pending_dabs(state)) return;
513 state->last_input_dab = *dab;
514 state->have_last_input_dab = TRUE;
515
516 dt_drawlayer_brush_dab_t first = *dab;
519 _emit_dab(state, &first);
520 state->sampled_arc_length = 0.0f;
521}
522
528{
529 if(IS_NULL_PTR(state) || !state->have_last_input_dab || !state->history || !_ensure_pending_dabs(state)) return;
530 if(!state->history || state->history->len > 0) return;
531
532 dt_drawlayer_brush_dab_t dab = state->last_input_dab;
535 _emit_dab(state, &dab);
536 state->sampled_arc_length = 0.0f;
537}
538
541 const dt_drawlayer_brush_dab_t *dab)
542{
543 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || state->history->len != 0) return;
544
545 const float dx = dab->x - state->last_input_dab.x;
546 const float dy = dab->y - state->last_input_dab.y;
547 const float dir_len = hypotf(dx, dy);
548 if(dir_len > 1e-6f)
549 {
550 state->last_input_dab.dir_x = dx / dir_len;
551 state->last_input_dab.dir_y = dy / dir_len;
552 }
553
555}
556
563 const dt_drawlayer_paint_callbacks_t *callbacks,
564 void *user_data)
565{
566 /* Raw event -> one segment update:
567 * - build input dab from callbacks,
568 * - update cumulative arc length,
569 * - emit uniformly spaced samples using arc-length interpolation,
570 * - apply optional smoothing and spacing enforcement. */
571 if(IS_NULL_PTR(state) || IS_NULL_PTR(input) || IS_NULL_PTR(callbacks) || IS_NULL_PTR(callbacks->build_dab) || !_ensure_pending_dabs(state)) return;
572
573 const float distance_percent = _clamp01(input->distance_percent);
574 state->distance_percent = distance_percent;
575 const float smoothing_percent = _clamp01(input->smoothing_percent);
576
578 {
580 state->stroke_seed = _paint_make_stroke_seed(input);
581 if(callbacks->on_stroke_seed) callbacks->on_stroke_seed(user_data, state->stroke_seed);
582 }
583
584 dt_drawlayer_brush_dab_t dab = { 0 };
585 if(!callbacks->build_dab(user_data, state, input, &dab)) return;
586
587 if(!state->have_last_input_dab)
588 {
590 return;
591 }
592
593 const dt_drawlayer_brush_dab_t segment_start = state->last_input_dab;
594 const float seg_len = hypotf(dab.x - segment_start.x, dab.y - segment_start.y);
595 const float prev_arc = state->stroke_arc_length;
596 enum { arc_lut_segments = 24 };
597 float arc_lut[arc_lut_segments + 1] = { 0.0f };
598 float arc_total = 0.0f;
599 _build_raw_segment_cubic_arclen_lut(state, &segment_start, &dab,
600 arc_lut, arc_lut_segments, &arc_total);
601 const float seg_arc = (arc_total > 1e-6f) ? arc_total : seg_len;
602 state->stroke_arc_length += seg_arc;
604
605 if(seg_arc > 1e-6f)
606 {
607 const dt_drawlayer_brush_dab_t spacing_ref_segment[2] = { segment_start, dab };
608 const float segment_spacing = _paint_segment_sample_spacing(spacing_ref_segment, 2, distance_percent);
609 while(TRUE)
610 {
611 const float sample_spacing = segment_spacing;
612 const float target_arc = state->sampled_arc_length + sample_spacing;
613 if(target_arc > state->stroke_arc_length + 1e-6f) break;
614
615 if(target_arc <= prev_arc + 1e-6f)
616 {
617 state->sampled_arc_length = target_arc;
618 continue;
619 }
620
621 const float t = _clamp01((target_arc - prev_arc) / seg_arc);
623 = _sample_raw_segment_cubic_arclen(state, &segment_start, &dab, t,
624 arc_lut, arc_lut_segments, arc_total);
625 sample.stroke_batch = input->stroke_batch;
627 _apply_quadratic_dab_smoothing(state, &sample, sample_spacing, smoothing_percent,
628 callbacks->layer_to_widget, user_data);
629 _enforce_dab_center_spacing(state, &sample, sample_spacing,
630 callbacks->layer_to_widget, user_data);
631 _freeze_emitted_dab_raster_state(&sample, sample_spacing);
632 _emit_dab(state, &sample);
633 state->sampled_arc_length = target_arc;
634 }
635 }
636
637 state->prev_raw_dab = segment_start;
638 state->have_prev_raw_dab = TRUE;
639 state->last_input_dab = dab;
640}
641
644{
645 if(IS_NULL_PTR(state) || IS_NULL_PTR(input) || !_ensure_raw_inputs(state)) return FALSE;
646 g_array_append_val(state->raw_inputs, *input);
647 return TRUE;
648}
649
651{
652 /* FIFO compaction after processing to avoid unbounded queue growth. */
653 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->raw_inputs)) return;
654 if(state->raw_input_cursor == 0) return;
655
656 const guint processed = state->raw_input_cursor;
657 const guint len = state->raw_inputs->len;
658 if(processed >= len)
659 g_array_set_size(state->raw_inputs, 0);
660 else
661 g_array_remove_range(state->raw_inputs, 0, processed);
662
663 state->raw_input_cursor = 0;
664}
665
667 const dt_drawlayer_paint_callbacks_t *callbacks,
668 void *user_data)
669{
670 /* Drain all queued raw inputs in FIFO order. No coalescing here. */
671 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->raw_inputs) || IS_NULL_PTR(callbacks)) return;
672
673 while(state->raw_input_cursor < state->raw_inputs->len)
674 {
676 = &g_array_index(state->raw_inputs, dt_drawlayer_paint_raw_input_t, state->raw_input_cursor);
677 _paint_process_one_raw_input(state, input, callbacks, user_data);
678 state->raw_input_cursor++;
679 }
680
682}
683
685 const dt_drawlayer_brush_dab_t *current,
686 const dt_drawlayer_brush_dab_t *previous)
687{
688 /* Smudge pickup follows stroke motion with a damped response. */
689 if(IS_NULL_PTR(state) || IS_NULL_PTR(current)) return;
690
692 float pickup_x = 0.0f;
693 float pickup_y = 0.0f;
695
696 if(!have_pickup)
697 {
699 return;
700 }
701
702 const float dx = previous ? (current->x - previous->x) : 0.0f;
703 const float dy = previous ? (current->y - previous->y) : 0.0f;
704 const float travel = hypotf(dx, dy);
705 if(travel <= 1e-6f) return;
706
707 const float radius = fmaxf(current->radius, 0.5f);
708 const float response = 1.0f - expf(-0.5f * travel / radius);
709 pickup_x = _lerpf(pickup_x, current->x, response);
710 pickup_y = _lerpf(pickup_y, current->y, response);
712}
713
715 const float distance_percent,
716 const dt_drawlayer_cache_patch_t *sample_patch,
718 const float scale,
719 dt_drawlayer_cache_patch_t *stroke_mask,
720 dt_drawlayer_damaged_rect_t *runtime_state,
721 dt_drawlayer_paint_stroke_t *runtime_private)
722{
723 /* Backend helper for raster path replay:
724 * keep a tiny dab window, compute spacing normalization, rasterize one sample. */
725 if(IS_NULL_PTR(dab) || !runtime_private || !runtime_private->dab_window) return FALSE;
726
727 GArray *const history = runtime_private->dab_window;
728 if(dab->stroke_pos == DT_DRAWLAYER_PAINT_STROKE_FIRST) g_array_set_size(history, 0);
729 g_array_append_val(history, *dab);
730
731 const int total = (int)history->len;
732 const int count = MIN(total, 3);
733 dt_drawlayer_brush_dab_t window[3] = { 0 };
734 memcpy(window, ((dt_drawlayer_brush_dab_t *)history->data) + (total - count),
735 (size_t)count * sizeof(dt_drawlayer_brush_dab_t));
736
737 const dt_drawlayer_brush_dab_t *sample = &window[count - 1];
738 const dt_drawlayer_brush_dab_t *previous_sample = (count > 1) ? &window[count - 2] : NULL;
739 const float spacing = (sample->sample_spacing > 1e-6f)
740 ? sample->sample_spacing
741 : _paint_dab_sample_spacing(sample, _clamp01(distance_percent));
742 const float sample_opacity_scale = (sample->sample_opacity_scale > 1e-6f)
743 ? sample->sample_opacity_scale
744 : _paint_stroke_sample_opacity_scale(sample, spacing);
745
746 if(count == 1 && runtime_private)
747 dt_drawlayer_paint_runtime_set_smudge_pickup(runtime_private, 0.0f, 0.0f, FALSE);
748
749 if(!IS_NULL_PTR(runtime_private))
750 {
752 {
753 if(!IS_NULL_PTR(previous_sample)) _advance_smudge_pickup_state(runtime_private, sample, previous_sample);
754 }
755 else
756 dt_drawlayer_paint_runtime_set_smudge_pickup(runtime_private, 0.0f, 0.0f, FALSE);
757 }
758
759 const double t0 = dt_get_wtime();
760 const gboolean rasterized
761 = dt_drawlayer_brush_rasterize(sample_patch, patch, scale, sample, sample_opacity_scale, stroke_mask,
762 runtime_private);
763 const double t1 = dt_get_wtime();
764 if(rasterized && !IS_NULL_PTR(runtime_state) && !IS_NULL_PTR(runtime_private) && runtime_private->bounds.valid)
765 dt_drawlayer_paint_runtime_note_dab_damage(runtime_state, &runtime_private->bounds);
766
768 {
769 if(!IS_NULL_PTR(runtime_private) && runtime_private->bounds.valid)
770 {
771 const int bounds_w = runtime_private->bounds.se[0] - runtime_private->bounds.nw[0];
772 const int bounds_h = runtime_private->bounds.se[1] - runtime_private->bounds.nw[1];
774 "[drawlayer] paint raster mode=%d pos=%d spacing=%.3f alpha_scale=%.4f area=%dx%d ms=%.3f\n",
775 sample->mode, sample->stroke_pos, spacing, sample_opacity_scale, bounds_w, bounds_h,
776 1000.0 * (t1 - t0));
777 }
778 else
780 "[drawlayer] paint raster mode=%d pos=%d spacing=%.3f alpha_scale=%.4f area=0x0 ms=%.3f\n",
781 sample->mode, sample->stroke_pos, spacing, sample_opacity_scale, 1000.0 * (t1 - t0));
782 }
783
784 if(history->len > 3) g_array_remove_range(history, 0, history->len - 3);
785 return TRUE;
786}
787
795
801
803{
804 if(IS_NULL_PTR(state)) return;
805 state->valid = FALSE;
806 state->nw[0] = 0;
807 state->nw[1] = 0;
808 state->se[0] = 0;
809 state->se[1] = 0;
810}
811
819
821{
822 if(IS_NULL_PTR(state) || !*state) return;
823 if((*state)->raw_inputs) g_array_free((*state)->raw_inputs, TRUE);
824 dt_free((*state)->smudge_pixels);
825 dt_free(*state);
826}
827
829{
830 /* Reset per-stroke transient payload while preserving reusable allocations. */
831 if(IS_NULL_PTR(state)) return;
832 state->smudge_pickup_x = 0.0f;
833 state->smudge_pickup_y = 0.0f;
834 state->have_smudge_pickup = FALSE;
835 if(state->smudge_pixels && state->smudge_width > 0 && state->smudge_height > 0)
836 memset(state->smudge_pixels, 0, (size_t)state->smudge_width * state->smudge_height * 4 * sizeof(float));
837}
838
840{
841 if(IS_NULL_PTR(state)) return;
842 state->stroke_seed = seed;
843}
844
849
851 const int height)
852{
853 if(IS_NULL_PTR(state) || width <= 0 || height <= 0) return FALSE;
854 if(state->smudge_width == width && state->smudge_height == height && state->smudge_pixels) return TRUE;
855
856 float *pixels = g_realloc(state->smudge_pixels, (size_t)width * height * 4 * sizeof(float));
857 if(IS_NULL_PTR(pixels)) return FALSE;
858 state->smudge_pixels = pixels;
859 state->smudge_width = width;
860 state->smudge_height = height;
861 memset(state->smudge_pixels, 0, (size_t)width * height * 4 * sizeof(float));
862 return TRUE;
863}
864
866{
867 return state ? state->smudge_pixels : NULL;
868}
869
871{
872 return state ? state->smudge_width : 0;
873}
874
876{
877 return state ? state->smudge_height : 0;
878}
879
881{
882 return state && state->have_smudge_pickup;
883}
884
886 float *x, float *y)
887{
888 if(!IS_NULL_PTR(x)) *x = state ? state->smudge_pickup_x : 0.0f;
889 if(!IS_NULL_PTR(y)) *y = state ? state->smudge_pickup_y : 0.0f;
890}
891
893 const float x, const float y, const gboolean have_pickup)
894{
895 if(IS_NULL_PTR(state)) return;
896 state->smudge_pickup_x = x;
897 state->smudge_pickup_y = y;
898 state->have_smudge_pickup = have_pickup;
899}
900
902 const dt_drawlayer_brush_dab_t *dab,
903 const int width, const int height,
904 const int origin_x, const int origin_y,
905 const float scale)
906{
907 /* Compute current dab footprint in target buffer coordinates. */
908 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || dab->radius <= 0.0f || dab->opacity <= 0.0f || scale <= 0.0f) return FALSE;
909 const float support_radius = dab->radius;
910
911 state->bounds.valid = TRUE;
912 state->bounds.nw[0] = MAX(0, (int)floorf((dab->x - support_radius) * scale) - origin_x);
913 state->bounds.nw[1] = MAX(0, (int)floorf((dab->y - support_radius) * scale) - origin_y);
914 state->bounds.se[0] = MIN(width, (int)ceilf((dab->x + support_radius) * scale) - origin_x + 1);
915 state->bounds.se[1] = MIN(height, (int)ceilf((dab->y + support_radius) * scale) - origin_y + 1);
916 if(state->bounds.se[0] <= state->bounds.nw[0]
917 || state->bounds.se[1] <= state->bounds.nw[1])
918 return FALSE;
919 return TRUE;
920}
921
923 const dt_drawlayer_damaged_rect_t *dab_rect)
924{
925 if(IS_NULL_PTR(state)) return;
926 if(IS_NULL_PTR(dab_rect) || !dab_rect->valid) return;
927 if(dab_rect->se[0] <= dab_rect->nw[0] || dab_rect->se[1] <= dab_rect->nw[1]) return;
928 if(!state->valid)
929 {
930 state->valid = TRUE;
931 *state = *dab_rect;
932 return;
933 }
934 state->nw[0] = MIN(state->nw[0], dab_rect->nw[0]);
935 state->nw[1] = MIN(state->nw[1], dab_rect->nw[1]);
936 state->se[0] = MAX(state->se[0], dab_rect->se[0]);
937 state->se[1] = MAX(state->se[1], dab_rect->se[1]);
938}
939
942{
943 if(IS_NULL_PTR(state) || !state->valid) return FALSE;
944 if(!IS_NULL_PTR(out_rect)) *out_rect = *state;
945 return TRUE;
946}
947
949 const dt_drawlayer_damaged_rect_t *add_rect)
950{
951 if(IS_NULL_PTR(rect)) return;
952 if(IS_NULL_PTR(add_rect) || !add_rect->valid) return;
953 if(add_rect->se[0] <= add_rect->nw[0] || add_rect->se[1] <= add_rect->nw[1]) return;
954
955 if(!rect->valid)
956 {
957 *rect = *add_rect;
958 return;
959 }
960
961 rect->nw[0] = MIN(rect->nw[0], add_rect->nw[0]);
962 rect->nw[1] = MIN(rect->nw[1], add_rect->nw[1]);
963 rect->se[0] = MAX(rect->se[0], add_rect->se[0]);
964 rect->se[1] = MAX(rect->se[1], add_rect->se[1]);
965}
966
968 dt_drawlayer_damaged_rect_t *target_rect)
969{
970 if(IS_NULL_PTR(path_state) || IS_NULL_PTR(target_rect)) return FALSE;
971
972 dt_drawlayer_damaged_rect_t add = { 0 };
973 const gboolean have_damage = dt_drawlayer_paint_runtime_get_stroke_damage(path_state, &add);
975 if(!have_damage) return FALSE;
976
977 _paint_union_damage_rect(target_rect, &add);
978 return TRUE;
979}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
Definition brush.h:51
@ DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN
Definition brush.h:40
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)
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
dt_store_simd(out, value)
@ DT_DEBUG_PERF
Definition darktable.h:719
@ DT_DEBUG_VERBOSE
Definition darktable.h:743
#define dt_free(ptr)
Definition darktable.h:456
static double dt_get_wtime(void)
Definition darktable.h:914
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
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).
static const float x
const int t
const float l1
const float v
float *const restrict const size_t k
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
int32_t unmuted
Definition darktable.h:760
Fully resolved input dab descriptor.
Definition brush.h:64
float display_color[3]
Definition brush.h:81
uint32_t stroke_batch
Definition brush.h:84
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