Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
test_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
19/* The stroke rasteriser, pixel by pixel.
20 *
21 * Every expectation here is a pixel value read back from an ARGB32 surface, because the
22 * rasteriser's whole contract is what it puts in pixels: a band of the right width at the
23 * right place, a round or flat end, a gap where a dash pattern says so, the bright pass over
24 * the dark one, and -- the one that bites -- the same place cairo would put it when drawing
25 * inside a pushed group, whose surface is offset from the target's device space. */
26
28
29#include <cairo.h>
30#include <setjmp.h>
31#include <stdarg.h>
32#include <stddef.h>
33#include <stdint.h>
34#include <cmocka.h>
35
36static uint32_t _pixel(cairo_surface_t *surface, const int x, const int y)
37{
38 cairo_surface_flush(surface);
39 const uint8_t *data = cairo_image_surface_get_data(surface);
40 const int stride = cairo_image_surface_get_stride(surface);
41 return *(const uint32_t *)(data + (size_t)y * stride + (size_t)x * 4);
42}
43
44static int _alpha(cairo_surface_t *surface, const int x, const int y)
45{
46 return (int)(_pixel(surface, x, y) >> 24);
47}
48
49static dt_stroke_style_t _solid(const double width, const double r, const double g, const double b)
50{
51 dt_stroke_style_t style = { 0 };
52 style.dark = (dt_stroke_pass_t){ .width = width, .red = r, .green = g, .blue = b, .alpha = 1.0 };
53 style.round_caps = TRUE;
54 return style;
55}
56
58{
59 (void)state;
60 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 100, 20);
61 const double xy[] = { 10.5, 10.5, 90.5, 10.5 }; /* pixel centres */
62 const dt_stroke_style_t style = _solid(4.0, 0.0, 0.0, 1.0);
63 assert_true(dt_stroke_raster_polyline(s, xy, 2, &style));
64
65 /* on the centreline: opaque blue, premultiplied */
66 assert_int_equal(_pixel(s, 50, 10), 0xff0000ffu);
67 /* one pixel off the centreline is still inside a 4-wide band (distance 1 < 2) */
68 assert_int_equal(_alpha(s, 50, 11), 255);
69 /* the ramp: distance 2 sits on the edge, half covered */
70 const int edge = _alpha(s, 50, 12);
71 assert_in_range(edge, 100, 155);
72 /* distance 3 is beyond the ramp */
73 assert_int_equal(_alpha(s, 50, 13), 0);
74 /* a round cap: one pixel past the end is inside (distance 1), the pixel at distance 2 sits on
75 * the cap's edge, and distance 3 is beyond it */
76 assert_int_equal(_alpha(s, 9, 10), 255);
77 assert_in_range(_alpha(s, 8, 10), 100, 155);
78 assert_int_equal(_alpha(s, 7, 10), 0);
79
80 cairo_rectangle_int_t touched;
81 assert_true(dt_stroke_raster_touched(s, &touched));
82 assert_true(touched.x <= 8 && touched.x + touched.width >= 93);
83 assert_true(touched.y <= 8 && touched.y + touched.height >= 13);
84 cairo_surface_destroy(s);
85}
86
88{
89 (void)state;
90 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 100, 20);
91 const double xy[] = { 10.5, 10.5, 90.5, 10.5 };
92 dt_stroke_style_t style = _solid(4.0, 0.0, 0.0, 1.0);
93 style.round_caps = FALSE;
94 assert_true(dt_stroke_raster_polyline(s, xy, 2, &style));
95 assert_int_equal(_alpha(s, 50, 10), 255);
96 assert_int_equal(_alpha(s, 8, 10), 0); /* nothing past the end */
97 assert_int_equal(_alpha(s, 11, 10), 255); /* the line itself is intact */
98 cairo_surface_destroy(s);
99}
100
102{
103 (void)state;
104 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 120, 20);
105 const double xy[] = { 0.5, 10.5, 100.5, 10.5 };
106 dt_stroke_style_t style = _solid(2.0, 1.0, 1.0, 1.0);
107 style.dash_on = 10.0;
108 style.dash_off = 10.0;
109 style.round_caps = FALSE;
110 assert_true(dt_stroke_raster_polyline(s, xy, 2, &style));
111 assert_int_equal(_alpha(s, 5, 10), 255); /* first dash, 0..10 */
112 assert_int_equal(_alpha(s, 15, 10), 0); /* first gap, 10..20 */
113 assert_int_equal(_alpha(s, 25, 10), 255); /* second dash */
114 assert_int_equal(_alpha(s, 35, 10), 0);
115 assert_int_equal(_alpha(s, 95, 10), 0); /* 90..100 is a gap */
116 cairo_surface_destroy(s);
117}
118
120{
121 (void)state;
122 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 100, 20);
123 const double xy[] = { 10.5, 10.5, 90.5, 10.5 };
124 dt_stroke_style_t style = _solid(6.0, 1.0, 0.0, 0.0);
125 style.bright = (dt_stroke_pass_t){ .width = 2.0, .red = 0.0, .green = 1.0, .blue = 0.0, .alpha = 1.0 };
126 assert_true(dt_stroke_raster_polyline(s, xy, 2, &style));
127 /* centre: green won */
128 assert_int_equal(_pixel(s, 50, 10), 0xff00ff00u);
129 /* two pixels out: only the dark, red pass */
130 assert_int_equal(_pixel(s, 50, 12), 0xffff0000u);
131 cairo_surface_destroy(s);
132}
133
135{
136 (void)state;
137 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 100, 20);
138 cairo_t *cr = cairo_create(s);
139 cairo_move_to(cr, 10, 10);
140 cairo_line_to(cr, 90, 10);
141 const dt_stroke_style_t style = _solid(4.0, 0.0, 0.0, 1.0);
142 assert_false(dt_stroke_raster_path(cr, &style));
143 /* the path is still there for cairo to stroke */
144 assert_true(cairo_has_current_point(cr));
145 cairo_path_t *path = cairo_copy_path(cr);
146 assert_true(path->num_data > 0);
147 cairo_path_destroy(path);
148 cairo_destroy(cr);
149 cairo_surface_destroy(s);
150}
151
153{
154 (void)state;
155 /* The group's surface is sized to the clip and offset to it; a stroke painted into that
156 * surface must still appear at the path's device position once the group is composited. */
157 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 100, 100);
158 cairo_t *cr = cairo_create(s);
159 cairo_rectangle(cr, 20, 20, 60, 60);
160 cairo_clip(cr);
161 cairo_push_group(cr);
162 cairo_move_to(cr, 30, 50);
163 cairo_line_to(cr, 70, 50);
164 const dt_stroke_style_t style = _solid(4.0, 0.0, 0.0, 1.0);
165 assert_true(dt_stroke_raster_path(cr, &style));
166 assert_false(cairo_has_current_point(cr)); /* consumed, as a stroke would */
167 cairo_pop_group_to_source(cr);
168 cairo_paint(cr);
169 cairo_destroy(cr);
170
171 assert_int_equal(_alpha(s, 50, 50), 255); /* on the line */
172 assert_int_equal(_alpha(s, 50, 45), 0); /* well off it */
173 assert_int_equal(_alpha(s, 10, 50), 0); /* outside the clip */
174 assert_int_equal(_alpha(s, 30, 50), 255); /* the start, inside the clip */
175 cairo_surface_destroy(s);
176}
177
179{
180 (void)state;
181 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 120, 60);
182 cairo_t *cr = cairo_create(s);
183 cairo_scale(cr, 2.0, 2.0);
184 cairo_move_to(cr, 10.25, 10.25); /* device (20.5, 20.5): a pixel centre */
185 cairo_line_to(cr, 40.25, 10.25);
186 dt_stroke_style_t style = _solid(2.0, 0.0, 0.0, 1.0); /* 2 user units = 4 device pixels */
187 assert_true(dt_stroke_raster_path(cr, &style));
188 cairo_destroy(cr);
189 assert_int_equal(_alpha(s, 50, 20), 255);
190 assert_int_equal(_alpha(s, 50, 21), 255); /* still inside a 4-wide band */
191 assert_int_equal(_alpha(s, 50, 23), 0); /* three pixels out: beyond it */
192 assert_int_equal(_alpha(s, 50, 10), 0); /* the user-space y would have been wrong here */
193 cairo_surface_destroy(s);
194}
195
197{
198 (void)state;
199 /* A HiDPI widget's surface carries a device scale: cairo's device space is then half the
200 * pixel grid, and cairo_user_to_device() stops there. The line below is at user (10..40, 10)
201 * on a surface scaled by 2: it must land on pixel row 20, from column 20 to 80, four pixels
202 * wide for a width of 2 user units -- not on row 10 at half size. */
203 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 120, 60);
204 cairo_surface_set_device_scale(s, 2.0, 2.0);
205 cairo_t *cr = cairo_create(s);
206 cairo_move_to(cr, 10.25, 10.25);
207 cairo_line_to(cr, 40.25, 10.25);
208 const dt_stroke_style_t style = _solid(2.0, 0.0, 0.0, 1.0);
209 assert_true(dt_stroke_raster_path(cr, &style));
210 cairo_destroy(cr);
211 assert_int_equal(_alpha(s, 50, 20), 255); /* on the line, in pixels */
212 assert_int_equal(_alpha(s, 50, 21), 255); /* four pixels wide */
213 assert_int_equal(_alpha(s, 50, 23), 0);
214 assert_int_equal(_alpha(s, 50, 10), 0); /* where device units would have put it */
215 assert_int_equal(_alpha(s, 25, 10), 0);
216 assert_int_equal(_alpha(s, 79, 20), 255); /* the end, in pixels */
217 assert_int_equal(_alpha(s, 86, 20), 0);
218 cairo_surface_destroy(s);
219
220 /* and inside a pushed group on such a surface, where the group's offset is in pixels */
221 s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 120, 120);
222 cairo_surface_set_device_scale(s, 2.0, 2.0);
223 cr = cairo_create(s);
224 cairo_rectangle(cr, 10, 10, 40, 40); /* pixels 20..100 */
225 cairo_clip(cr);
226 cairo_push_group(cr);
227 cairo_move_to(cr, 15, 30); /* pixels (30, 60) .. (90, 60) */
228 cairo_line_to(cr, 45, 30);
229 assert_true(dt_stroke_raster_path(cr, &style));
230 cairo_pop_group_to_source(cr);
231 cairo_paint(cr);
232 cairo_destroy(cr);
233 assert_int_equal(_alpha(s, 60, 60), 255);
234 assert_int_equal(_alpha(s, 60, 30), 0); /* device units would have put it here */
235 assert_int_equal(_alpha(s, 10, 60), 0); /* outside the clip */
236 cairo_surface_destroy(s);
237}
238
240{
241 (void)state;
242 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 100, 20);
243 cairo_rectangle_int_t touched;
244 assert_false(dt_stroke_raster_touched(s, &touched));
245 const double xy[] = { 10.5, 10.5, 20.5, 10.5 };
246 const dt_stroke_style_t style = _solid(2.0, 1.0, 1.0, 1.0);
247 assert_true(dt_stroke_raster_polyline(s, xy, 2, &style));
248 assert_true(dt_stroke_raster_touched(s, &touched));
250 assert_false(dt_stroke_raster_touched(s, &touched));
251 cairo_surface_destroy(s);
252}
253
254int main(void)
255{
256 const struct CMUnitTest tests[] = {
258 cmocka_unit_test(_flat_caps_stop_at_the_ends),
259 cmocka_unit_test(_dashes_leave_gaps_along_the_line),
265 cmocka_unit_test(_the_touched_record_resets),
266 };
267 return cmocka_run_group_tests(tests, NULL, NULL);
268}
269
270// clang-format off
271// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
272// vim: shiftwidth=2 expandtab tabstop=2 cindent
273// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
274// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const float x
uint32_t width
Definition mipmap_cache.c:0
const float uint32_t state[4]
const float r
void dt_stroke_raster_touched_reset(cairo_surface_t *surface)
gboolean dt_stroke_raster_polyline(cairo_surface_t *surface, const double *xy, int count, const dt_stroke_style_t *style)
gboolean dt_stroke_raster_path(cairo_t *cr, const dt_stroke_style_t *style)
gboolean dt_stroke_raster_touched(cairo_surface_t *surface, cairo_rectangle_int_t *touched)
dt_stroke_pass_t dark
dt_stroke_pass_t bright
static void _a_path_in_a_pushed_group_lands_where_cairo_puts_it(void **state)
static void _a_horizontal_line_paints_a_band_of_its_width(void **state)
static void _dashes_leave_gaps_along_the_line(void **state)
static dt_stroke_style_t _solid(const double width, const double r, const double g, const double b)
static void _a_surface_it_cannot_write_is_refused_and_the_path_kept(void **state)
static void _a_device_scaled_surface_is_painted_in_its_pixels(void **state)
static int _alpha(cairo_surface_t *surface, const int x, const int y)
static void _flat_caps_stop_at_the_ends(void **state)
int main(void)
static uint32_t _pixel(cairo_surface_t *surface, const int x, const int y)
static void _the_touched_record_resets(void **state)
static void _the_bright_pass_paints_over_the_dark_one(void **state)
static void _the_matrix_scales_widths_and_positions(void **state)