-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add draw arc procedure. #2243
Comments
Hello! I had the same feature request, so implemented the Andres circle algorithm, which I adapted to include arc features. This algorithm is quite effective, as it sets each pixel only once, and can run with no floating points and no trigonometric function (in this case an approximation for the number of pixels drawn has to be made, so the arc edge can be noisy on large arc widths/radius: it can be annoying for some use cases, but if you make an animated spinner it is ok...) I made a JavaScript version so you can test it and see for yourself here: https://motla.github.io/arc-algorithm/ C implementation for u8g2It is just a proposal. u8g2_arc.h#ifndef U8G2_ARC_H
#define U8G2_ARC_H
#include "u8g2.h"
typedef float (*u8g2_atan2f_t)(float, float);
void u8g2_DrawArc(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad_in, u8g2_uint_t rad_out, u8g2_uint_t angle_start, u8g2_uint_t angle_end, u8g2_atan2f_t atan2f_func);
#endif // U8G2_ARC_H u8g2_arc.c#include "u8g2_arc.h"
static const float M_PI_2 = 1.57079632679489661923;
static const float M_PI_4 = 0.78539816339744830962;
static void u8g2_draw_arc(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad_in, u8g2_uint_t rad_out, u8g2_uint_t angle_start, u8g2_uint_t angle_end, u8g2_atan2f_t atan2f_func)
{
// Declare variables
u8g2_long_t x, y, d, r, as, ae, cnt, num_pts;
// Manage angle inputs
if(angle_start == angle_end) return;
uint8_t inverted = (angle_start > angle_end);
as = inverted ? angle_end : angle_start;
ae = inverted ? angle_start : angle_end;
// Trace each arc radius with the Andres circle algorithm
for(r = rad_in; r <= rad_out; r++)
{
x = 0;
y = r;
d = r - 1;
cnt = -1;
num_pts = atan2f_func ? 100 : (r * 8 / 10); // if no atan2f() function is provided, we make a low cost approximation of the number of pixels drawn for a 1/8th circle of radius r
// Process each pixel of a 1/8th circle of radius r
while (y >= x)
{
// If atan2f() function is provided, get the percentage of 1/8th circle drawn, otherwise count the drawn pixels
cnt = atan2f_func ? ((M_PI_2 - atan2f_func(y, x)) * 100 / M_PI_4) : (cnt + 1);
// Fill the pixels of the 8 sections of the circle, but only on the arc defined by the angles (start and end)
if((cnt > num_pts * as / 45 && cnt <= num_pts * ae / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 + y, y0 - x);
if((cnt > num_pts * (90 - ae) / 45 && cnt <= num_pts * (90 - as) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 + x, y0 - y);
if((cnt > num_pts * (as - 90) / 45 && cnt <= num_pts * (ae - 90) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 - x, y0 - y);
if((cnt > num_pts * (180 - ae) / 45 && cnt <= num_pts * (180 - as) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 - y, y0 - x);
if((cnt > num_pts * (as - 180) / 45 && cnt <= num_pts * (ae - 180) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 - y, y0 + x);
if((cnt > num_pts * (270 - ae) / 45 && cnt <= num_pts * (270 - as) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 - x, y0 + y);
if((cnt > num_pts * (as - 270) / 45 && cnt <= num_pts * (ae - 270) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 + x, y0 + y);
if((cnt > num_pts * (360 - ae) / 45 && cnt <= num_pts * (360 - as) / 45) ^ inverted) u8g2_DrawPixel(u8g2, x0 + y, y0 + x);
// Run Andres circle algorithm to get to the next pixel
if (d >= 2 * x)
{
d = d - 2 * x - 1;
x = x + 1;
} else if (d < 2 * (r - y))
{
d = d + 2 * y - 1;
y = y - 1;
} else
{
d = d + 2 * (y - x - 1);
y = y - 1;
x = x + 1;
}
}
}
}
void u8g2_DrawArc(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad_in, u8g2_uint_t rad_out, u8g2_uint_t angle_start, u8g2_uint_t angle_end, u8g2_atan2f_t atan2f_func)
{
/* check for bounding box */
#ifdef U8G2_WITH_INTERSECTION
{
if ( u8g2_IsIntersection(u8g2, x0-rad_out, y0-rad_out, x0+rad_out+1, y0+rad_out+1) == 0 )
return;
}
#endif /* U8G2_WITH_INTERSECTION */
/* draw arc */
u8g2_draw_arc(u8g2, x0, y0, rad_in, rad_out, angle_start, angle_end, atan2f_func);
} UsageAngles are between 0 and 360 degree. Fast approximation mode#include "u8g2.h"
#include "u8g2_arc.h"
u8g2_DrawArc(&u8g2, 30, 30, 12, 16, 0, 300, NULL); Precision mode#include <math.h>
#include "u8g2.h"
#include "u8g2_arc.h"
u8g2_DrawArc(&u8g2, 30, 30, 12, 16, 0, 300, atan2f); If you have a DSP, you can provide its |
@olikraus cool! happy to contribute to a library that I used for a decade now... thank you and don't hesitate if you have questions about the implementation |
With u8g2 i wanted to avoid float, because it might require a lot of extra code space, which is not available for very small uC. But indeed it looks a little bit odd. My test code is currently here: https://github.com/olikraus/u8g2/blob/master/sys/sdl/draw_arc/main.c |
I also wonder, what the 100 means in num_pts = atan2f_func ? 100 : (r * 8 / 10); |
One more problem is: Although the atan2 function can be provided, the float calculation is still part of the code, which causes Arduino to include all the float math procedures. Ideally a pure integer version would be preferred. |
I think the only way to improve it is to run the circle algorithm twice (only for 1/8th a circle), one time just to get the pixel count for radius r (which I now approximate with a linear function), another time for the drawing. I'm not sure it will be perfect but it should be a lot less expensive than using floats and
Yes good idea. We will have less granularity on angles, but for small displays I guess it's ok. // Fill the pixels of the 8 sections of the circle, but only on the arc defined by the angles (start and end)
if((cnt > num_pts * as / 32 && cnt <= num_pts * ae / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 + y, y0 - x);
if((cnt > num_pts * (64 - ae) / 32 && cnt <= num_pts * (64 - as) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 + x, y0 - y);
if((cnt > num_pts * (as - 64) / 32 && cnt <= num_pts * (ae - 64) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 - x, y0 - y);
if((cnt > num_pts * (128 - ae) / 32 && cnt <= num_pts * (128 - as) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 - y, y0 - x);
if((cnt > num_pts * (as - 128) / 32 && cnt <= num_pts * (ae - 128) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 - y, y0 + x);
if((cnt > num_pts * (192 - ae) / 32 && cnt <= num_pts * (192 - as) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 - x, y0 + y);
if((cnt > num_pts * (as - 192) / 32 && cnt <= num_pts * (ae - 192) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 + x, y0 + y);
if((cnt > num_pts * (256 - ae) / 32 && cnt <= num_pts * (256 - as) / 32) ^ inverted) u8g2_DrawPixel(u8g2, x0 + y, y0 + x);
It is just a random value for comparaison in the case you use
Yes I agree, I will test the solution mentioned above. |
I found an algorithm here: https://dl.acm.org/doi/pdf/10.1145/245.246 appendix A which fully avoids any kind of float math, but procedure arguments are little bit different. I am not sure how to continue from here. In order to fit into Arduino Uno, the algorithm should not use float at all. I can add the integer version of the above code, but the errors are very visible. I also wonder, whether we should remove the radius range and instead draw the arc only for one radius. The users could simply do their own loop of a radius range. |
Nice paper, I have to check it. I chose Andres algorithm because if you increment the radius, it doesn't let holes between traces unlike traditional Bresenham algorithms.
Ok I agree, it will work the same. We just have to remove the Regarding the idea to run the algorithm twice to avoid floats, the render is slightly better but there is still noise, because the number of drawn pixels by the algorithm is not exactly proportional to the angle. Will have to check your paper then. |
Not really, the SDL version just runs on an intel based Ubuntu laptop.
this makes sense...
It has a complete different approach regarding the arc specification. Not so sure whether this makes sense. |
@olikraus I checked your paper, it is really great but indeed for the arc algorithm we have to provide two points with (x,y) coordinates for the start and the end of the arc, which is unpractical for the user. On another hand, I found a formula which approximates arctan very well: double fast_atan(double x) {
return M_PI_4*x - x*(fabs(x) - 1)*(0.2447 + 0.0663*fabs(x));
} Now it runs on floats but a very interesting lead I'd like to fiddle with is to adapt this function with normalized integers, for example let's say instead of running between 0.0 and 1.0 making it run between 0 and 4095 and to use it internally to process I have to finish urgent ongoing projects but I hope I can take a look at it soon, probably a month from now I will have more time to work on it. |
Hi @olikraus, I'm finally back and finished the version with fast arctan formula using no floating points, which works really good. No more glitchy edges. This line: ratio = (M_PI_2 - atan2f(y, x)) * 32 / M_PI_4; // [0..32] can been replaced by these two lines: ratio = x * 255 / y; // x/y [0..255]
ratio = ratio * (770195 - (ratio - 255) * (ratio + 941)) / 6137491; // arctan(x/y) [0..32] The only thing is that we must use at least I removed the arc width (the user has to make a for loop for every radius). Also the start and end angles [0..255]. I made a pull request so you can test it (I played around a bit to make an animated spinner with SDL). |
very nice... ok, i read the PR first. let me link both together. |
very cool arguments: |
some documentation required in the wiki |
thanks! 😉 with github I can't do pull requests on the wiki, but it should be straightforward. just tell me if you want me to do something in particular or if you miss information 👍 |
ah, no, that was just a reminder for myself. I will do the wiki updates. |
Moin! I would like to use drawArc for a compass element, in detail for drawing an arc as a direction marker (small line in the area of current wind direction). In that case start and end could be > 255 . But that's not possible with an u8-type for start and end angle! Current signature of draw method is all Example code of my project:
Any hints are welcome 🙃 Olli |
@nordblick2 Thanks for your interest! Indeed we made this implementation to optimize for 8bit calculus, it's not a bug. Angles are thus 0-255 and not in degrees. If the first angle is superior to the second, it will draw backwards. You can try values quickly with this tool: https://motla.github.io/arc-algorithm/ |
Thanks everyone for the contribution here. |
Discussed in #1740
Originally posted by AndreZinoviev December 30, 2021
Hi.
Happy New Year, everyone!!!
Is it possible to draw an arc of any size? How can I do that?
If there is no such function, is it possible to integrate such a function into your library?
I guess it should be like this:
void U8G2::drawArc(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, u8g2_uint_t line width, u8g2_uint_t starting angle, u8g2_uint_t end angle)
The text was updated successfully, but these errors were encountered: