web-dev-qa-db-fra.com

algorithme rapide pour dessiner des cercles remplis?

J'utilise algorithme de cercle de Bresenham pour le dessin de cercle rapide. Cependant, je souhaite également (à la demande de l'utilisateur) dessiner un cercle plein.

Existe-t-il un moyen rapide et efficace de le faire? Quelque chose dans le même sens que Bresenham?

La langue que j'utilise est C.

46
horseyguy

Après avoir lu la page Wikipedia sur l'algorithme de cercle de Bresenham (également 'Midpoint') , il semblerait que la chose la plus simple à faire serait de modifier ses actions, de sorte qu'au lieu de

setPixel(x0 + x, y0 + y);
setPixel(x0 - x, y0 + y);

et similaire, chaque fois que vous le faites

lineFrom(x0 - x, y0 + y, x0 + x, y0 + y);

Autrement dit, pour chaque paire de points (avec le même y) que Bresenham voudriez-vous que vous tracez , vous à la place connectez-vous avec une ligne .

80
AakashM

Utilisez simplement la force brute. Cette méthode itère sur quelques pixels de trop, mais elle utilise uniquement des multiplications et des ajouts entiers. Vous évitez complètement la complexité de Bresenham et le goulot d'étranglement possible de 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);
56
palm3D

Voici un guide approximatif en C # (cela ne devrait pas être si difficile de se faire une bonne idée pour C) - c'est la forme "brute" sans utiliser Bresenham pour éliminer les racines carrées répétées.

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");
22
Daniel Earwicker

Vous pouvez utiliser ceci:

void DrawFilledCircle(int x0, int y0, int radius)
{
    int x = radius;
    int y = 0;
    int xChange = 1 - (radius << 1);
    int yChange = 0;
    int radiusError = 0;

    while (x >= y)
    {
        for (int i = x0 - x; i <= x0 + x; i++)
        {
            SetPixel(i, y0 + y);
            SetPixel(i, y0 - y);
        }
        for (int i = x0 - y; i <= x0 + y; i++)
        {
            SetPixel(i, y0 + x);
            SetPixel(i, y0 - x);
        }

        y++;
        radiusError += yChange;
        yChange += 2;
        if (((radiusError << 1) + xChange) > 0)
        {
            x--;
            radiusError += xChange;
            xChange += 2;
        }
    }
}
11
Leszek Szary

J'aime la réponse de Palm3D. Pour être une force brute, c'est une solution incroyablement rapide. Il n'y a pas de fonction racine carrée ou trigonométrique pour le ralentir. Sa seule faiblesse est la boucle imbriquée.

La conversion en une seule boucle rend cette fonction presque deux fois plus rapide.

int r2 = r * r;
int area = r2 << 2;
int rr = r << 1;

for (int i = 0; i < area; i++)
{
    int tx = (i % rr) - r;
    int ty = (i / rr) - r;

    if (tx * tx + ty * ty <= r2)
        SetPixel(x + tx, y + ty, c);
}

Cette solution à boucle unique rivalise avec l'efficacité d'une solution de dessin au trait.

            int r2 = r * r;
            for (int cy = -r; cy <= r; cy++)
            {
                int cx = (int)(Math.Sqrt(r2 - cy * cy) + 0.5);
                int cyy = cy + y;

                lineDDA(x - cx, cyy, x + cx, cyy, c);
            }
6
kmillen

Voici comment je le fais:
J'utilise des valeurs à virgule fixe avec une précision de deux bits (nous devons gérer les demi-points et les valeurs carrées des demi-points)
Comme mentionné dans une réponse précédente, j'utilise également des valeurs carrées au lieu de racines carrées.
Premièrement, je détecte la limite de bordure de mon cercle dans une partie 1/8 du cercle. J'utilise symétrique de ces points pour tracer les 4 "bordures" du cercle. Ensuite, je dessine le carré à l'intérieur du cercle.

Contrairement à algorithme du cercle médian, celui-ci fonctionnera avec des diamètres pairs (et avec des diamètres réels aussi, avec quelques petites modifications).

Veuillez me pardonner si mes explications n'étaient pas claires, je suis français;)

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

Pour utiliser un diamètre non entier, vous pouvez augmenter la précision du point fixe ou utiliser des valeurs doubles. Il devrait même être possible de faire une sorte d'anti-alias en fonction de la différence entre dY2 + (ray - x) * (ray - x) et ray2 (dx² + dy² et r²)

3
Greg Joseph

L'algorithme de force brute de Palm3D m'a semblé être un bon point de départ. Cette méthode utilise la même prémisse, mais elle comprend plusieurs façons d'ignorer la vérification de la plupart des pixels.

Tout d'abord, voici le code:

int largestX = circle.radius;
for (int y = 0; y <= radius; ++y) {
    for (int x = largestX; x >= 0; --x) {
        if ((x * x) + (y * y) <= (circle.radius * circle.radius)) {
            drawLine(circle.center.x - x, circle.center.x + x, circle.center.y + y);
            drawLine(circle.center.x - x, circle.center.x + x, circle.center.y - y);
            largestX = x;
            break; // go to next y coordinate
        }
    }
}

Ensuite, l'explication.

La première chose à noter est que si vous trouvez la coordonnée x minimale qui se trouve dans le cercle pour une ligne horizontale donnée, vous connaissez immédiatement la coordonnée x maximale. Cela est dû à la symétrie du cercle. Si la coordonnée x minimale est de 10 pixels en avant de la gauche du cadre de délimitation du cercle, alors le x maximal est de 10 pixels en arrière de la droite du cadre de délimitation du cercle.

La raison d'itérer des valeurs x élevées vers des valeurs x faibles est que la valeur x minimale sera trouvée avec moins d'itérations. En effet, la valeur minimale de x est plus proche de la gauche du cadre de délimitation que la coordonnée centrale x du cercle pour la plupart des lignes, car le cercle est incurvé vers l'extérieur, comme indiqué sur cette image La suivante chose à noter est que puisque le cercle est également symétrique verticalement, chaque ligne que vous trouvez vous donne une deuxième ligne gratuite à dessiner, chaque fois que vous trouvez une ligne dans la moitié supérieure du cercle, vous en obtenez une sur la moitié inférieure au rayon -y coordonnée y. Par conséquent, lorsqu'une ligne est trouvée, deux peuvent être tracées et seule la moitié supérieure des valeurs y doit être itérée.

La dernière chose à noter est que si vous commencez à partir d'une valeur ay qui est au centre du cercle et que vous vous déplacez ensuite vers le haut pour y, la valeur x minimale pour chaque ligne suivante doit être plus proche de la coordonnée x centrale de le cercle que la dernière ligne. Cela est également dû au fait que le cercle se rapproche de la valeur centrale x lorsque vous montez le cercle. Voici un visuel sur la façon dont c'est le cas.

En résumé:

  1. Si vous trouvez la coordonnée x minimale d'une ligne, vous obtenez gratuitement la coordonnée x maximale.
  2. Chaque ligne que vous trouvez à dessiner sur la moitié supérieure du cercle vous donne gratuitement une ligne sur la moitié inférieure du cercle.
  3. Chaque coordonnée x minimale doit être plus proche du centre du cercle que la coordonnée x précédente pour chaque ligne lors de l'itération de la coordonnée y centrale vers le haut.

Vous pouvez également stocker la valeur de (radius * radius), et aussi (y * y) au lieu de les calculer plusieurs fois.

2
Arphimigon

Si vous voulez un algorithme rapide, pensez à dessiner un polygone avec N côtés, plus N est élevé, plus le cercle sera précis.

2
Ramy Al Zuhouri

Je voudrais simplement générer une liste de points, puis utiliser une fonction de dessin de polygone pour le rendu.

0
KPexEA