-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support path function to fulfill TinyVG rendering #74
Conversation
8f7ace6
to
fe63524
Compare
src/trig.c
Outdated
int quadrant; | ||
twin_fixed_t abs_x = x; | ||
twin_fixed_t abs_y = y; | ||
if (x >= 0 && y >= 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it can be modified to a branch-free method for determining the quadrant of the angle.
The return values seem to follow a certain pattern, which can be expressed in the following algebraic form as
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sign variable, there is a more elegant approach using bitwise operations. Similarly, the absolute values of x
and y
can also be simplified through bitwise manipulation, streamlining the entire computation.
To achieve the method described above, two key concepts are essential:
- the two's complement system
-x = ~x + 1
and - XOR operation.
From the two's complement system, we know that −x = ∼x + 1 = x ^ -1 - (-1)
. Using the definition of the absolute value function:
This can be rewritten using the two's complement system as:
At the same time, -1
in the two's complement system is represented as 0xFFFFFFFF
, which serves as the sign bit mask when x
is a negative number.
By using x >> 31
, the sign bit mask can be obtained. For example:
- When
x
is negative,x >> 31
results in0xFFFFFFFF
. - When
x
is non-negative,x >> 31
results in0x00000000
.
Based on the above discussion, the absolute value of x
can be implemented in a branch-free version using the sign bit mask.
x_sign_mask = x >> 31;
abs_x = (x ^ x_sign_mask) - x_sign_mask;
Next, we can determine m
based on the sign bit mask, and it can be implemented in a branch-free version.
Here, we use some auxiliary functions m
.
where the functions
The above auxiliary functions exist and can be used to represent m
as
m = ((~x_sign_mask & ~y_sign_mask ) * 0) +
((x_sign_mask & ~y_sign_mask ) * 1) +
((x_sign_mask & y_sign_mask ) * 1) +
((~x_sign_mask & y_sign_mask ) * 2);
you can substitute simple values to verify the calculation.
Finally, the sign
can also be determined using a branch-free method as following
sign = 1 - 2 * (x_sign_mask^ y_sign_mask);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you still have no idea to implement, ChatGPT is a good helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jouae Thanks for the detailed explanation : )
twin_int_to_fixed(2)); | ||
twin_path_empty(path); | ||
for (i = 0; i < NPT; i++) { | ||
if (spline->n_points == 4) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you generalize the handling for n_points = 3 and 4? That is, share code by avoiding duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set your username in Git, and use git commit --amend --author
to change your name to your formal name in Pinyin mentioned in your resume. This helps others recognize your identity and acknowledge your contributions.
twin_sfixed_t dy2 = twin_fixed_to_sfixed(y1) - y2s; | ||
twin_sfixed_t cx2 = x2s + twin_sfixed_mul(dx2, twin_int_to_sfixed(2) / 3); | ||
twin_sfixed_t cy2 = y2s + twin_sfixed_mul(dy2, twin_int_to_sfixed(2) / 3); | ||
_twin_path_scurve(path, cx1, cy1, cx2, cy2, x2s, y2s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m curious why the function twin_path_quadratic_curve()
does not use the function _twin_matrix_x()
and _twin_matrix_y()
.
Another question: Is there any difference compared to directly calculating the values x1
, y1
, x2
, and y2
, which are of type twin_fixed_t
, and then calling twin_path_curve(path, x1, y1, x2, y2, x3, y3)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it because operations with twin_fixed_t
are too complex?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another way to write this twin_path_quadratic_curve()
.
void twin_path_quadratic_curve(twin_path_t *path,
twin_fixed_t x1,
twin_fixed_t y1,
twin_fixed_t x2,
twin_fixed_t y2)
{
twin_spoint_t p0 = _twin_path_current_spoint(path);
twin_fixed_t x1_ratio = twin_fixed_div(
twin_fixed_mul(x1, twin_int_to_fixed(2)), twin_int_to_fixed(3));
twin_fixed_t y1_ratio = twin_fixed_div(
twin_fixed_mul(y1, twin_int_to_fixed(2)), twin_int_to_fixed(3));
return twin_path_curve(
path,
twin_fixed_div(twin_sfixed_to_fixed(p0.x), twin_int_to_fixed(3)) +
x1_ratio,
twin_fixed_div(twin_sfixed_to_fixed(p0.y), twin_int_to_fixed(3)) +
y1_ratio,
x1_ratio + twin_fixed_div(x2, twin_int_to_fixed(3)),
y1_ratio + twin_fixed_div(y2, twin_int_to_fixed(3)), x2, y2);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m curious why the function
twin_path_quadratic_curve()
does not use the function_twin_matrix_x()
and_twin_matrix_y()
.
Yeah. I miss it, Thanks for the reminder.
Another question: Is there any difference compared to directly calculating the values x1, y1, x2, and y2, which are of type twin_fixed_t, and then calling twin_path_curve(path, x1, y1, x2, y2, x3, y3)?
It's because the p0
we get from _twin_path_current_spoint
is translated by the path->state.matrix
(I would like to call it the absolute coordinate) while (x1, y1)
, (x2, y2)
are not translated (call it the relative coordinate) and we should not mixed these two system coordinate in calculation.
Since we could't get the relative coordinate of p0
(it would require inverse matrix of path->state.matrix
or additional variable to store it), I calculate the point with absolute coordinate system and call _twin_path_scurve
to produce the curve.
I think the revised code would be like this?
void twin_path_quadratic_curve(twin_path_t *path,
twin_fixed_t x1,
twin_fixed_t y1,
twin_fixed_t x2,
twin_fixed_t y2)
{
twin_spoint_t p0 = _twin_path_current_spoint(path);
/* Convert quadratic to cubic control point */
twin_sfixed_t x1s = _twin_matrix_x(&path->state.matrix, x1, y1);
twin_sfixed_t y1s = _twin_matrix_y(&path->state.matrix, x1, y1);
twin_sfixed_t x2s = _twin_matrix_x(&path->state.matrix, x2, y2);
twin_sfixed_t y2s = _twin_matrix_y(&path->state.matrix, x2, y2);
/* CP1 = P0 + 2/3 * (P1 - P0) */
twin_sfixed_t dx1 = x1s - p0.x;
twin_sfixed_t dy1 = y1s - p0.y;
twin_sfixed_t cx1 = p0.x + twin_sfixed_mul(dx1, twin_double_to_sfixed(2.0/3.0));
twin_sfixed_t cy1 = p0.y + twin_sfixed_mul(dy1, twin_double_to_sfixed(2.0/3.0));
/* CP2 = P2 + 2/3 * (P1 - P2) */
twin_sfixed_t dx2 = x1s - x2s;
twin_sfixed_t dy2 = y1s - y2s;
twin_sfixed_t cx2 = x2s + twin_sfixed_mul(dx2, twin_double_to_sfixed(2.0/3.0));
twin_sfixed_t cy2 = y2s + twin_sfixed_mul(dy2, twin_double_to_sfixed(2.0/3.0));
_twin_path_scurve(path, cx1, cy1, cx2, cy2, x2s, y2s);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think calculating (2/3)P1, (1/3)P0, and (1/3)P2 first would make it easier to read. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we could't get the relative coordinate of p0(it would require inverse matrix of path->state.matrix or additional variable to store it), I calculate the point with absolute coordinate system and call _twin_path_scurve to produce the curve.
However, why it need to get the relative coordinate of p0? My idea is calculating the cubic spline's P1 and P2 which is in the absolute coordinate system first and then using twin_path_curve()
to get the relative coordinate of p1, p2 and p3.
The function of twin_path_curve
is to change the point to relative coordinate and then change to twin_sfixed_t
.
The function of twin_path_quadratic_curve()
is to creat the cubic spline's P1 and P2 which is in the absolute coordinate system by P1 of quadratic spline and then use twin_path_curve()
is to change the point to relative coordinate and then change to twin_sfixed_t
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I didn't get it well.
twin_path_curve
convert the input point with relative coordinate and convert it with _twin_matrix_x
and _twin_matrix_y
to point with absolute coordinate and pass it to _twin_path_scurve
and I just design twin_path_quadratic_curve
with same style( convert relative coordinate input point and pass it with absolute coordinate to _twin_path_scurve
)
Maybe you could simply write your idea with code again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void twin_path_quadratic_curve(twin_path_t *path,
twin_fixed_t x1,
twin_fixed_t y1,
twin_fixed_t x2,
twin_fixed_t y2)
{
twin_spoint_t p0 = _twin_path_current_spoint(path);
/* Calculate (2/3) * P1 first */
x1 -= twin_fixed_div(x1, twin_int_to_fixed(3));
y1 -= twin_fixed_div(y1, twin_int_to_fixed(3));
/* Reuse the function twin_path_curve() that already implements "converting
* relative coordinate input point and passing it with absolute coordinate
* to _twin_path_scurve()." */
return twin_path_curve(
path,
x1 + twin_fixed_div(twin_sfixed_to_fixed(p0.x), twin_int_to_fixed(3)),
y1 + twin_fixed_div(twin_sfixed_to_fixed(p0.y), twin_int_to_fixed(3)),
x1 + twin_fixed_div(x2, twin_int_to_fixed(3)),
y1 + twin_fixed_div(y2, twin_int_to_fixed(3)), x2, y2);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because the p0 we get from _twin_path_current_spoint is translated by the path->state.matrix(I would like to call it the absolute coordinate) while (x1, y1), (x2, y2) are not translated (call it the relative coordinate) and we should not mixed these two system coordinate in calculation.
Just like I have mentioned, you couldn't calculate p0
( already translated to absolute coordinate by transition matrix) with x1
, x2
, y1
, y2
When I applied rotation (make the transition matrix is not identity) to the path
and use your code, the quadratic curve would be incorrect like :
That's why I said twin_sfixed_to_fixed(p0.x
) needs to be replaced with an operation involving the inverse matrix, making the calculation with x1 and x2 reasonable. But we don't want to use inverse matrix and we choose to calculate them in absolute coordinate system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
This is really interesting! By using cubic Bézier curves when only minor curve adjustments are needed, we can reduce the number of points required to store the font from three to just two. However, converting a cubic Bézier curve to a quadratic Bézier curve requires an approximation approach to make the two curves as similar as possible. |
92e164e
to
86f3a23
Compare
28a3cfd
to
a3b3ebe
Compare
c04675e
to
9ae9814
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the message reported by CI pipeline:
No newline at end of file
src/fixed.c
ce53f9a
to
19c4981
Compare
src/path.c
Outdated
twin_xfixed_t pry_x = twin_xfixed_mul(ry_x, ry_x); | ||
twin_xfixed_t p_ry_divided_rx_x = twin_xfixed_div(ry_x, rx_x); | ||
twin_xfixed_t p_x_divided_y_x = twin_xfixed_div(rx_x, ry_x); | ||
twin_xfixed_t p_y_divided_x_x = twin_xfixed_div(ry_x, rx_x); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These variables are too complicated to understand. For example, what is the difference between p_ry_divided_rx_x
and p_y_divided_x_x
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry Typo here. p_ry_divided_rx_x
is p_y_divided_x_x
is p_x_divided_y_x
is A
, B
, C
.
@@ -125,6 +138,11 @@ typedef int8_t twin_gfixed_t; | |||
#define twin_dfixed_div(a, b) \ | |||
((((twin_dfixed_t) (a)) << 8) / ((twin_dfixed_t) (b))) | |||
|
|||
#define twin_xfixed_mul(a, b) \ | |||
(twin_xfixed_t)((((__int128_t) (a)) * ((__int128_t) (b))) >> 32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am curious about the reason of using __int128_t
instead of using twin_xfixed_t
as the type for type casting, because the function primarily involves twin_xfixed_to_fixed()
and twin_fixed_to_xfixed()
, and twin_fixed_t
is defined as a 16.16 fixed-point format.
I understand the necessity of using __int128_t
in twin_xfixed_mul(p_ry_divided_rx_x, px_x)
(line 311 in path.c), because p_ry_divided_rx_x
has already undergone one twin_xfixed_t operation:
twin_xfixed_t p_ry_divided_rx_x = twin_xfixed_div(ry_x, rx_x);
This means that operand in twin_xfixed_mul(p_ry_divided_rx_x, px_x)
actually undergo two twin_xfixed_t
operations when twin_xfixed_mul()
is executed.
However, why not convert p_ry_divided_rx_x
to twin_fixed_t
in advance, as done in line 296 in path.c ?
twin_fixed_t L = twin_xfixed_to_fixed(twin_xfixed_div(px_x, prx_x) +
twin_xfixed_div(py_x, pry_x));
If p_ry_divided_rx_x
were converted to twin_fixed_t
(16.16 format) beforehand, it would ensure that all operands in twin_dfixed_div()
and twin_xfixed_mul()
undergo their first twin_xfixed_t
operation when these functions are executed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you misunderstood it, we didn't perform fixed-point type conversion in twin_xfixed_mul
and twin_xfixed_div
. Converting a
and b
to __int128_t
is to avoid calculation overflow. Similar to how we handle twin_fixed_mul
or twin_fixed_div
#define twin_fixed_mul(a, b) ((twin_fixed_t) (((int64_t) (a) * (b)) >> 16))
#define twin_fixed_div(a, b) ((twin_fixed_t) ((((int64_t) (a)) << 16) / (b)))
However, why not convert p_ry_divided_rx_x to twin_fixed_t in advance, as done in line 296 in path.c ?
I didn't get it, what's the relation between p_ry_divided_rx_x
and this formula.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I misunderstand. Therefore, when performing multiplication or division, it is necessary to use a data type whose size is twice as large as the original operand's data type to avoid overflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#define twin_sfixed_mul(a, b) \
((((twin_sfixed_t) (a)) * ((twin_sfixed_t) (b))) >> 4)
#define twin_sfixed_div(a, b) \
((((twin_sfixed_t) (a)) << 4) / ((twin_sfixed_t) (b)))
#define twin_dfixed_mul(a, b) \
((((twin_dfixed_t) (a)) * ((twin_dfixed_t) (b))) >> 8)
#define twin_dfixed_div(a, b) \
((((twin_dfixed_t) (a)) << 8) / ((twin_dfixed_t) (b)))
Therefore, this part should also use a data type that is twice the size of the original operand's data type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__int128_t
is a GNU extension. You should add FIXME
note for portable solutions.
016f72c
to
c180730
Compare
twin_xfixed_t py_x = twin_xfixed_mul(y_x, y_x); | ||
twin_xfixed_t prx_x = twin_xfixed_mul(rx_x, rx_x); | ||
twin_xfixed_t pry_x = twin_xfixed_mul(ry_x, ry_x); | ||
twin_xfixed_t p_ry_divided_rx_x = twin_xfixed_div(pry_x, prx_x); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pry_divided_prx_x
: I'm unsure if there's a better way to name these variables.
The p
in p_ry_divided_rx_x
represents the p
of pry_x
and prx_x
.
However, in px_x
, the p
means multiplication itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p_ry_divided_rx_x
is px_x
is p
represents power of 2 here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
apps/spline.c
Outdated
_twin_widget_queue_paint(&spline->widget); | ||
} | ||
|
||
#define D(x) twin_double_to_fixed(x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The D(x) twin_double_to_fixed(x)
has already been defined. Another question is: why not use twin_int_to_fixed()
in twin_button_create()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both are OK. I use D(x)
just for simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is #define D(x) twin_double_to_fixed(x)
in line 14 of apps/spline.c
src/trig.c
Outdated
|
||
static const twin_angle_t atan_table[] = { | ||
0x0200, /* arctan(2^0) = 45° -> 512 */ | ||
0x0130, /* arctan(2^-1) = 26.565° -> 303 */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4096 * 26.565° / 360° = 302.25 -> 303.
However, 0x0130 = 304
c72e558
to
e20c014
Compare
include/twin_private.h
Outdated
@@ -95,6 +105,9 @@ typedef int8_t twin_gfixed_t; | |||
#define twin_sfixed_to_dfixed(s) (((twin_dfixed_t) (s)) << 4) | |||
#define twin_dfixed_to_sfixed(d) ((twin_sfixed_t) ((d) >> 4)) | |||
|
|||
#define twin_xfixed_to_fixed(x) (twin_fixed_t)((x) >> 16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
((twin_fixed_t) ((x) >> 16)) for consistency
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
Thank @ndsl7109256 for contributing! |
Add missing path functionalities required for rendering TinyVG (Tiny Vector Graphics) images. Specifically, the following features have been implemented:
Draws an ellipse segment between the current and the target point, where
radius_x
andradius_y
determine the both radii of the ellipse.large_arc
determine small(0) or larger(1) circle segment is drawn. Ifsweep
is 1, the ellipse segment will make a left turn, otherwise it will make a right turn.rotation
is the rotation of the ellipse.Draws an ellipse segment between the current and the target point, where
radius
determine the radius of the circle.large_arc
determine small(0) or larger(1) circle segment is drawn. Ifsweep
is 1, the circle segment will make a left turn, otherwise it will make a right turn.Draws a Bézier curve with a single control point.
Demo
2024-11-26.12.08.42.mov
Ref:
[1]:https://fontforge.org/docs/techref/bezier.html#converting-truetype-to-postscript
[2]:https://www.w3.org/TR/SVG/implnote.html