fast algorithm for drawing filled circles?

前端 未结 10 1112
清歌不尽
清歌不尽 2020-12-02 07:28

I am using Bresenham\'s circle algorithm for fast circle drawing. However, I also want to (at the request of the user) draw a filled circle.

Is there a fast and effi

相关标签:
10条回答
  • 2020-12-02 08:17

    Here's how I'm doing it:
    I'm using fixed point values with two bits precision (we have to manage half points and square values of half points)
    As mentionned in a previous answer, I'm also using square values instead of square roots.
    First, I'm detecting border limit of my circle in a 1/8th portion of the circle. I'm using symetric of these points to draw the 4 "borders" of the circle. Then I'm drawing the square inside the circle.

    Unlike the midpoint circle algorith, this one will work with even diameters (and with real numbers diameters too, with some little changes).

    Please forgive me if my explanations were not clear, I'm french ;)

    void DrawFilledCircle(int circleDiameter, int circlePosX, int circlePosY)
    {
        const int FULL = (1 << 2);
        const int HALF = (FULL >> 1);
    
        int size = (circleDiameter << 2);// fixed point value for size
        int ray = (size >> 1);
        int dY2;
        int ray2 = ray * ray;
        int posmin,posmax;
        int Y,X;
        int x = ((circleDiameter&1)==1) ? ray : ray - HALF;
        int y = HALF;
        circlePosX -= (circleDiameter>>1);
        circlePosY -= (circleDiameter>>1);
    
        for (;; y+=FULL)
        {
            dY2 = (ray - y) * (ray - y);
    
            for (;; x-=FULL)
            {
                if (dY2 + (ray - x) * (ray - x) <= ray2) continue;
    
                if (x < y)
                {
                    Y = (y >> 2);
                    posmin = Y;
                    posmax = circleDiameter - Y;
    
                    // Draw inside square and leave
                    while (Y < posmax)
                    {
                        for (X = posmin; X < posmax; X++)
                            setPixel(circlePosX+X, circlePosY+Y);
                        Y++;
                    }
                    // Just for a better understanding, the while loop does the same thing as:
                    // DrawSquare(circlePosX+Y, circlePosY+Y, circleDiameter - 2*Y);
                    return;
                }
    
                // Draw the 4 borders
                X = (x >> 2) + 1;
                Y = y >> 2;
                posmax = circleDiameter - X;
                int mirrorY = circleDiameter - Y - 1;
    
                while (X < posmax)
                {
                    setPixel(circlePosX+X, circlePosY+Y);
                    setPixel(circlePosX+X, circlePosY+mirrorY);
                    setPixel(circlePosX+Y, circlePosY+X);
                    setPixel(circlePosX+mirrorY, circlePosY+X);
                    X++;
                }
                // Just for a better understanding, the while loop does the same thing as:
                // int lineSize = circleDiameter - X*2;
                // Upper border:
                // DrawHorizontalLine(circlePosX+X, circlePosY+Y, lineSize);
                // Lower border:
                // DrawHorizontalLine(circlePosX+X, circlePosY+mirrorY, lineSize);
                // Left border:
                // DrawVerticalLine(circlePosX+Y, circlePosY+X, lineSize);
                // Right border:
                // DrawVerticalLine(circlePosX+mirrorY, circlePosY+X, lineSize);
    
                break;
            }
        }
    }
    
    void DrawSquare(int x, int y, int size)
    {
        for( int i=0 ; i<size ; i++ )
            DrawHorizontalLine(x, y+i, size);
    }
    
    void DrawHorizontalLine(int x, int y, int width)
    {
        for(int i=0 ; i<width ; i++ )
            SetPixel(x+i, y);
    }
    
    void DrawVerticalLine(int x, int y, int height)
    {
        for(int i=0 ; i<height ; i++ )
            SetPixel(x, y+i);
    }
    

    To use non-integer diameter, you can increase precision of fixed point or use double values. It should even be possible to make a sort of anti-alias depending on the difference between dY2 + (ray - x) * (ray - x) and ray2 (dx² + dy² and r²)

    0 讨论(0)
  • 2020-12-02 08:18

    Here's a C# rough guide (shouldn't be that hard to get the right idea for C) - this is the "raw" form without using Bresenham to eliminate repeated square-roots.

    Bitmap bmp = new Bitmap(200, 200);
    
    int r = 50; // radius
    int ox = 100, oy = 100; // origin
    
    for (int x = -r; x < r ; x++)
    {
        int height = (int)Math.Sqrt(r * r - x * x);
    
        for (int y = -height; y < height; y++)
            bmp.SetPixel(x + ox, y + oy, Color.Red);
    }
    
    bmp.Save(@"c:\users\dearwicker\Desktop\circle.bmp");
    
    0 讨论(0)
  • 2020-12-02 08:18

    Great ideas here! Since I'm at a project that requires many thousands of circles to be drawn, I have evaluated all suggestions here (and improved a few by precomputing the square of the radius):

    http://quick-bench.com/mwTOodNOI81k1ddaTCGH_Cmn_Ag

    The Rev variants just have x and y swapped because consecutive access along the y axis are faster with the way my grid/canvas structure works.

    The clear winner is Daniel Earwicker's method ( DrawCircleBruteforcePrecalc ) that precomputes the Y value to avoid unnecessary radius checks. Somewhat surprisingly that negates the additional computation caused by the sqrt call.

    Some comments suggest that kmillen's variant (DrawCircleSingleLoop) that works with a single loop should be very fast, but it's the slowest here. I assume that is because of all the divisions. But perhaps I have adapted it wrong to the global variables in that code. Would be great if someone takes a look.

    EDIT: After looking for the first time since college years at some assembler code, I managed find that the final additions of the circle's origin are a culprit. Precomputing those, I improved the fastest method by a factor of another 3.7-3.9 according to the bench! http://quick-bench.com/7ZYitwJIUgF_OkDUgnyMJY4lGlA Amazing.

    This being my code:

    for (int x = -radius; x < radius ; x++)
    {
        int hh = (int)std::sqrt(radius_sqr - x * x);
        int rx = center_x + x;
        int ph = center_y + hh;
    
        for (int y = center_y-hh; y < ph; y++)
            canvas[rx][y] = 1;
    }
    
    0 讨论(0)
  • 2020-12-02 08:22

    Just use brute force. This method iterates over a few too many pixels, but it only uses integer multiplications and additions. You completely avoid the complexity of Bresenham and the possible bottleneck of sqrt.

    for(int y=-radius; y<=radius; y++)
        for(int x=-radius; x<=radius; x++)
            if(x*x+y*y <= radius*radius)
                setpixel(origin.x+x, origin.y+y);
    
    0 讨论(0)
提交回复
热议问题