быстрый алгоритм рисования закрашенных кругов? - PullRequest
44 голосов
/ 29 июля 2009

Я использую алгоритм круга Брезенхема для быстрого рисования круга. Однако я также хочу (по просьбе пользователя) нарисовать закрашенный круг.

Есть ли быстрый и эффективный способ сделать это? Что-то в том же духе, что и Брезенхэм?

Я использую язык C.

Ответы [ 9 ]

80 голосов
/ 29 июля 2009

Прочитав страницу Википедии по алгоритму окружности Брезенхэма (также «средняя точка») , оказалось, что проще всего было бы изменить его действия, чтобы вместо

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

и тому подобное, каждый раз, когда вы вместо этого делаете

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

То есть, для каждой пары точек (с тем же y), что у Брезенхэма, у вас есть сюжет , вместо этого соединяется линией .

55 голосов
/ 06 августа 2009

Просто используйте грубую силу. Этот метод повторяет несколько слишком много пикселей, но он использует только целочисленные умножения и сложения. Вы полностью избегаете сложности Bresenham и возможного узкого места 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);
21 голосов
/ 29 июля 2009

Вот грубое руководство по C # (не должно быть так сложно найти правильную идею для C) - это «сырая» форма без использования Брезенхэма для устранения повторяющихся квадратных корней.

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");
11 голосов
/ 20 февраля 2013

Вы можете использовать это:

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;
        }
    }
}
6 голосов
/ 27 июня 2014

Мне нравится ответ palm3D. Для грубой силы это удивительно быстрое решение. Там нет квадратного корня или тригонометрических функций, чтобы замедлить его. Его единственная слабость - вложенная петля.

Преобразование этого в один цикл делает эту функцию почти в два раза быстрее.

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

Это решение с одним контуром конкурирует с эффективностью решения для рисования линий.

            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);
            }
3 голосов
/ 05 сентября 2012

Вот как я это делаю:
Я использую значения с фиксированной точкой с точностью в два бита (мы должны управлять половинными и квадратными значениями половинных точек)
Как упоминалось в предыдущем ответе, я также использую квадратные значения вместо квадратных корней.
Во-первых, я определяю границу границы моего круга в 1/8 части круга. Я использую симметрию этих точек, чтобы нарисовать 4 «границы» круга. Затем я рисую квадрат внутри круга.

В отличие от алгоритма окружности средней точки , он будет работать с четными диаметрами (и с диаметрами действительных чисел, с небольшими изменениями).

Пожалуйста, прости меня, если мои объяснения не были ясны, я француз;)

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

Чтобы использовать нецелочисленный диаметр, вы можете увеличить точность фиксированной точки или использовать двойные значения. Можно даже создать своего рода сглаживание в зависимости от разницы между dY2 + (ray - x) * (ray - x) и ray2 (dx² + dy² и r²)

2 голосов
/ 25 февраля 2019

Алгоритм перебора Palm3D, который я нашел, является хорошей отправной точкой. Этот метод использует ту же предпосылку, но включает несколько способов пропустить проверку большинства пикселей.

Во-первых, вот код:

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

Далее объяснение.

Первое, что нужно отметить, это то, что если вы найдете минимальную координату х, которая находится внутри круга для данной горизонтальной линии, вы сразу узнаете максимальную координату х. Это связано с симметрией круга. Если минимальная координата x находится на 10 пикселей впереди левой части ограничительной рамки круга, то максимальная x находится на 10 пикселей позади правой границы ограничительной рамки.

Причина перехода от высоких значений x к низким значениям x заключается в том, что минимальное значение x будет найдено с меньшим количеством итераций. Это связано с тем, что минимальное значение x ближе к левому краю ограничительной рамки, чем центральная координата x круга для большинства линий, из-за того, что круг изогнут наружу, как видно на на этом изображении Следующее, что следует отметить, это то, что поскольку круг также симметричен по вертикали, каждая найденная линия дает вам свободную вторую линию для рисования, каждый раз, когда вы находите линию в верхней половине круга, вы получаете одну в нижней половине в радиус-у у координата. Следовательно, когда найдена какая-либо линия, можно нарисовать две, и только верхняя половина значений y должна быть перебрана.

Последнее, что следует отметить, это то, что если вы начинаете с значения y, находящегося в центре круга, а затем двигаетесь к вершине для y, то минимальное значение x для каждой следующей строки должно быть ближе к центру координата х круга, чем последняя строка. Это также связано с тем, что окружность изгибается ближе к значению центра х, когда вы поднимаетесь по кругу. Вот изображение того, как это происходит.

В итоге:

  1. Если вы найдете минимальную x-координату линии, вы получите максимальную x-координату бесплатно.
  2. Каждая линия, которую вы рисуете в верхней половине круга, дает вам линию в нижней половине круга бесплатно.
  3. Каждая минимальная координата x должна быть ближе к центру круга, чем предыдущая координата x для каждой линии при итерации от координаты y центра к вершине.

Вы также можете сохранить значение (radius * radius), а также (y * y) вместо их вычисления несколько раз.

2 голосов
/ 23 мая 2012

Если вам нужен быстрый алгоритм, рассмотрите возможность рисования многоугольника с N сторонами, чем выше N, тем точнее будет круг.

0 голосов
/ 29 июля 2009

Я бы просто сгенерировал список точек, а затем использовал бы функцию рисования полигонов для визуализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...