Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
stroke_raster.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
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
20
21#include <float.h>
22#include <limits.h>
23#include <math.h>
24#include <stdint.h>
25#include <string.h>
26
27/* This file holds no state of its own: src/widgets keeps none outside its two registries, and
28 * a rasteriser has no business remembering anything between two strokes. What it needs across
29 * a call -- a scratch plane -- and across a frame -- what was touched -- lives on the surface
30 * being painted, as cairo user data, and dies with it. */
31
32/* ---------------------------------------------------------------------------------------------
33 * The touched rectangle, kept on the surface it describes.
34 *
35 * A surface carries its own record of what was painted into it here, so a caller that owns the
36 * surface can composite or clear exactly that much: the rasteriser is the one thing that knows
37 * where its pixels went. Half-open pixel ranges, [x0, x1) x [y0, y1). */
38typedef struct _touched_t
39{
40 gboolean any;
41 int x0;
42 int y0;
43 int x1;
44 int y1;
46
47static const cairo_user_data_key_t _touched_key = { 0 };
48
49static _touched_t *_touched_of(cairo_surface_t *surface, const gboolean create)
50{
51 _touched_t *touched = (_touched_t *)cairo_surface_get_user_data(surface, &_touched_key);
52 if(touched || !create) return touched;
53 touched = g_malloc0(sizeof(_touched_t));
54 if(cairo_surface_set_user_data(surface, &_touched_key, touched, g_free) != CAIRO_STATUS_SUCCESS)
55 {
56 g_free(touched);
57 return NULL;
58 }
59 return touched;
60}
61
62static void _touched_add(cairo_surface_t *surface, const int x0, const int y0, const int x1, const int y1)
63{
64 if(x1 <= x0 || y1 <= y0) return;
65 _touched_t *touched = _touched_of(surface, TRUE);
66 if(!touched) return;
67 if(!touched->any)
68 {
69 touched->x0 = x0;
70 touched->y0 = y0;
71 touched->x1 = x1;
72 touched->y1 = y1;
73 touched->any = TRUE;
74 return;
75 }
76 touched->x0 = MIN(touched->x0, x0);
77 touched->y0 = MIN(touched->y0, y0);
78 touched->x1 = MAX(touched->x1, x1);
79 touched->y1 = MAX(touched->y1, y1);
80}
81
82gboolean dt_stroke_raster_touched(cairo_surface_t *surface, cairo_rectangle_int_t *touched)
83{
84 if(!surface || !touched) return FALSE;
85 const _touched_t *record = _touched_of(surface, FALSE);
86 if(!record || !record->any) return FALSE;
87 touched->x = record->x0;
88 touched->y = record->y0;
89 touched->width = record->x1 - record->x0;
90 touched->height = record->y1 - record->y0;
91 return TRUE;
92}
93
94void dt_stroke_raster_touched_reset(cairo_surface_t *surface)
95{
96 if(!surface) return;
97 _touched_t *record = _touched_of(surface, FALSE);
98 if(record) record->any = FALSE;
99}
100
101gboolean dt_stroke_raster_can_paint(cairo_surface_t *surface)
102{
103 return surface && cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS
104 && cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE
105 && cairo_image_surface_get_format(surface) == CAIRO_FORMAT_ARGB32;
106}
107
108/* ---------------------------------------------------------------------------------------------
109 * The distance plane, and the scratch it is cut from.
110 *
111 * One plane over the polyline's bounding box, holding per pixel not the distance but
112 * `reach^2 - d^2', where reach is the widest pass's half-width plus the antialiasing pixel:
113 * positive inside the stroke's reach, zero outside and for anything never stamped. Zero being
114 * the resting state is what makes the scratch cheap to keep: it is never cleared as a whole,
115 * only the spans a stroke actually wrote are zeroed again once composited, and memory that
116 * only ever grows starts every new byte at zero. Stamping is MAX, which for this quantity is
117 * the nearest point of the polyline, and squared distances need no square root until
118 * compositing, which touches only the pixels of the stroke's band and not the box around it.
119 *
120 * Per row, the extent that was written, so compositing and the clearing after it walk the band
121 * rather than the box. The scratch belongs to the surface, grows to the largest box that
122 * surface ever asked for, and is freed with it. */
123typedef struct _scratch_t
124{
125 float *value;
126 size_t count;
129 int rows;
131
132static const cairo_user_data_key_t _scratch_key = { 0 };
133
134static void _scratch_free(void *data)
135{
136 _scratch_t *scratch = (_scratch_t *)data;
137 if(!scratch) return;
138 g_free(scratch->value);
139 g_free(scratch->span_min);
140 g_free(scratch->span_max);
141 g_free(scratch);
142}
143
144static _scratch_t *_scratch_of(cairo_surface_t *surface)
145{
146 _scratch_t *scratch = (_scratch_t *)cairo_surface_get_user_data(surface, &_scratch_key);
147 if(scratch) return scratch;
148 scratch = g_malloc0(sizeof(_scratch_t));
149 if(cairo_surface_set_user_data(surface, &_scratch_key, scratch, _scratch_free) != CAIRO_STATUS_SUCCESS)
150 {
151 g_free(scratch);
152 return NULL;
153 }
154 return scratch;
155}
156
157typedef struct _plane_t
158{
159 float *value; /* reach^2 - d^2, zero at rest */
160 int *span_min; /* per row of the box: first column written, or INT_MAX */
161 int *span_max; /* per row of the box: last column written, or -1 */
162 int width; /* the box's, in pixels */
164 int x0; /* the box's origin in the surface's pixel grid */
165 int y0;
167
168/* Cut a plane for a box of @p width x @p height from the surface's scratch, growing it --
169 * zeroed -- as needed. */
170static gboolean _plane_acquire(cairo_surface_t *surface, _plane_t *plane, const int x0, const int y0,
171 const int width, const int height)
172{
173 _scratch_t *scratch = _scratch_of(surface);
174 if(!scratch) return FALSE;
175 const size_t count = (size_t)width * (size_t)height;
176 if(count > scratch->count)
177 {
178 float *grown = g_try_malloc0(count * sizeof(float));
179 if(!grown) return FALSE;
180 g_free(scratch->value);
181 scratch->value = grown;
182 scratch->count = count;
183 }
184 if(height > scratch->rows)
185 {
186 int *min_grown = g_try_malloc(sizeof(int) * (size_t)height);
187 int *max_grown = g_try_malloc(sizeof(int) * (size_t)height);
188 if(!min_grown || !max_grown)
189 {
190 g_free(min_grown);
191 g_free(max_grown);
192 return FALSE;
193 }
194 g_free(scratch->span_min);
195 g_free(scratch->span_max);
196 scratch->span_min = min_grown;
197 scratch->span_max = max_grown;
198 scratch->rows = height;
199 }
200 for(int y = 0; y < height; y++)
201 {
202 scratch->span_min[y] = INT_MAX;
203 scratch->span_max[y] = -1;
204 }
205 plane->value = scratch->value;
206 plane->span_min = scratch->span_min;
207 plane->span_max = scratch->span_max;
208 plane->width = width;
209 plane->height = height;
210 plane->x0 = x0;
211 plane->y0 = y0;
212 return TRUE;
213}
214
215/* ---------------------------------------------------------------------------------------------
216 * Stamping: the capsule of a segment. */
217
218/* One segment of a polyline, in surface pixels, with what its ends are: a capped end is round,
219 * an uncapped one is cut flat at the segment's end plane, which is what a butt cap is. Every
220 * end inside a polyline is capped, so consecutive segments join round and seamless. */
221typedef struct _segment_t
222{
223 double ax;
224 double ay;
225 double bx;
226 double by;
227 gboolean cap_a;
228 gboolean cap_b;
230
231/* One row of the capsule: every pixel of [x_first, x_last] within reach of the segment takes
232 * the nearest distance. Returns whether any did. */
233static inline gboolean _plane_stamp_row(_plane_t *const plane, const int y, const int x_first, const int x_last,
234 const _segment_t *const seg, const double reach2)
235{
236 const double dx = seg->bx - seg->ax;
237 const double dy = seg->by - seg->ay;
238 const double len2 = dx * dx + dy * dy;
239 const double py = (double)(y + plane->y0) + 0.5;
240 float *const row = plane->value + (size_t)y * plane->width;
241 gboolean wrote = FALSE;
242 for(int x = x_first; x <= x_last; x++)
243 {
244 const double px = (double)(x + plane->x0) + 0.5;
245 double t = (len2 > 0.0) ? ((px - seg->ax) * dx + (py - seg->ay) * dy) / len2 : 0.0;
246 if(t < 0.0 && !seg->cap_a) continue;
247 if(t > 1.0 && !seg->cap_b) continue;
248 t = CLAMP(t, 0.0, 1.0);
249 const double ex = seg->ax + t * dx - px;
250 const double ey = seg->ay + t * dy - py;
251 const double d2 = ex * ex + ey * ey;
252 if(d2 >= reach2) continue;
253 const float inside = (float)(reach2 - d2);
254 if(inside <= row[x]) continue;
255 row[x] = inside;
256 wrote = TRUE;
257 }
258 return wrote;
259}
260
261/* Stamp the capsule of half-width @p reach around @p seg into the plane. */
262static inline void _plane_stamp_capsule(_plane_t *const plane, const _segment_t *const seg, const double reach)
263{
264 const double reach2 = reach * reach;
265 const int x_first = MAX((int)floor(MIN(seg->ax, seg->bx) - reach) - plane->x0, 0);
266 const int x_last = MIN((int)ceil(MAX(seg->ax, seg->bx) + reach) - plane->x0, plane->width - 1);
267 const int y_first = MAX((int)floor(MIN(seg->ay, seg->by) - reach) - plane->y0, 0);
268 const int y_last = MIN((int)ceil(MAX(seg->ay, seg->by) + reach) - plane->y0, plane->height - 1);
269 if(x_first > x_last || y_first > y_last) return;
270
271 for(int y = y_first; y <= y_last; y++)
272 {
273 if(!_plane_stamp_row(plane, y, x_first, x_last, seg, reach2)) continue;
274 plane->span_min[y] = MIN(plane->span_min[y], x_first);
275 plane->span_max[y] = MAX(plane->span_max[y], x_last);
276 }
277}
278
279/* ---------------------------------------------------------------------------------------------
280 * The walk along a polyline: dashes cut by arc length, capsules stamped. */
281
282/* Where the walk is in the dash pattern. */
283typedef struct _dash_t
284{
285 gboolean on; /* inside a dash rather than a gap */
286 double left; /* what remains of the current dash or gap */
287 gboolean fresh; /* the current dash started at a cut, not at the line's start */
289
290/* The dashed pieces of one segment. A piece that starts at a cut, or ends at one, gets the
291 * cap style's end there; one that continues from or into the neighbouring segment is a join,
292 * always capped so the two halves meet round. */
293static void _plane_stamp_dashed(_plane_t *const plane, const _segment_t *const seg, const dt_stroke_style_t *style,
294 const double reach, _dash_t *const dash)
295{
296 const double length = hypot(seg->bx - seg->ax, seg->by - seg->ay);
297 const gboolean caps = style->round_caps;
298 double pos = 0.0;
299 gboolean piece_starts_at_cut = dash->fresh; /* the line's own start counts as a cut */
300 while(pos < length)
301 {
302 const double run = MIN(dash->left, length - pos);
303 const double end = pos + run;
304 if(dash->on)
305 {
306 const double t0 = pos / length;
307 const double t1 = end / length;
308 const gboolean ends_at_cut = (end < length) || seg->cap_b == FALSE;
309 const _segment_t piece = { .ax = seg->ax + t0 * (seg->bx - seg->ax),
310 .ay = seg->ay + t0 * (seg->by - seg->ay),
311 .bx = seg->ax + t1 * (seg->bx - seg->ax),
312 .by = seg->ay + t1 * (seg->by - seg->ay),
313 .cap_a = caps || !(piece_starts_at_cut || (pos == 0.0 && !seg->cap_a)),
314 .cap_b = caps || !ends_at_cut };
315 _plane_stamp_capsule(plane, &piece, reach);
316 }
317 dash->left -= run;
318 pos = end;
319 if(dash->left <= 0.0)
320 {
321 dash->on = !dash->on;
322 dash->left = dash->on ? style->dash_on : style->dash_off;
323 piece_starts_at_cut = TRUE;
324 }
325 else
326 piece_starts_at_cut = FALSE;
327 }
328 /* the next segment continues whatever this one was in, unless a cut fell exactly at its end */
329 dash->fresh = piece_starts_at_cut;
330}
331
332static void _polyline_stamp(_plane_t *const plane, const double *xy, const int count, const dt_stroke_style_t *style,
333 const double reach, const gboolean closed, _dash_t *const dash)
334{
335 const gboolean caps = style->round_caps;
336 if(count == 1)
337 {
338 if(!caps) return;
339 const _segment_t dot = { xy[0], xy[1], xy[0], xy[1], TRUE, TRUE };
340 _plane_stamp_capsule(plane, &dot, reach);
341 return;
342 }
343 const gboolean dashed = style->dash_on > 0.0 && style->dash_off > 0.0;
344 const int last = count - 1;
345 for(int i = 0; i < last; i++)
346 {
347 /* the polyline's own two ends take the cap style; a closed one has none */
348 const gboolean starts_line = (i == 0) && !closed;
349 const gboolean ends_line = (i == last - 1) && !closed;
350 const _segment_t seg = { .ax = xy[2 * i],
351 .ay = xy[2 * i + 1],
352 .bx = xy[2 * i + 2],
353 .by = xy[2 * i + 3],
354 .cap_a = caps || !starts_line,
355 .cap_b = caps || !ends_line };
356 if(dashed)
357 _plane_stamp_dashed(plane, &seg, style, reach, dash);
358 else
359 _plane_stamp_capsule(plane, &seg, reach);
360 }
361}
362
363/* The dash pattern at the start of a stroke: a dash, with its whole length ahead. */
364static _dash_t _dash_start(const dt_stroke_style_t *const style)
365{
366 const _dash_t dash = { .on = TRUE, .left = style->dash_on, .fresh = TRUE };
367 return dash;
368}
369
370/* ---------------------------------------------------------------------------------------------
371 * Compositing: the two passes from the distance, premultiplied OVER, into ARGB32.
372 *
373 * ARGB32 is one native-endian uint32 per pixel, alpha in the top byte, colour premultiplied by
374 * alpha. The dark pass goes first and the bright pass over it, each with coverage
375 * clamp(R + 1/2 - d, 0, 1) for its own half-width R -- the one-pixel ramp of a fast antialiased
376 * cairo stroke. A pass with alpha or width at zero contributes nothing. */
377typedef struct _composite_t
378{
379 const dt_stroke_pass_t *dark; /* NULL when the pass contributes nothing */
381 double reach2;
382 double half_dark;
384 uint8_t *data;
389
390static inline uint32_t _pixel_pack(const double a, const double r, const double g, const double b)
391{
392 const uint32_t ia = (uint32_t)(a * 255.0 + 0.5);
393 const uint32_t ir = (uint32_t)(r * 255.0 + 0.5);
394 const uint32_t ig = (uint32_t)(g * 255.0 + 0.5);
395 const uint32_t ib = (uint32_t)(b * 255.0 + 0.5);
396 return (ia << 24) | (ir << 16) | (ig << 8) | ib;
397}
398
399static inline void _pixel_over(uint32_t *const pixel, const dt_stroke_pass_t *const pass, const double coverage)
400{
401 const double src_a = pass->alpha * coverage;
402 if(src_a <= 0.0) return;
403 const uint32_t dst = *pixel;
404 const double keep = 1.0 - src_a;
405 const double a = src_a + ((dst >> 24) & 0xff) / 255.0 * keep;
406 const double r = pass->red * src_a + ((dst >> 16) & 0xff) / 255.0 * keep;
407 const double g = pass->green * src_a + ((dst >> 8) & 0xff) / 255.0 * keep;
408 const double b = pass->blue * src_a + (dst & 0xff) / 255.0 * keep;
409 *pixel = _pixel_pack(MIN(a, 1.0), MIN(r, 1.0), MIN(g, 1.0), MIN(b, 1.0));
410}
411
412/* One row's span: composite what was stamped, and widen the touched range [tx0, tx1) to it.
413 * Returns whether any pixel was painted. */
414static inline gboolean _composite_row(const _plane_t *const plane, const int y, const _composite_t *const c,
415 int *const tx0, int *const tx1)
416{
417 const int sy = y + plane->y0;
418 if(sy < 0 || sy >= c->surface_height) return FALSE;
419 const float *const row = plane->value + (size_t)y * plane->width;
420 uint32_t *const pixels = (uint32_t *)(c->data + (size_t)sy * c->stride);
421 gboolean painted = FALSE;
422 for(int x = plane->span_min[y]; x <= plane->span_max[y]; x++)
423 {
424 const float inside = row[x];
425 if(inside <= 0.0f) continue;
426 const int sx = x + plane->x0;
427 if(sx < 0 || sx >= c->surface_width) continue;
428 const double d = sqrt(MAX(c->reach2 - (double)inside, 0.0));
429 if(c->dark) _pixel_over(&pixels[sx], c->dark, CLAMP(c->half_dark + 0.5 - d, 0.0, 1.0));
430 if(c->bright) _pixel_over(&pixels[sx], c->bright, CLAMP(c->half_bright + 0.5 - d, 0.0, 1.0));
431 *tx0 = MIN(*tx0, sx);
432 *tx1 = MAX(*tx1, sx + 1);
433 painted = TRUE;
434 }
435 return painted;
436}
437
438static void _plane_composite_and_clear(_plane_t *const plane, cairo_surface_t *surface, const dt_stroke_style_t *style,
439 const double reach)
440{
441 cairo_surface_flush(surface);
442 const gboolean with_dark = style->dark.width > 0.0 && style->dark.alpha > 0.0;
443 const gboolean with_bright = style->bright.width > 0.0 && style->bright.alpha > 0.0;
444 const _composite_t c = { .dark = with_dark ? &style->dark : NULL,
445 .bright = with_bright ? &style->bright : NULL,
446 .reach2 = reach * reach,
447 .half_dark = 0.5 * style->dark.width,
448 .half_bright = 0.5 * style->bright.width,
449 .data = cairo_image_surface_get_data(surface),
450 .stride = cairo_image_surface_get_stride(surface),
451 .surface_width = cairo_image_surface_get_width(surface),
452 .surface_height = cairo_image_surface_get_height(surface) };
453
454 int tx0 = INT_MAX;
455 int tx1 = -1;
456 int ty0 = INT_MAX;
457 int ty1 = -1;
458 for(int y = 0; y < plane->height; y++)
459 {
460 if(plane->span_max[y] < plane->span_min[y]) continue;
461 if(_composite_row(plane, y, &c, &tx0, &tx1))
462 {
463 ty0 = MIN(ty0, y + plane->y0);
464 ty1 = MAX(ty1, y + plane->y0 + 1);
465 }
466 /* back to rest: only what was written */
467 memset(plane->value + (size_t)y * plane->width + plane->span_min[y], 0,
468 sizeof(float) * (size_t)(plane->span_max[y] - plane->span_min[y] + 1));
469 }
470
471 if(tx1 > tx0 && ty1 > ty0)
472 {
473 cairo_surface_mark_dirty_rectangle(surface, tx0, ty0, tx1 - tx0, ty1 - ty0);
474 _touched_add(surface, tx0, ty0, tx1, ty1);
475 }
476}
477
478/* ---------------------------------------------------------------------------------------------
479 * A polyline, and a path's worth of them. */
480
481/* Stroke one polyline; @p dash is the pattern's state, carried across the sub-paths of one
482 * stroke so that a dash is a function of the arc length along everything drawn and not of
483 * where a sub-path happened to start. An outline is many sub-paths -- one per run between the
484 * stretches the boundary pass hides -- and restarting the pattern at each bunched and stretched
485 * the dashes at every run boundary. */
486static gboolean _stroke_polyline(cairo_surface_t *surface, const double *xy, const int count,
487 const dt_stroke_style_t *style, const gboolean closed, _dash_t *const dash)
488{
489 if(count < 1) return FALSE;
490 const double widest = MAX(style->dark.width, style->bright.width);
491 if(widest <= 0.0) return FALSE;
492 const double reach = 0.5 * widest + 1.0; /* the antialiasing pixel beyond the widest pass */
493
494 double x_min = DBL_MAX;
495 double y_min = DBL_MAX;
496 double x_max = -DBL_MAX;
497 double y_max = -DBL_MAX;
498 for(int i = 0; i < count; i++)
499 {
500 x_min = MIN(x_min, xy[2 * i]);
501 x_max = MAX(x_max, xy[2 * i]);
502 y_min = MIN(y_min, xy[2 * i + 1]);
503 y_max = MAX(y_max, xy[2 * i + 1]);
504 }
505 if(!isfinite(x_min) || !isfinite(x_max) || !isfinite(y_min) || !isfinite(y_max)) return FALSE;
506
507 /* the box: the polyline's, grown by the reach, clipped to the surface */
508 const int surface_width = cairo_image_surface_get_width(surface);
509 const int surface_height = cairo_image_surface_get_height(surface);
510 const int x0 = MAX((int)floor(x_min - reach) - 1, 0);
511 const int y0 = MAX((int)floor(y_min - reach) - 1, 0);
512 const int x1 = MIN((int)ceil(x_max + reach) + 1, surface_width - 1);
513 const int y1 = MIN((int)ceil(y_max + reach) + 1, surface_height - 1);
514 if(x1 < x0 || y1 < y0) return FALSE; /* entirely off the surface */
515
516 _plane_t plane;
517 if(!_plane_acquire(surface, &plane, x0, y0, x1 - x0 + 1, y1 - y0 + 1)) return FALSE;
518 _polyline_stamp(&plane, xy, count, style, reach, closed, dash);
519 _plane_composite_and_clear(&plane, surface, style, reach);
520 return TRUE;
521}
522
523gboolean dt_stroke_raster_polyline(cairo_surface_t *surface, const double *xy, int count,
524 const dt_stroke_style_t *style)
525{
526 if(!dt_stroke_raster_can_paint(surface) || !xy || !style || count < 1) return FALSE;
527 _dash_t dash = _dash_start(style);
528 return _stroke_polyline(surface, xy, count, style, FALSE, &dash);
529}
530
531/* How much cr's matrix scales a length, taken as the geometric mean of the two axes so an
532 * anisotropic matrix -- which no overlay has -- degrades gracefully. */
533static double _matrix_scale(cairo_t *cr)
534{
535 double ax = 1.0;
536 double ay = 0.0;
537 double bx = 0.0;
538 double by = 1.0;
539 cairo_user_to_device_distance(cr, &ax, &ay);
540 cairo_user_to_device_distance(cr, &bx, &by);
541 const double scale = sqrt(hypot(ax, ay) * hypot(bx, by));
542 return isfinite(scale) ? scale : 1.0;
543}
544
545/* The polyline being gathered from a path: its vertices in surface pixels, and its first one
546 * for a close.
547 *
548 * Cairo's "device space" is not the pixel grid. It is the space the CTM maps user space into,
549 * and the surface then applies its own device transform: pixel = device * device_scale +
550 * device_offset, the scale being what a HiDPI widget carries (2 on a 2x screen) and the offset
551 * what a pushed group carries (minus its clip's origin, in pixels). cairo_user_to_device()
552 * stops at device space -- measured: on a surface with device scale 2, (10, 10) maps to
553 * (10, 10) -- so both factors are applied here. Leaving the scale out put every overlay at half
554 * size in the top-left quadrant of a HiDPI view. */
555typedef struct _gather_t
556{
557 GArray *vertices;
558 double first_x;
559 double first_y;
560 gboolean closed;
561 double scale_x; /* the surface's device scale */
562 double scale_y;
563 double offset_x; /* the surface's device offset, in pixels */
564 double offset_y;
566
567static void _gather_flush(_gather_t *const g, cairo_surface_t *surface, const dt_stroke_style_t *style,
568 _dash_t *const dash)
569{
570 if(g->vertices->len >= 2)
571 _stroke_polyline(surface, (const double *)g->vertices->data, (int)(g->vertices->len / 2), style, g->closed, dash);
572 g_array_set_size(g->vertices, 0);
573 g->closed = FALSE;
574}
575
576static void _gather_point(_gather_t *const g, cairo_t *cr, const cairo_path_data_t *const point)
577{
578 double x = point->point.x;
579 double y = point->point.y;
580 cairo_user_to_device(cr, &x, &y);
581 x = x * g->scale_x + g->offset_x;
582 y = y * g->scale_y + g->offset_y;
583 if(g->vertices->len == 0)
584 {
585 g->first_x = x;
586 g->first_y = y;
587 }
588 g_array_append_val(g->vertices, x);
589 g_array_append_val(g->vertices, y);
590}
591
592gboolean dt_stroke_raster_path(cairo_t *cr, const dt_stroke_style_t *style)
593{
594 if(!cr || !style) return FALSE;
595 cairo_surface_t *surface = cairo_get_group_target(cr);
596 if(!dt_stroke_raster_can_paint(surface)) return FALSE;
597
598 cairo_path_t *path = cairo_copy_path_flat(cr);
599 if(!path || path->status != CAIRO_STATUS_SUCCESS)
600 {
601 if(path) cairo_path_destroy(path);
602 return FALSE;
603 }
604
605 /* the surface's pixel grid is device space scaled by the surface's device scale and shifted
606 * by its device offset: for the group cairo pushed, that is the clip's origin */
607 _gather_t gather = { .vertices = g_array_sized_new(FALSE, FALSE, sizeof(double), 2 * 1024),
608 .scale_x = 1.0,
609 .scale_y = 1.0 };
610 cairo_surface_get_device_scale(surface, &gather.scale_x, &gather.scale_y);
611 cairo_surface_get_device_offset(surface, &gather.offset_x, &gather.offset_y);
612 if(gather.scale_x <= 0.0) gather.scale_x = 1.0;
613 if(gather.scale_y <= 0.0) gather.scale_y = 1.0;
614
615 const double scale = _matrix_scale(cr) * sqrt(gather.scale_x * gather.scale_y);
616 dt_stroke_style_t device_style = *style;
617 device_style.dark.width *= scale;
618 device_style.bright.width *= scale;
619 device_style.dash_on *= scale;
620 device_style.dash_off *= scale;
621 _dash_t dash = _dash_start(&device_style); /* one pattern for the whole path */
622
623 for(int i = 0; i < path->num_data; i += path->data[i].header.length)
624 {
625 const cairo_path_data_t *const element = &path->data[i];
626 switch(element->header.type)
627 {
628 case CAIRO_PATH_MOVE_TO:
629 _gather_flush(&gather, surface, &device_style, &dash);
630 _gather_point(&gather, cr, &element[1]);
631 break;
632 case CAIRO_PATH_LINE_TO:
633 _gather_point(&gather, cr, &element[1]);
634 break;
635 case CAIRO_PATH_CLOSE_PATH:
636 if(gather.vertices->len >= 2)
637 {
638 g_array_append_val(gather.vertices, gather.first_x);
639 g_array_append_val(gather.vertices, gather.first_y);
640 gather.closed = TRUE;
641 }
642 break;
643 default:
644 break; /* a flattened path has no curves */
645 }
646 }
647 _gather_flush(&gather, surface, &device_style, &dash);
648
649 g_array_free(gather.vertices, TRUE);
650 cairo_path_destroy(path);
651 cairo_new_path(cr);
652 return TRUE;
653}
654
655// clang-format off
656// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
657// vim: shiftwidth=2 expandtab tabstop=2 cindent
658// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
659// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const float x
const int t
static const int row
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
const float r
static _dash_t _dash_start(const dt_stroke_style_t *const style)
static void _plane_composite_and_clear(_plane_t *const plane, cairo_surface_t *surface, const dt_stroke_style_t *style, const double reach)
static void _pixel_over(uint32_t *const pixel, const dt_stroke_pass_t *const pass, const double coverage)
static gboolean _plane_acquire(cairo_surface_t *surface, _plane_t *plane, const int x0, const int y0, const int width, const int height)
static void _polyline_stamp(_plane_t *const plane, const double *xy, const int count, const dt_stroke_style_t *style, const double reach, const gboolean closed, _dash_t *const dash)
static void _plane_stamp_dashed(_plane_t *const plane, const _segment_t *const seg, const dt_stroke_style_t *style, const double reach, _dash_t *const dash)
static void _touched_add(cairo_surface_t *surface, const int x0, const int y0, const int x1, const int y1)
void dt_stroke_raster_touched_reset(cairo_surface_t *surface)
static _scratch_t * _scratch_of(cairo_surface_t *surface)
static double _matrix_scale(cairo_t *cr)
gboolean dt_stroke_raster_polyline(cairo_surface_t *surface, const double *xy, int count, const dt_stroke_style_t *style)
static void _scratch_free(void *data)
static gboolean _stroke_polyline(cairo_surface_t *surface, const double *xy, const int count, const dt_stroke_style_t *style, const gboolean closed, _dash_t *const dash)
static _touched_t * _touched_of(cairo_surface_t *surface, const gboolean create)
static const cairo_user_data_key_t _touched_key
gboolean dt_stroke_raster_path(cairo_t *cr, const dt_stroke_style_t *style)
static void _plane_stamp_capsule(_plane_t *const plane, const _segment_t *const seg, const double reach)
static const cairo_user_data_key_t _scratch_key
static uint32_t _pixel_pack(const double a, const double r, const double g, const double b)
gboolean dt_stroke_raster_can_paint(cairo_surface_t *surface)
gboolean dt_stroke_raster_touched(cairo_surface_t *surface, cairo_rectangle_int_t *touched)
static gboolean _plane_stamp_row(_plane_t *const plane, const int y, const int x_first, const int x_last, const _segment_t *const seg, const double reach2)
static void _gather_flush(_gather_t *const g, cairo_surface_t *surface, const dt_stroke_style_t *style, _dash_t *const dash)
static void _gather_point(_gather_t *const g, cairo_t *cr, const cairo_path_data_t *const point)
static gboolean _composite_row(const _plane_t *const plane, const int y, const _composite_t *const c, int *const tx0, int *const tx1)
const dt_stroke_pass_t * bright
uint8_t * data
const dt_stroke_pass_t * dark
gboolean fresh
double left
gboolean on
double first_x
double scale_x
double first_y
gboolean closed
double offset_x
GArray * vertices
double scale_y
double offset_y
int * span_max
int * span_min
float * value
float * value
gboolean cap_b
gboolean cap_a
gboolean any
dt_stroke_pass_t dark
dt_stroke_pass_t bright
int y
Definition ashift_lsd.c:192
int x
Definition ashift_lsd.c:192
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29