Skip to content
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

Merged
merged 2 commits into from
Dec 5, 2024

Conversation

ndsl7109256
Copy link
Collaborator

@ndsl7109256 ndsl7109256 commented Nov 19, 2024

Add missing path functionalities required for rendering TinyVG (Tiny Vector Graphics) images. Specifically, the following features have been implemented:

  • Arc Ellipse
    Draws an ellipse segment between the current and the target point, where radius_x and radius_y determine the both radii of the ellipse. large_arc determine small(0) or larger(1) circle segment is drawn. If sweep is 1, the ellipse segment will make a left turn, otherwise it will make a right turn. rotation is the rotation of the ellipse.
  • Arc circle
    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. If sweep is 1, the circle segment will make a left turn, otherwise it will make a right turn.
  • Quadratic Bézier
    Draws a Bézier curve with a single control point.

Demo

  • Ellipse arc
image
  • Quadratic Bezier
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

@ndsl7109256 ndsl7109256 changed the title Path Implement path function to fulfill TinyVG rendering Nov 19, 2024
@ndsl7109256 ndsl7109256 force-pushed the path branch 3 times, most recently from 8f7ace6 to fe63524 Compare November 19, 2024 03:37
@ndsl7109256 ndsl7109256 changed the title Implement path function to fulfill TinyVG rendering Support path function to fulfill TinyVG rendering Nov 19, 2024
@jserv jserv requested review from weihsinyeh and jouae November 19, 2024 06:12
apps/multi.c Outdated Show resolved Hide resolved
apps/multi.c Outdated Show resolved Hide resolved
src/path.c Outdated Show resolved Hide resolved
src/path.c Outdated Show resolved Hide resolved
@jserv jserv requested a review from Bennctu November 19, 2024 15:05
apps/spline.c Outdated Show resolved Hide resolved
src/path.c Outdated Show resolved Hide resolved
src/trig.c Show resolved Hide resolved
src/trig.c Outdated
int quadrant;
twin_fixed_t abs_x = x;
twin_fixed_t abs_y = y;
if (x >= 0 && y >= 0) {
Copy link
Collaborator

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 $\text{result} = 2048 * \text{m} + \text{sign} * \text{angle}$.

Copy link
Collaborator

@jouae jouae Nov 20, 2024

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:

  1. the two's complement system -x = ~x + 1 and
  2. 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:

$$abs(x)= \begin{cases} x &,\text{if }x \geq 0, \\\ -x &,\text{if }x <0. \end{cases}$$

This can be rewritten using the two's complement system as:

$$abs(x)= \begin{cases} (x\text{ XOR } - 0) - (-0) &,\text{if }x \geq 0, \\\ (x \text{ XOR } -1) - (-1) &,\text{if }x <0. \end{cases}$$

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 in 0xFFFFFFFF.
  • When x is non-negative, x >> 31 results in 0x00000000.

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.

$$m= \begin{cases} 0 &,\text{if } x\geq 0 \text{ and }y\geq 0, \\\ 1 &,\text{if } x< 0 \text{ and }y\geq 0, \\\ 1 &,\text{if } x< 0 \text{ and }y<0, \\\ 2 &,\text{if } x\geq 0 \text{ and }y< 0. \end{cases}$$

Here, we use some auxiliary functions $f_i$ to represent m.

$$m = \sum^4_{i=1} f_i(x, y)$$

where the functions $f_i(x,y)$ have the following constraints:
$f_1(x,y) \neq 0$ and $f_j(x,y)=0$ for $j\neq 1$ if $x\geq 0,y\geq 0$, the remaining auxiliary functions follow a similar pattern.

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);

Copy link
Collaborator

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.

Copy link
Collaborator Author

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) {
Copy link
Contributor

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.

Copy link
Contributor

@jserv jserv left a 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.

@jserv
Copy link
Contributor

jserv commented Nov 20, 2024

Mado spline

It is confusing to have two windows titled "Spline" serving the same purpose. Consider integrating them into a single application with buttons to switch the number of points.

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);
Copy link
Collaborator

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)?

Copy link
Collaborator

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?

Copy link
Collaborator

@weihsinyeh weihsinyeh Nov 21, 2024

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);
}

Copy link
Collaborator Author

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);
}

Copy link
Collaborator

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?

Copy link
Collaborator

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.
spline (1)

Copy link
Collaborator Author

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?

Copy link
Collaborator

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);
}

Copy link
Collaborator Author

@ndsl7109256 ndsl7109256 Nov 24, 2024

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 :
image

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@weihsinyeh
Copy link
Collaborator

weihsinyeh commented Nov 21, 2024

It is confusing to have two windows titled "Spline" serving the same purpose. Consider integrating them into a single application with buttons to switch the number of points.

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.

include/twin.h Show resolved Hide resolved
@ndsl7109256 ndsl7109256 force-pushed the path branch 2 times, most recently from 92e164e to 86f3a23 Compare November 26, 2024 04:04
apps/spline.c Outdated Show resolved Hide resolved
@ndsl7109256 ndsl7109256 force-pushed the path branch 2 times, most recently from 28a3cfd to a3b3ebe Compare November 26, 2024 06:24
src/trig.c Outdated Show resolved Hide resolved
@ndsl7109256 ndsl7109256 force-pushed the path branch 3 times, most recently from c04675e to 9ae9814 Compare December 2, 2024 07:53
Copy link
Contributor

@jserv jserv left a 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

See https://medium.com/@alexey.inkin/how-to-force-newline-at-end-of-files-and-why-you-should-do-it-fdf76d1d090e

@ndsl7109256 ndsl7109256 force-pushed the path branch 2 times, most recently from ce53f9a to 19c4981 Compare December 2, 2024 09:04
@jserv jserv requested a review from weihsinyeh December 2, 2024 16:38
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);
Copy link
Collaborator

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?

Copy link
Collaborator Author

@ndsl7109256 ndsl7109256 Dec 3, 2024

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 $(ry)^2/(rx)^2$, p_y_divided_x_x is $y^2 / x^2$ , p_x_divided_y_x is $x^2 / y^2$ , I have added comments for 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)
Copy link
Collaborator

@weihsinyeh weihsinyeh Dec 2, 2024

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.

Copy link
Collaborator Author

@ndsl7109256 ndsl7109256 Dec 3, 2024

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Contributor

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.

@ndsl7109256 ndsl7109256 force-pushed the path branch 2 times, most recently from 016f72c to c180730 Compare December 3, 2024 03:40
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);
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 $(ry)^2/(rx)^2 = (ry/rx)^2$, px_x is $x^2$, the p represents power of 2 here.

Copy link
Collaborator

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)
Copy link
Collaborator

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()

Copy link
Collaborator Author

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.

Copy link
Collaborator

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 */
Copy link
Collaborator

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

@ndsl7109256 ndsl7109256 force-pushed the path branch 2 times, most recently from c72e558 to e20c014 Compare December 4, 2024 02:07
@jserv jserv requested a review from weihsinyeh December 4, 2024 15:44
apps/multi.c Outdated Show resolved Hide resolved
apps/multi.c Outdated Show resolved Hide resolved
src/path.c Outdated Show resolved Hide resolved
@@ -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)
Copy link
Collaborator

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
@jserv jserv merged commit 16f7f9e into sysprog21:main Dec 5, 2024
3 checks passed
@jserv
Copy link
Contributor

jserv commented Dec 5, 2024

Thank @ndsl7109256 for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants