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 .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{
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 }
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{
293 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || smoothing_percent <= 0.0f) return;
294 if(!state->history || state->history->len < 3) return;
295 const float real_x = dab->x;
296 const float real_y = dab->y;
297
298 const dt_drawlayer_brush_dab_t *h = (const dt_drawlayer_brush_dab_t *)state->history->data;
299 const int n = (int)state->history->len;
300 const dt_drawlayer_brush_dab_t *p0 = &h[n - 3];
301 const dt_drawlayer_brush_dab_t *p1 = &h[n - 2];
302 const dt_drawlayer_brush_dab_t *p2 = &h[n - 1];
303 const float real_radius = dab->radius;
304 const float real_opacity = dab->opacity;
305 const float real_flow = dab->flow;
306 const float real_hardness = dab->hardness;
307 const float real_sprinkles = dab->sprinkles;
308 const float real_sprinkle_size = dab->sprinkle_size;
309 const float real_sprinkle_coarseness = dab->sprinkle_coarseness;
310 const float real_color[4] = { dab->color[0], dab->color[1], dab->color[2], dab->color[3] };
311 const float real_display_color[3] = { dab->display_color[0], dab->display_color[1], dab->display_color[2] };
312
313 /* Quadratic extrapolation from previous 3 dabs, then enforce exactly one
314 * sample-spacing ahead from p2 so smoothing honors the sampling constraint. */
315 const float qx = 3.0f * p2->x - 3.0f * p1->x + p0->x;
316 const float qy = 3.0f * p2->y - 3.0f * p1->y + p0->y;
317 float dvx = qx - p2->x;
318 float dvy = qy - p2->y;
319 float dlen = hypotf(dvx, dvy);
320 if(dlen <= 1e-6f)
321 {
322 dvx = real_x - p2->x;
323 dvy = real_y - p2->y;
324 dlen = hypotf(dvx, dvy);
325 }
326 const float step = fmaxf(sample_spacing, 1e-6f);
327 const float pred_x = (dlen > 1e-6f) ? (p2->x + dvx * (step / dlen)) : real_x;
328 const float pred_y = (dlen > 1e-6f) ? (p2->y + dvy * (step / dlen)) : real_y;
329 const float blend = 0.5f * _clamp01(smoothing_percent);
330 const float pred_radius = fmaxf(0.5f, 3.0f * p2->radius - 3.0f * p1->radius + p0->radius);
331 const float pred_opacity = _clamp01(3.0f * p2->opacity - 3.0f * p1->opacity + p0->opacity);
332 const float pred_flow = _clamp01(3.0f * p2->flow - 3.0f * p1->flow + p0->flow);
333 const float pred_hardness = _clamp01(3.0f * p2->hardness - 3.0f * p1->hardness + p0->hardness);
334 const float pred_sprinkles = _clamp01(3.0f * p2->sprinkles - 3.0f * p1->sprinkles + p0->sprinkles);
335 const float pred_sprinkle_size = fmaxf(0.0f, 3.0f * p2->sprinkle_size - 3.0f * p1->sprinkle_size + p0->sprinkle_size);
336 const float pred_sprinkle_coarseness
338 dab->x = _lerpf(dab->x, pred_x, blend);
339 dab->y = _lerpf(dab->y, pred_y, blend);
340 dab->radius = _lerpf(real_radius, pred_radius, blend);
341 dab->opacity = _lerpf(real_opacity, pred_opacity, blend);
342 dab->flow = _lerpf(real_flow, pred_flow, blend);
343 dab->hardness = _lerpf(real_hardness, pred_hardness, blend);
344 dab->sprinkles = _lerpf(real_sprinkles, pred_sprinkles, blend);
345 dab->sprinkle_size = _lerpf(real_sprinkle_size, pred_sprinkle_size, blend);
346 dab->sprinkle_coarseness = _lerpf(real_sprinkle_coarseness, pred_sprinkle_coarseness, blend);
347 dab->color[0] = _lerpf(real_color[0], 3.0f * p2->color[0] - 3.0f * p1->color[0] + p0->color[0], blend);
348 dab->color[1] = _lerpf(real_color[1], 3.0f * p2->color[1] - 3.0f * p1->color[1] + p0->color[1], blend);
349 dab->color[2] = _lerpf(real_color[2], 3.0f * p2->color[2] - 3.0f * p1->color[2] + p0->color[2], blend);
350 dab->color[3] = _lerpf(real_color[3], 3.0f * p2->color[3] - 3.0f * p1->color[3] + p0->color[3], blend);
351 dab->display_color[0]
352 = _lerpf(real_display_color[0], 3.0f * p2->display_color[0] - 3.0f * p1->display_color[0] + p0->display_color[0], blend);
353 dab->display_color[1]
354 = _lerpf(real_display_color[1], 3.0f * p2->display_color[1] - 3.0f * p1->display_color[1] + p0->display_color[1], blend);
355 dab->display_color[2]
356 = _lerpf(real_display_color[2], 3.0f * p2->display_color[2] - 3.0f * p1->display_color[2] + p0->display_color[2], blend);
357
358 const dt_drawlayer_brush_dab_t *prev = &h[n - 1];
359 const float rvx = real_x - prev->x;
360 const float rvy = real_y - prev->y;
361 const float svx = dab->x - prev->x;
362 const float svy = dab->y - prev->y;
363 const float real_dist = hypotf(rvx, rvy);
364 const float smooth_dist = hypotf(svx, svy);
365 const float min_safe = 0.5f * fmaxf(sample_spacing, 1e-6f);
366 const float dot = rvx * svx + rvy * svy;
367 if((smooth_dist < min_safe && real_dist > smooth_dist) || dot <= 0.0f)
368 {
369 dab->x = real_x;
370 dab->y = real_y;
371 }
372}
373
376{
377 /* Emit one dab and keep a full emitted history for smoothing/prediction. */
378 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || IS_NULL_PTR(state->pending_dabs)) return;
379 if(state->history && state->history->len > 0)
380 {
381 const dt_drawlayer_brush_dab_t *prev
382 = &g_array_index(state->history, dt_drawlayer_brush_dab_t, state->history->len - 1);
383 const float dx = dab->x - prev->x;
384 const float dy = dab->y - prev->y;
385 const float len = hypotf(dx, dy);
386 if(len > 1e-6f)
387 {
388 dab->dir_x = dx / len;
389 dab->dir_y = dy / len;
390 }
391 }
392 g_array_append_val(state->history, *dab);
393 g_array_append_val(state->pending_dabs, *dab);
394}
395
399 const float sample_spacing)
400{
401 if(IS_NULL_PTR(dab)) return;
402 dab->sample_spacing = fmaxf(sample_spacing, 1e-6f);
403
404 /* Four floats decide the quadrature's answer. Compare them rather than integrate again:
405 * with no tablet map on size or softness -- the shipped default -- every dab of a stroke
406 * hits this, turning 32 asinf + 32 sqrtf + ~96 divisions per dab into four comparisons. */
407 if(!IS_NULL_PTR(state) && state->opacity_scale_valid
408 && state->opacity_scale_radius == dab->radius
409 && state->opacity_scale_hardness == dab->hardness
410 && state->opacity_scale_step == dab->sample_spacing
411 && state->opacity_scale_shape == dab->shape)
412 {
413 dab->sample_opacity_scale = state->opacity_scale_value;
414 return;
415 }
416
418
419 if(!IS_NULL_PTR(state))
420 {
421 state->opacity_scale_radius = dab->radius;
422 state->opacity_scale_hardness = dab->hardness;
423 state->opacity_scale_step = dab->sample_spacing;
424 state->opacity_scale_shape = dab->shape;
425 state->opacity_scale_value = dab->sample_opacity_scale;
426 state->opacity_scale_valid = TRUE;
427 }
428}
429
433 const float sample_spacing)
434{
435 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || state->history->len == 0) return;
436 const dt_drawlayer_brush_dab_t *prev
437 = &g_array_index(state->history, dt_drawlayer_brush_dab_t, state->history->len - 1);
438
439 const float target = fmaxf(sample_spacing, 1e-6f);
440 float dx = dab->x - prev->x;
441 float dy = dab->y - prev->y;
442 float d = hypotf(dx, dy);
443 if(!(d > 1e-6f))
444 {
445 float dir_x = dab->dir_x;
446 float dir_y = dab->dir_y;
447 float dir_len = hypotf(dir_x, dir_y);
448 if(dir_len <= 1e-6f)
449 {
450 dir_x = prev->dir_x;
451 dir_y = prev->dir_y;
452 dir_len = hypotf(dir_x, dir_y);
453 }
454 if(dir_len <= 1e-6f)
455 {
456 dir_x = 1.0f;
457 dir_y = 0.0f;
458 dir_len = 1.0f;
459 }
460 dx = dir_x / dir_len;
461 dy = dir_y / dir_len;
462 }
463 else
464 {
465 dx /= d;
466 dy /= d;
467 }
468
469 dab->x = prev->x + dx * target;
470 dab->y = prev->y + dy * target;
471 dab->dir_x = dx;
472 dab->dir_y = dy;
473}
474
477{
478 /* Reset only per-stroke path generation state.
479 * Queue storage and reusable allocations are kept by the owner. */
480 if(IS_NULL_PTR(state)) return;
481 if(state->history) g_array_set_size(state->history, 0);
482 if(state->pending_dabs) g_array_set_size(state->pending_dabs, 0);
483 if(state->dab_window) g_array_set_size(state->dab_window, 0);
484 state->last_input_dab = (dt_drawlayer_brush_dab_t){ 0 };
485 state->have_last_input_dab = FALSE;
486 state->prev_raw_dab = (dt_drawlayer_brush_dab_t){ 0 };
487 state->have_prev_raw_dab = FALSE;
488 state->stroke_arc_length = 0.0f;
489 state->sampled_arc_length = 0.0f;
490 state->distance_percent = 0.0f;
491 state->stroke_seed = 0u;
492}
493
496{
497 /* Full stroke reset: pending raw events + generated path state. */
498 if(IS_NULL_PTR(state)) return;
499 if(state->raw_inputs) g_array_set_size(state->raw_inputs, 0);
500 state->raw_input_cursor = 0;
502}
503
507{
508 if(IS_NULL_PTR(state) || IS_NULL_PTR(input)) return FALSE;
510 || (state->have_last_input_dab && input->stroke_batch != 0u
511 && state->last_input_dab.stroke_batch != 0u
512 && input->stroke_batch != state->last_input_dab.stroke_batch);
513}
514
517{
518 if(IS_NULL_PTR(input)) return 0u;
519 const uint64_t qx = (uint64_t)(int64_t)llrintf((double)input->wx * 256.0);
520 const uint64_t qy = (uint64_t)(int64_t)llrintf((double)input->wy * 256.0);
521 return ((uint64_t)input->stroke_batch << 32)
522 ^ (uint64_t)input->event_ts
523 ^ (qx * 0x9e3779b185ebca87ull)
524 ^ (qy * 0xc2b2ae3d27d4eb4full);
525}
526
529 const dt_drawlayer_brush_dab_t *dab)
530{
531 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || !_ensure_pending_dabs(state)) return;
532 state->last_input_dab = *dab;
533 state->have_last_input_dab = TRUE;
534
535 dt_drawlayer_brush_dab_t first = *dab;
538 _emit_dab(state, &first);
539 state->sampled_arc_length = 0.0f;
540}
541
547{
548 if(IS_NULL_PTR(state) || !state->have_last_input_dab || !state->history || !_ensure_pending_dabs(state)) return;
549 if(!state->history || state->history->len > 0) return;
550
551 dt_drawlayer_brush_dab_t dab = state->last_input_dab;
554 _emit_dab(state, &dab);
555 state->sampled_arc_length = 0.0f;
556}
557
560 const dt_drawlayer_brush_dab_t *dab)
561{
562 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || !state->history || state->history->len != 0) return;
563
564 const float dx = dab->x - state->last_input_dab.x;
565 const float dy = dab->y - state->last_input_dab.y;
566 const float dir_len = hypotf(dx, dy);
567 if(dir_len > 1e-6f)
568 {
569 state->last_input_dab.dir_x = dx / dir_len;
570 state->last_input_dab.dir_y = dy / dir_len;
571 }
572
574}
575
582 const dt_drawlayer_paint_callbacks_t *callbacks,
583 void *user_data)
584{
585 /* Raw event -> one segment update:
586 * - build input dab from callbacks,
587 * - update cumulative arc length,
588 * - emit uniformly spaced samples using arc-length interpolation,
589 * - apply optional smoothing and spacing enforcement. */
590 if(IS_NULL_PTR(state) || IS_NULL_PTR(input) || IS_NULL_PTR(callbacks) || IS_NULL_PTR(callbacks->build_dab) || !_ensure_pending_dabs(state)) return;
591
592 const float distance_percent = _clamp01(input->distance_percent);
593 state->distance_percent = distance_percent;
594 const float smoothing_percent = _clamp01(input->smoothing_percent);
595
597 {
599 state->stroke_seed = _paint_make_stroke_seed(input);
600 if(callbacks->on_stroke_seed) callbacks->on_stroke_seed(user_data, state->stroke_seed);
601 }
602
603 dt_drawlayer_brush_dab_t dab = { 0 };
604 if(!callbacks->build_dab(user_data, state, input, &dab)) return;
605
606 if(!state->have_last_input_dab)
607 {
609 return;
610 }
611
612 const dt_drawlayer_brush_dab_t segment_start = state->last_input_dab;
613 const float seg_len = hypotf(dab.x - segment_start.x, dab.y - segment_start.y);
614 const float prev_arc = state->stroke_arc_length;
615 enum { arc_lut_segments = 24 };
616 float arc_lut[arc_lut_segments + 1] = { 0.0f };
617 float arc_total = 0.0f;
618 _build_raw_segment_cubic_arclen_lut(state, &segment_start, &dab,
619 arc_lut, arc_lut_segments, &arc_total);
620 const float seg_arc = (arc_total > 1e-6f) ? arc_total : seg_len;
621 state->stroke_arc_length += seg_arc;
623
624 if(seg_arc > 1e-6f)
625 {
626 const dt_drawlayer_brush_dab_t spacing_ref_segment[2] = { segment_start, dab };
627 const float segment_spacing = _paint_segment_sample_spacing(spacing_ref_segment, 2, distance_percent);
628 while(TRUE)
629 {
630 const float sample_spacing = segment_spacing;
631 const float target_arc = state->sampled_arc_length + sample_spacing;
632 if(target_arc > state->stroke_arc_length + 1e-6f) break;
633
634 if(target_arc <= prev_arc + 1e-6f)
635 {
636 state->sampled_arc_length = target_arc;
637 continue;
638 }
639
640 const float t = _clamp01((target_arc - prev_arc) / seg_arc);
642 = _sample_raw_segment_cubic_arclen(state, &segment_start, &dab, t,
643 arc_lut, arc_lut_segments, arc_total);
644 sample.stroke_batch = input->stroke_batch;
646 _apply_quadratic_dab_smoothing(state, &sample, sample_spacing, smoothing_percent);
647 _enforce_dab_center_spacing(state, &sample, sample_spacing);
648 _freeze_emitted_dab_raster_state(state, &sample, sample_spacing);
649 _emit_dab(state, &sample);
650 state->sampled_arc_length = target_arc;
651 }
652 }
653
654 state->prev_raw_dab = segment_start;
655 state->have_prev_raw_dab = TRUE;
656 state->last_input_dab = dab;
657}
658
661{
662 if(IS_NULL_PTR(state) || IS_NULL_PTR(input) || !_ensure_raw_inputs(state)) return FALSE;
663 g_array_append_val(state->raw_inputs, *input);
664 return TRUE;
665}
666
668{
669 /* FIFO compaction after processing to avoid unbounded queue growth. */
670 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->raw_inputs)) return;
671 if(state->raw_input_cursor == 0) return;
672
673 const guint processed = state->raw_input_cursor;
674 const guint len = state->raw_inputs->len;
675 if(processed >= len)
676 g_array_set_size(state->raw_inputs, 0);
677 else
678 g_array_remove_range(state->raw_inputs, 0, processed);
679
680 state->raw_input_cursor = 0;
681}
682
684 const dt_drawlayer_paint_callbacks_t *callbacks,
685 void *user_data)
686{
687 /* Drain all queued raw inputs in FIFO order. No coalescing here. */
688 if(IS_NULL_PTR(state) || IS_NULL_PTR(state->raw_inputs) || IS_NULL_PTR(callbacks)) return;
689
690 while(state->raw_input_cursor < state->raw_inputs->len)
691 {
693 = &g_array_index(state->raw_inputs, dt_drawlayer_paint_raw_input_t, state->raw_input_cursor);
694 _paint_process_one_raw_input(state, input, callbacks, user_data);
695 state->raw_input_cursor++;
696 }
697
699}
700
702 const dt_drawlayer_brush_dab_t *current,
703 const dt_drawlayer_brush_dab_t *previous)
704{
705 /* Smudge pickup follows stroke motion with a damped response. */
706 if(IS_NULL_PTR(state) || IS_NULL_PTR(current)) return;
707
709 float pickup_x = 0.0f;
710 float pickup_y = 0.0f;
712
713 if(!have_pickup)
714 {
716 return;
717 }
718
719 const float dx = previous ? (current->x - previous->x) : 0.0f;
720 const float dy = previous ? (current->y - previous->y) : 0.0f;
721 const float travel = hypotf(dx, dy);
722 if(travel <= 1e-6f) return;
723
724 const float radius = fmaxf(current->radius, 0.5f);
725 const float response = 1.0f - expf(-0.5f * travel / radius);
726 pickup_x = _lerpf(pickup_x, current->x, response);
727 pickup_y = _lerpf(pickup_y, current->y, response);
729}
730
732 const float distance_percent,
733 const dt_drawlayer_cache_patch_t *sample_patch,
735 const float scale,
736 dt_drawlayer_cache_patch_t *stroke_mask,
737 dt_drawlayer_damaged_rect_t *runtime_state,
738 dt_drawlayer_paint_stroke_t *runtime_private)
739{
740 /* Backend helper for raster path replay:
741 * keep a tiny dab window, compute spacing normalization, rasterize one sample. */
742 if(IS_NULL_PTR(dab) || !runtime_private || !runtime_private->dab_window) return FALSE;
743
744 GArray *const history = runtime_private->dab_window;
745 if(dab->stroke_pos == DT_DRAWLAYER_PAINT_STROKE_FIRST) g_array_set_size(history, 0);
746 g_array_append_val(history, *dab);
747
748 const int total = (int)history->len;
749 const int count = MIN(total, 3);
751 memcpy(window, ((dt_drawlayer_brush_dab_t *)history->data) + (total - count),
752 (size_t)count * sizeof(dt_drawlayer_brush_dab_t));
753
754 const dt_drawlayer_brush_dab_t *sample = &window[count - 1];
755 const dt_drawlayer_brush_dab_t *previous_sample = (count > 1) ? &window[count - 2] : NULL;
756 const float spacing = (sample->sample_spacing > 1e-6f)
757 ? sample->sample_spacing
758 : _paint_dab_sample_spacing(sample, _clamp01(distance_percent));
759 const float sample_opacity_scale = (sample->sample_opacity_scale > 1e-6f)
760 ? sample->sample_opacity_scale
761 : _paint_stroke_sample_opacity_scale(sample, spacing);
762
763 if(count == 1 && runtime_private)
764 dt_drawlayer_paint_runtime_set_smudge_pickup(runtime_private, 0.0f, 0.0f, FALSE);
765
766 if(!IS_NULL_PTR(runtime_private))
767 {
769 {
770 if(!IS_NULL_PTR(previous_sample)) _advance_smudge_pickup_state(runtime_private, sample, previous_sample);
771 }
772 else
773 dt_drawlayer_paint_runtime_set_smudge_pickup(runtime_private, 0.0f, 0.0f, FALSE);
774 }
775
776 /* The clock is read only when the trace that consumes it is on. Two `dt_get_wtime()` per dab
777 * is two vDSO calls on the hot path at 3000 dabs/s, paid for a string nobody asked for. */
778 const gboolean trace_timing = (dt_get_debug_flags() & DT_DEBUG_VERBOSE) != 0;
779 const double t0 = trace_timing ? dt_get_wtime() : 0.0;
780 const gboolean rasterized
781 = dt_drawlayer_brush_rasterize(sample_patch, patch, scale, sample, sample_opacity_scale, stroke_mask,
782 runtime_private);
783 const double t1 = trace_timing ? dt_get_wtime() : 0.0;
784 if(rasterized && !IS_NULL_PTR(runtime_state) && !IS_NULL_PTR(runtime_private) && runtime_private->bounds.valid)
785 dt_drawlayer_paint_runtime_note_dab_damage(runtime_state, &runtime_private->bounds);
786
787 if(trace_timing)
788 {
789 if(!IS_NULL_PTR(runtime_private) && runtime_private->bounds.valid)
790 {
791 const int bounds_w = runtime_private->bounds.se[0] - runtime_private->bounds.nw[0];
792 const int bounds_h = runtime_private->bounds.se[1] - runtime_private->bounds.nw[1];
794 "[drawlayer] paint raster mode=%d pos=%d spacing=%.3f alpha_scale=%.4f area=%dx%d ms=%.3f\n",
795 sample->mode, sample->stroke_pos, spacing, sample_opacity_scale, bounds_w, bounds_h,
796 1000.0 * (t1 - t0));
797 }
798 else
800 "[drawlayer] paint raster mode=%d pos=%d spacing=%.3f alpha_scale=%.4f area=0x0 ms=%.3f\n",
801 sample->mode, sample->stroke_pos, spacing, sample_opacity_scale, 1000.0 * (t1 - t0));
802 }
803
804 if(history->len > 3) g_array_remove_range(history, 0, history->len - 3);
805 return TRUE;
806}
807
815
821
823{
824 if(IS_NULL_PTR(state)) return;
825 state->valid = FALSE;
826 state->nw[0] = 0;
827 state->nw[1] = 0;
828 state->se[0] = 0;
829 state->se[1] = 0;
830}
831
839
841{
842 if(IS_NULL_PTR(state) || !*state) return;
843 if((*state)->raw_inputs) g_array_free((*state)->raw_inputs, TRUE);
844 dt_free((*state)->smudge_pixels);
845 dt_free(*state);
846}
847
849{
850 /* Reset per-stroke transient payload while preserving reusable allocations. */
851 if(IS_NULL_PTR(state)) return;
852 state->smudge_pickup_x = 0.0f;
853 state->smudge_pickup_y = 0.0f;
854 state->have_smudge_pickup = FALSE;
855 if(state->smudge_pixels && state->smudge_width > 0 && state->smudge_height > 0)
856 memset(state->smudge_pixels, 0, (size_t)state->smudge_width * state->smudge_height * 4 * sizeof(float));
857}
858
860{
861 if(IS_NULL_PTR(state)) return;
862 state->stroke_seed = seed;
863}
864
869
871 const int height)
872{
873 if(IS_NULL_PTR(state) || width <= 0 || height <= 0) return FALSE;
874 if(state->smudge_width == width && state->smudge_height == height && state->smudge_pixels) return TRUE;
875
876 float *pixels = g_realloc(state->smudge_pixels, (size_t)width * height * 4 * sizeof(float));
877 if(IS_NULL_PTR(pixels)) return FALSE;
878 state->smudge_pixels = pixels;
879 state->smudge_width = width;
880 state->smudge_height = height;
881 memset(state->smudge_pixels, 0, (size_t)width * height * 4 * sizeof(float));
882 return TRUE;
883}
884
886{
887 return state ? state->smudge_pixels : NULL;
888}
889
891{
892 return state ? state->smudge_width : 0;
893}
894
896{
897 return state && state->have_smudge_pickup;
898}
899
901 float *x, float *y)
902{
903 if(!IS_NULL_PTR(x)) *x = state ? state->smudge_pickup_x : 0.0f;
904 if(!IS_NULL_PTR(y)) *y = state ? state->smudge_pickup_y : 0.0f;
905}
906
908 const float x, const float y, const gboolean have_pickup)
909{
910 if(IS_NULL_PTR(state)) return;
911 state->smudge_pickup_x = x;
912 state->smudge_pickup_y = y;
913 state->have_smudge_pickup = have_pickup;
914}
915
917 const dt_drawlayer_brush_dab_t *dab,
918 const int width, const int height,
919 const int origin_x, const int origin_y,
920 const float scale)
921{
922 /* Compute current dab footprint in target buffer coordinates. */
923 if(IS_NULL_PTR(state) || IS_NULL_PTR(dab) || dab->radius <= 0.0f || dab->opacity <= 0.0f || scale <= 0.0f) return FALSE;
924 const float support_radius = dab->radius;
925
926 state->bounds.valid = TRUE;
927 state->bounds.nw[0] = MAX(0, (int)floorf((dab->x - support_radius) * scale) - origin_x);
928 state->bounds.nw[1] = MAX(0, (int)floorf((dab->y - support_radius) * scale) - origin_y);
929 state->bounds.se[0] = MIN(width, (int)ceilf((dab->x + support_radius) * scale) - origin_x + 1);
930 state->bounds.se[1] = MIN(height, (int)ceilf((dab->y + support_radius) * scale) - origin_y + 1);
931 if(state->bounds.se[0] <= state->bounds.nw[0]
932 || state->bounds.se[1] <= state->bounds.nw[1])
933 return FALSE;
934 return TRUE;
935}
936
938 const dt_drawlayer_damaged_rect_t *dab_rect)
939{
940 if(IS_NULL_PTR(state)) return;
941 if(IS_NULL_PTR(dab_rect) || !dab_rect->valid) return;
942 if(dab_rect->se[0] <= dab_rect->nw[0] || dab_rect->se[1] <= dab_rect->nw[1]) return;
943 if(!state->valid)
944 {
945 state->valid = TRUE;
946 *state = *dab_rect;
947 return;
948 }
949 state->nw[0] = MIN(state->nw[0], dab_rect->nw[0]);
950 state->nw[1] = MIN(state->nw[1], dab_rect->nw[1]);
951 state->se[0] = MAX(state->se[0], dab_rect->se[0]);
952 state->se[1] = MAX(state->se[1], dab_rect->se[1]);
953}
954
957{
958 if(IS_NULL_PTR(state) || !state->valid) return FALSE;
959 if(!IS_NULL_PTR(out_rect)) *out_rect = *state;
960 return TRUE;
961}
962
964 const dt_drawlayer_damaged_rect_t *add_rect)
965{
966 if(IS_NULL_PTR(rect)) return;
967 if(IS_NULL_PTR(add_rect) || !add_rect->valid) return;
968 if(add_rect->se[0] <= add_rect->nw[0] || add_rect->se[1] <= add_rect->nw[1]) return;
969
970 if(!rect->valid)
971 {
972 *rect = *add_rect;
973 return;
974 }
975
976 rect->nw[0] = MIN(rect->nw[0], add_rect->nw[0]);
977 rect->nw[1] = MIN(rect->nw[1], add_rect->nw[1]);
978 rect->se[0] = MAX(rect->se[0], add_rect->se[0]);
979 rect->se[1] = MAX(rect->se[1], add_rect->se[1]);
980}
981
983 dt_drawlayer_damaged_rect_t *target_rect)
984{
985 if(IS_NULL_PTR(path_state) || IS_NULL_PTR(target_rect)) return FALSE;
986
987 dt_drawlayer_damaged_rect_t add = { 0 };
988 const gboolean have_damage = dt_drawlayer_paint_runtime_get_stroke_damage(path_state, &add);
990 if(!have_damage) return FALSE;
991
992 _paint_union_damage_rect(target_rect, &add);
993 return TRUE;
994}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
Definition brush.h:53
@ DT_DRAWLAYER_BRUSH_SHAPE_GAUSSIAN
Definition brush.h:42
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.
static void _enforce_dab_center_spacing(dt_drawlayer_paint_stroke_t *state, dt_drawlayer_brush_dab_t *dab, const float sample_spacing)
Re-project current dab center to exact target spacing from previous dab.
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 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.
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.
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 void _freeze_emitted_dab_raster_state(dt_drawlayer_paint_stroke_t *state, dt_drawlayer_brush_dab_t *dab, const float sample_spacing)
Freeze raster-time normalization into one emitted dab record.
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.
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)
Apply optional quadratic smoothing to one emitted dab.
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.
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
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t int int int int * origin_y
Definition layers.h:52
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t int int int * origin_x
Definition layers.h:51
@ 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
#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:66
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
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