Skip to content

Commit da8ac92

Browse files
committed
Implement endpoint-based elliptical arc drawing
Allow drawing elliptical arcs by specifying end points instead of center point, following some common vector graphic endpoint parameterization format. Ref: https://www.w3.org/TR/SVG/implnote.html
1 parent 5433792 commit da8ac92

File tree

4 files changed

+299
-1
lines changed

4 files changed

+299
-1
lines changed

apps/multi.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,54 @@ static void apps_jelly_start(twin_screen_t *screen, int x, int y, int w, int h)
220220
twin_window_show(window);
221221
}
222222

223+
static void draw_flower(twin_path_t *path,
224+
twin_fixed_t radius,
225+
int number_of_petals)
226+
{
227+
const twin_angle_t angle_shift = TWIN_ANGLE_360 / number_of_petals;
228+
const twin_angle_t angle_start = angle_shift / 2;
229+
twin_fixed_t p_x = twin_fixed_mul(radius, twin_cos(-angle_start));
230+
twin_fixed_t p_y = twin_fixed_mul(radius, twin_sin(-angle_start));
231+
twin_path_move(path, p_x, p_y);
232+
233+
for (twin_angle_t a = angle_start; a <= TWIN_ANGLE_360; a += angle_shift) {
234+
twin_fixed_t c_x = twin_fixed_mul(radius, twin_cos(a));
235+
twin_fixed_t c_y = twin_fixed_mul(radius, twin_sin(a));
236+
twin_fixed_t rx = radius;
237+
twin_fixed_t ry = radius * 3;
238+
twin_path_arc_ellipse(path, 1, 1, rx, ry, p_x, p_y, c_x, c_y,
239+
a - angle_start);
240+
p_x = c_x;
241+
p_y = c_y;
242+
}
243+
244+
twin_path_close(path);
245+
}
246+
247+
static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h)
248+
{
249+
twin_window_t *window = twin_window_create(
250+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
251+
twin_pixmap_t *pixmap = window->pixmap;
252+
twin_path_t *stroke = twin_path_create();
253+
twin_path_t *path = twin_path_create();
254+
twin_path_translate(path, D(200), D(200));
255+
twin_path_scale(path, D(10), D(10));
256+
twin_path_translate(stroke, D(200), D(200));
257+
twin_fill(pixmap, 0xffffffff, TWIN_SOURCE, 0, 0, w, h);
258+
twin_window_set_name(window, "Flower");
259+
twin_path_move(stroke, D(-200), D(0));
260+
twin_path_draw(stroke, D(200), D(0));
261+
twin_path_move(stroke, D(0), D(200));
262+
twin_path_draw(stroke, D(0), D(-200));
263+
twin_path_set_cap_style(stroke, TwinCapProjecting);
264+
twin_paint_stroke(pixmap, 0xffcc9999, stroke, D(10));
265+
draw_flower(path, D(3), 5);
266+
twin_paint_path(pixmap, 0xffe2d2d2, path);
267+
twin_path_destroy(stroke);
268+
twin_window_show(window);
269+
}
270+
223271
void apps_multi_start(twin_screen_t *screen,
224272
const char *name,
225273
int x,
@@ -233,4 +281,5 @@ void apps_multi_start(twin_screen_t *screen,
233281
apps_quickbrown_start(screen, x += 20, y += 20, w, h);
234282
apps_ascii_start(screen, x += 20, y += 20, w, h);
235283
apps_jelly_start(screen, x += 20, y += 20, w / 2, h);
284+
apps_flower_start(screen, x += 20, y += 20, w, h);
236285
}

include/twin.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ void twin_clear_file(twin_file_t *file);
670670
*/
671671

672672
#define twin_fixed_mul(a, b) ((twin_fixed_t) (((int64_t) (a) * (b)) >> 16))
673-
#define twin_fixed_div(a, b) ((twin_fixed_t) ((((int64_t) (a)) << 16) / b))
673+
#define twin_fixed_div(a, b) ((twin_fixed_t) ((((int64_t) (a)) << 16) / (b)))
674674

675675
twin_fixed_t twin_fixed_sqrt(twin_fixed_t a);
676676

@@ -800,6 +800,7 @@ void twin_path_ellipse(twin_path_t *path,
800800
twin_fixed_t y,
801801
twin_fixed_t x_radius,
802802
twin_fixed_t y_radius);
803+
803804
void twin_path_arc(twin_path_t *path,
804805
twin_fixed_t x,
805806
twin_fixed_t y,
@@ -808,6 +809,26 @@ void twin_path_arc(twin_path_t *path,
808809
twin_angle_t start,
809810
twin_angle_t extent);
810811

812+
void twin_path_arc_ellipse(twin_path_t *path,
813+
bool large_arc,
814+
bool sweep,
815+
twin_fixed_t radius_x,
816+
twin_fixed_t radius_y,
817+
twin_fixed_t cur_x,
818+
twin_fixed_t cur_y,
819+
twin_fixed_t target_x,
820+
twin_fixed_t target_y,
821+
twin_angle_t rotation);
822+
823+
void twin_path_arc_circle(twin_path_t *path,
824+
bool large_arc,
825+
bool sweep,
826+
twin_fixed_t radius,
827+
twin_fixed_t cur_x,
828+
twin_fixed_t cur_y,
829+
twin_fixed_t target_x,
830+
twin_fixed_t target_y);
831+
811832
void twin_path_rectangle(twin_path_t *path,
812833
twin_fixed_t x,
813834
twin_fixed_t y,
@@ -1104,6 +1125,10 @@ twin_fixed_t twin_tan(twin_angle_t a);
11041125

11051126
void twin_sincos(twin_angle_t a, twin_fixed_t *sin, twin_fixed_t *cos);
11061127

1128+
twin_angle_t twin_atan2(twin_fixed_t y, twin_fixed_t x);
1129+
1130+
twin_angle_t twin_acos(twin_fixed_t x);
1131+
11071132
/*
11081133
* widget.c
11091134
*/

src/path.c

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,152 @@ void twin_path_arc(twin_path_t *path,
236236
twin_path_set_matrix(path, save);
237237
}
238238

239+
static twin_angle_t vector_angle(twin_fixed_t ux,
240+
twin_fixed_t uy,
241+
twin_fixed_t vx,
242+
twin_fixed_t vy)
243+
{
244+
twin_fixed_t dot = twin_fixed_mul(ux, vx) + twin_fixed_mul(uy, vy);
245+
246+
twin_fixed_t ua =
247+
twin_fixed_sqrt(twin_fixed_mul(ux, ux) + twin_fixed_mul(uy, uy));
248+
twin_fixed_t va =
249+
twin_fixed_sqrt(twin_fixed_mul(vx, vx) + twin_fixed_mul(vy, vy));
250+
251+
/* cos(theta) = (u ⋅ v) / (|u| * |v|) */
252+
twin_fixed_t cos_theta = twin_fixed_div(dot, twin_fixed_mul(ua, va));
253+
twin_fixed_t cross = twin_fixed_mul(ux, vy) - twin_fixed_mul(uy, vx);
254+
twin_angle_t angle = twin_acos(cos_theta);
255+
return (cross < 0) ? -angle : angle;
256+
}
257+
258+
typedef struct {
259+
twin_fixed_t cx, cy;
260+
twin_angle_t start, extent;
261+
} twin_ellipse_param_t;
262+
263+
static twin_ellipse_param_t get_center_parameters(twin_fixed_t x1,
264+
twin_fixed_t y1,
265+
twin_fixed_t x2,
266+
twin_fixed_t y2,
267+
bool fa,
268+
bool fs,
269+
twin_fixed_t rx,
270+
twin_fixed_t ry,
271+
twin_fixed_t phi)
272+
{
273+
twin_fixed_t sin_phi = twin_sin(phi);
274+
twin_fixed_t cos_phi = twin_cos(phi);
275+
276+
/* Simplify through translation/rotation */
277+
twin_fixed_t x =
278+
twin_fixed_mul(cos_phi, twin_fixed_mul(x1 - x2, TWIN_FIXED_HALF)) +
279+
twin_fixed_mul(sin_phi, twin_fixed_mul(y1 - y2, TWIN_FIXED_HALF));
280+
281+
twin_fixed_t y =
282+
twin_fixed_mul(-sin_phi, twin_fixed_mul(x1 - x2, TWIN_FIXED_HALF)) +
283+
twin_fixed_mul(cos_phi, twin_fixed_mul(y1 - y2, TWIN_FIXED_HALF));
284+
285+
twin_fixed_t px = twin_fixed_mul(x, x);
286+
twin_fixed_t py = twin_fixed_mul(y, y);
287+
twin_fixed_t prx = twin_fixed_mul(rx, rx);
288+
twin_fixed_t pry = twin_fixed_mul(ry, ry);
289+
/* Correct out-of-range radii */
290+
twin_fixed_t L = twin_fixed_div(px, prx) + twin_fixed_div(py, pry);
291+
292+
if (L > TWIN_FIXED_ONE) {
293+
twin_fixed_t sqrt_L = twin_fixed_sqrt(L);
294+
rx = twin_fixed_mul(sqrt_L, twin_fixed_abs(rx));
295+
ry = twin_fixed_mul(sqrt_L, twin_fixed_abs(ry));
296+
} else {
297+
rx = twin_fixed_abs(rx);
298+
ry = twin_fixed_abs(ry);
299+
}
300+
301+
/* Compute center */
302+
twin_fixed_t sign = (fa != fs) ? -1 : 1;
303+
double px_d = twin_fixed_to_double(px);
304+
double py_d = twin_fixed_to_double(py);
305+
double prx_d = twin_fixed_to_double(prx);
306+
double pry_d = twin_fixed_to_double(pry);
307+
308+
twin_fixed_t A =
309+
twin_double_to_fixed(pry_d / (py_d + (pry_d / prx_d) * px_d));
310+
twin_fixed_t B =
311+
twin_double_to_fixed(prx_d / (prx_d + (px_d / py_d) * pry_d));
312+
twin_fixed_t C =
313+
twin_double_to_fixed(pry_d / (pry_d + (py_d / px_d) * prx_d));
314+
315+
twin_fixed_t pM = A - B - C;
316+
twin_fixed_t M = sign * twin_fixed_sqrt(pM);
317+
twin_fixed_t _cx =
318+
twin_fixed_mul(M, twin_fixed_div(twin_fixed_mul(rx, y), ry));
319+
twin_fixed_t _cy =
320+
twin_fixed_mul(M, twin_fixed_div(twin_fixed_mul(-ry, x), rx));
321+
322+
twin_ellipse_param_t ret;
323+
ret.cx = twin_fixed_mul(cos_phi, _cx) - twin_fixed_mul(sin_phi, _cy) +
324+
twin_fixed_mul(x1 + x2, TWIN_FIXED_HALF);
325+
326+
ret.cy = twin_fixed_mul(sin_phi, _cx) + twin_fixed_mul(cos_phi, _cy) +
327+
twin_fixed_mul(y1 + y2, TWIN_FIXED_HALF);
328+
329+
/* Compute θ and dθ */
330+
ret.start = vector_angle(TWIN_FIXED_ONE, 0, twin_fixed_div(x - _cx, rx),
331+
twin_fixed_div(y - _cy, ry));
332+
twin_angle_t extent = vector_angle(
333+
twin_fixed_div(x - _cx, rx), twin_fixed_div(y - _cy, ry),
334+
twin_fixed_div(-x - _cx, rx), twin_fixed_div(-y - _cy, ry));
335+
336+
if (fs && extent > TWIN_ANGLE_0)
337+
extent -= TWIN_ANGLE_360;
338+
if (!fs && extent < TWIN_ANGLE_0)
339+
extent += TWIN_ANGLE_360;
340+
ret.start %= TWIN_ANGLE_360;
341+
extent %= TWIN_ANGLE_360;
342+
343+
ret.extent = extent;
344+
return ret;
345+
}
346+
347+
void twin_path_arc_ellipse(twin_path_t *path,
348+
bool large_arc,
349+
bool sweep,
350+
twin_fixed_t radius_x,
351+
twin_fixed_t radius_y,
352+
twin_fixed_t cur_x,
353+
twin_fixed_t cur_y,
354+
twin_fixed_t target_x,
355+
twin_fixed_t target_y,
356+
twin_angle_t rotation)
357+
{
358+
twin_ellipse_param_t param;
359+
param = get_center_parameters(cur_x, cur_y, target_x, target_y, large_arc,
360+
sweep, radius_x, radius_y, rotation);
361+
twin_matrix_t save = twin_path_current_matrix(path);
362+
363+
twin_path_translate(path, param.cx, param.cy);
364+
twin_path_rotate(path, rotation);
365+
twin_path_translate(path, -param.cx, -param.cy);
366+
twin_path_arc(path, param.cx, param.cy, radius_x, radius_y, param.start,
367+
param.extent);
368+
369+
twin_path_set_matrix(path, save);
370+
}
371+
372+
void twin_path_arc_circle(twin_path_t *path,
373+
bool large_arc,
374+
bool sweep,
375+
twin_fixed_t radius,
376+
twin_fixed_t cur_x,
377+
twin_fixed_t cur_y,
378+
twin_fixed_t target_x,
379+
twin_fixed_t target_y)
380+
{
381+
twin_path_arc_ellipse(path, large_arc, sweep, radius, radius, cur_x, cur_y,
382+
target_x, target_y, TWIN_ANGLE_0);
383+
}
384+
239385
void twin_path_rectangle(twin_path_t *path,
240386
twin_fixed_t x,
241387
twin_fixed_t y,

src/trig.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,81 @@ void twin_sincos(twin_angle_t a, twin_fixed_t *sin, twin_fixed_t *cos)
9797
*cos = cos_val;
9898
}
9999
}
100+
101+
static const twin_angle_t atan_table[] = {
102+
0x0200, /* arctan(2^0) = 45° -> 512 */
103+
0x0130, /* arctan(2^-1) = 26.565° -> 303 */
104+
0x009B, /* arctan(2^-2) = 14.036° -> 155 */
105+
0x004F, /* arctan(2^-3) = 7.125° -> 79 */
106+
0x0027, /* arctan(2^-4) = 3.576° -> 39 */
107+
0x0014, /* arctan(2^-5) = 1.790° -> 20 */
108+
0x000A, /* arctan(2^-6) = 0.895° -> 10 */
109+
0x0005, /* arctan(2^-7) = 0.448° -> 5 */
110+
0x0002, /* arctan(2^-8) = 0.224° -> 2 */
111+
0x0001, /* arctan(2^-9) = 0.112° -> 1 */
112+
0x0001, /* arctan(2^-10) = 0.056° -> 1 */
113+
0x0000, /* arctan(2^-11) = 0.028° -> 0 */
114+
};
115+
116+
static twin_angle_t twin_atan2_first_quadrant(twin_fixed_t y, twin_fixed_t x)
117+
{
118+
if (x == 0 && y == 0)
119+
return TWIN_ANGLE_0;
120+
if (x == 0)
121+
return TWIN_ANGLE_90;
122+
if (y == 0)
123+
return TWIN_ANGLE_0;
124+
twin_angle_t angle = 0;
125+
/* CORDIC iteration */
126+
for (int i = 0; i < 12; i++) {
127+
twin_fixed_t temp_x = x;
128+
if (y > 0) {
129+
x += (y >> i);
130+
y -= (temp_x >> i);
131+
angle += atan_table[i];
132+
} else {
133+
x -= (y >> i);
134+
y += (temp_x >> i);
135+
angle -= atan_table[i];
136+
}
137+
}
138+
return angle;
139+
}
140+
141+
twin_angle_t twin_atan2(twin_fixed_t y, twin_fixed_t x)
142+
{
143+
if (x == 0 && y == 0)
144+
return TWIN_ANGLE_0;
145+
if (x == 0)
146+
return (y > 0) ? TWIN_ANGLE_90 : TWIN_ANGLE_270;
147+
if (y == 0)
148+
return (x > 0) ? TWIN_ANGLE_0 : TWIN_ANGLE_180;
149+
twin_fixed_t x_sign_mask = x >> 31;
150+
twin_fixed_t abs_x = (x ^ x_sign_mask) - x_sign_mask;
151+
twin_fixed_t y_sign_mask = y >> 31;
152+
twin_fixed_t abs_y = (y ^ y_sign_mask) - y_sign_mask;
153+
twin_fixed_t m = ((~x_sign_mask & ~y_sign_mask) * 0) +
154+
((x_sign_mask & ~y_sign_mask) * 1) +
155+
((x_sign_mask & y_sign_mask) * 1) +
156+
((~x_sign_mask & y_sign_mask) * 2);
157+
twin_fixed_t sign = 1 - 2 * (x_sign_mask ^ y_sign_mask);
158+
twin_angle_t angle = twin_atan2_first_quadrant(abs_y, abs_x);
159+
/* First quadrant : angle
160+
* Second quadrant : 180 - angle
161+
* Third quadrant : 180 + angle
162+
* Fourth quadrant : 360 - angle
163+
*/
164+
return TWIN_ANGLE_180 * m + sign * angle;
165+
}
166+
167+
twin_angle_t twin_acos(twin_fixed_t x)
168+
{
169+
if (x <= -TWIN_FIXED_ONE)
170+
return TWIN_ANGLE_180;
171+
if (x >= TWIN_FIXED_ONE)
172+
return TWIN_ANGLE_0;
173+
twin_fixed_t y = twin_fixed_sqrt(TWIN_FIXED_ONE - twin_fixed_mul(x, x));
174+
if (x < 0)
175+
return TWIN_ANGLE_180 - twin_atan2_first_quadrant(y, -x);
176+
return twin_atan2_first_quadrant(y, x);
177+
}

0 commit comments

Comments
 (0)