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