3

I created this function that draws a simple polygon with n number of vertexes:

void polygon (int n)
{
    double pI = 3.141592653589;
    double area = min(width / 2, height / 2);
    int X = 0, Y = area - 1;
    double offset = Y;
    int lastx, lasty;

    double radius = sqrt(X * X + Y * Y);
    double quadrant = atan2(Y, X);

    int i;

    for (i = 1; i <= n; i++)
    {
        lastx = X; lasty = Y;
        quadrant = quadrant + pI * 2.0 / n;

        X = round((double)radius * cos(quadrant));
        Y = round((double)radius * sin(quadrant));

        setpen((i * 255) / n, 0, 0, 0.0, 1); // r(interval) g b, a, size

        moveto(offset + lastx, offset + lasty); // Moves line offset
        lineto(offset + X, offset + Y); // Draws a line from offset
    }
}

How can I fill it with a solid color? I have no idea how can I modify my code in order to draw it filled.

Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
Imobilis
  • 1,475
  • 8
  • 29
  • What library are you using to draw? That is not standard C. At a guess `fill(x,y,color)`, but without knowing the library, it's a 100% guess. – John3136 Aug 04 '15 at 01:09
  • This is not the place to ask for code enhancements. Try on code review or a forum site. – too honest for this site Aug 04 '15 at 01:11
  • @John3136 No this is exactly standard C and there is no library involved. I am performing VGA programming using simple implementations of the standard, simple, base, known drawing functions. – Imobilis Aug 04 '15 at 01:17
  • 2
    @Olaf Enhancements ?? This is your second time you are being highly improbable. Being unable to fill drawing is a problem with a severe solution.. This function would not work as desired if it isn't the correct way of filling it.. it is considered non-working code. Definitely not for code review. – Imobilis Aug 04 '15 at 01:21
  • May be "standard" on your platform, but it isn't standard C. From a quick google of "C moveto lineto" the lib I came to says you could have used `drawpoly()` (instead of writing your own) and it's counterpart `fillpoly()`. Again, no idea if this is the same library that you are using or not... – John3136 Aug 04 '15 at 01:26
  • As I said, I am not using library.. the implementations involve calling a specific BIOS interrupt on a accessible OS mode. These simple implementations are indeed very similar to others basic drawing functions, widely used in graphics. For any case, the way they work is commented. – Imobilis Aug 04 '15 at 01:28
  • 1
    You are asking how to extend your code to fill the polygons created. That is an extension ("enhancement was the wrong term - acknowledged). Having a look at your past posts, you seem to use this as a tutorial site, instead of doing some research on your own. I'd call this "social hacking". (Oh, and apparently, I am very "propable"). – too honest for this site Aug 04 '15 at 01:32
  • @Olaf Well yes, I guess I am evidently extending it (hence the name is just "polygon" not "filled_polygon" or something. It is still a code issue and I am actually truthfully unable to find solution to it, as it is a sheer implementation of a shape. I had to learn a lot until succeed to create such function from scratch. From the real scratch. – Imobilis Aug 04 '15 at 01:34
  • http://stackoverflow.com/a/31796502/2410359 answers how to draw a filled polygon. – chux - Reinstate Monica Aug 04 '15 at 01:47
  • Ok, so what have you done yourself to find a solution? I'm very sure you will find a suitable algorithm (that's what you are actually looking for) on the web. – too honest for this site Aug 04 '15 at 01:47
  • I created this function, because I couldn't find similar one in the internet. Plus why not? I don't want to copy everything I find in the internet. This is rather offensive. @chux I know, because I asked this question. It was how to do everything (draw a filled n-polygon).. it helped me with the implementation of this function. – Imobilis Aug 04 '15 at 01:50

4 Answers4

3

The common approach to fill shapes is to find where the edges of the polygon cross either each x or each y coordinate. Usually, y coordinates are used, so that the filling can be done using horizontal lines. (On framebuffer devices like VGA, horizontal lines are faster than vertical lines, because they use consecutive memory/framebuffer addresses.)

In that vein,

void fill_regular_polygon(int center_x, int center_y, int vertices, int radius)
{
    const double a = 2.0 * 3.14159265358979323846 / (double)vertices;
    int i = 1;
    int y, px, py, nx, ny;

    if (vertices < 3 || radius < 1)
        return;

    px = 0;
    py = -radius;
    nx = (int)(0.5 + radius * sin(a));
    ny = (int)(0.5 - radius * cos(a));
    y  = -radius;

    while (y <= ny || ny > py) {
        const int x = px + (nx - px) * (y - py) / (ny - py);
        if (center_y + y >= 0 && center_y + y < height) {
            if (center_x - x >= 0)
                moveto(center_x - x, center_y + y);
            else
                moveto(0, center_y + y);
            if (center_x + x < width)
                lineto(center_x + x, center_y + y);
            else
                lineto(width - 1, center_y + y);
        }
        y++;
        while (y > ny) {
            if (nx < 0)
                return;
            i++;
            px = nx;
            py = ny;
            nx = (int)(0.5 + radius * sin(a * (double)i));
            ny = (int)(0.5 - radius * cos(a * (double)i));
        }
    }
}

Note that I only tested the above with a simple SVG generator, and compared the drawn lines to the polygon. Seems to work correctly, but use at your own risk; no guarantees.

For general shapes, use your favourite search engine to look for "polygon filling" algorithms. For example, this, this, this, and this.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • It works for me too. I will now measure which one is faster and use it. Also yes, horizontal assignment is supposed to be slightly faster, although I always use `table[yOffset + iterator][xOffset]` to access each pixel to be assigned on the verticals. It is faster than raster scan. – Imobilis Aug 04 '15 at 11:28
  • Your approach just like I suspected is heavily faster. – Imobilis Aug 04 '15 at 11:47
  • @Malina: Note that to turn that into a filled circle, you can use for example `const int x = (int)(0.5 + sqrt((double)(radius*radius) - (double)(y*y)));` and omit `px`, `py`, `nx`, `ny`, and their related code altogether. This is because `y(x)=±sqrt(r*r-x*x)` and `x(y)=±sqrt(r*r-y*y)` describe the same circle as `x=r*cos(t), y=r*sin(t)` does. Or just loop over `x=-radius..radius`, `y=-radius..radius`, and fill pixel `x+center_x`, `y+center_y` if and only if `x*x+y*y <= radius*radius`. – Nominal Animal Aug 04 '15 at 18:02
3

There are 2 different ways to implement a solution:

Scan-line

Starting at the coordinate that is at the top (smallest y value), continue to scan down line by line (incrementing y) and see which edges intersect the line.

  • For convex polygons you find 2 points, (x1,y) and (x2,y). Simply draw a line between those on each scan-line.
  • For concave polygons this can also be a multiple of 2. Simply draw lines between each pair. After one pair, go to the next 2 coordinates. This will create a filled/unfilled/filled/unfilled pattern on that scan line which resolves to the correct overall solution.

In case you have self-intersecting polygons, you would also find coordinates that are equal to some of the polygon points, and you have to filter them out. After that, you should be in one of the cases above.

If you filtered out the polygon points during scan-lining, don't forget to draw them as well.

Flood-fill

The other option is to use flood-filling. It has to perform more work evaluating the border cases at every step per pixel, so this tends to turn out as a slower version. The idea is to pick a seed point within the polygon, and basically recursively extend up/down/left/right pixel by pixel until you hit a border.

Picking a seed point Usually this is done by randomly selecting a coordinate more or less inside a bound around all corner vertices. No matter which point you select as seed point, first you have to check that the chosen point is either inside, outside or at the very edge of the polygon. You can do this by tracing an imaginary line from the seed point to the 'edge of the canvas' (or infinity). If that imaginary trace intersects with any of the polygon edges a total number of times that is odd, the point lies inside the polygon. If the trace runs exactly through one of the corner vertices, that counts as 2 intersections and won't contribute to 'becoming inside' (for concave polygons - otherwise you can count it as 1). In that case try a different coordinate as seed point. Sometimes it makes sense to evaluate which seed point tends to have a higher fill-ratio based on how far it is from the nearest edge / corner vertex, especially if your fill algorithm is biased to fill faster in a specific direction (e.g. horizontal lines). Usually the approach is to start with points near the center of the bounding box.

Performance The algorithm has to read and write the entire surface of the polygon, and does not cross self-intersection points. There can be considerable stack-buildup (for naive implementations at least) for large surfaces, and the reduced flexibility you have for the border condition is pixel-based (e.g. flooding into gaps when other things are drawn on top of the polygon). In this sense, this is not a mathematically correct solution, but it works well for many applications.

StarShine
  • 1,940
  • 1
  • 27
  • 45
  • How do you process the horizontal edges of the polygon if they exist? For example, if my polygon is a rectangle, there are 3 edges in the top line. – 0xAA55 Aug 07 '23 at 08:18
  • Good question. You can actually 'choose' what works best. One option is to preserve the even/odd scheme of 'intersections' and simply also draw any edge that has slope 0 (i.e. the horizontal ones) - the other option is that you only store a given intersections point at most once in the list, so double occurrences never occur, which should give you the desired pattern outcome. With a good (hashed) list/collection you shouldn't see significant performance degradation. – StarShine Aug 07 '23 at 08:55
  • Actually by just remembering the last stored intersection, it's just one extra compare to prevent double occurrences, no need for expensive collections. – StarShine Aug 07 '23 at 09:20
0

Anyway, it seems that I / solved / this myself again, when not relying on assistance (or any attempt for it)

void polygon (int n)
{
    double pI = 3.141592653589;
    double area = min(width / 2, height / 2);
    int X = 0, Y = area - 1;
    double offset = Y;
    int lastx, lasty;

    while(Y-->0) {
    double radius = sqrt(X * X + Y * Y);
    double quadrant = atan2(Y, X);

    int i;


   for (i = 1; i <= n; i++)
    {
        lastx = X; lasty = Y;
        quadrant = quadrant + pI * 2.0 / n;

        X = round((double)radius * cos(quadrant));
        Y = round((double)radius * sin(quadrant));

        //setpen((i * 255) / n, 0, 0, 0.0, 1);
        setpen(255, 0, 0, 0.0, 1); // just red

        moveto(offset + lastx, offset + lasty);
        lineto(offset + X, offset + Y);
    } }
}

As you can see, it isn't very complex, which means it might not be the most efficient solution either.. but it is close enough. It decrements radius and fills it by virtue of its smaller version with smaller radius. On that way, precision plays an important role and the higher n is the less accuracy it will be filled with.

Imobilis
  • 1,475
  • 8
  • 29
  • Not robust, and coding-wise not the best example either. As the shape gets redrawn on different scales, the algorithm fails to take into account aliasing: a coordinate can fall in between 2 pixels and will be rounded, leading to pixels being covered 2ce and pixels not covered at all, i.e. there will be holes. Additionally, the number of corners and the overall 'area' define it's performance, instead of on the actual space it takes in screen resolution. If you need to draw many overlapping instances, any alternative that is resolution dependent will blow it out of the water. – StarShine May 05 '23 at 06:46
0

The most efficient solution is by decomposing the regular polygon in trapezoids (and one or two triangles).

By symmetry, the vertexes are vertically aligned and it is an easy matter to find the limiting abscissas (X + R cos(2πn/N) and X + R cos(2π(+1)N)).

You also have the ordinates (Y + R sin(2πn/N) and Y + R sin(2π(+1)N)) and it suffices to interpolate linearly between two vertexes by Y = Y0 + (Y1 - Y0) (X - X0) / (X1 - X0).

Filling in horizontal runs is a little more complex, as the vertices may not be aligned horizontally and there are more trapezoids.