С++ Алгоритм линии Брезенхэма рисует дугу и вращается

Я ищу способ сделать дугу с помощью линейного алгоритма Брезенхэма. Этот алгоритм рисует идеальный круг, но что, если мне нужно нарисовать дугу (от 0 до Пи) и повернуть ее на 30 градусов (например)?

void DrawCircle(HDC hdc,int x0, int y0, int radius) 
{
        int x = 0;
        int y = radius;
        int delta = 2 - 2 * radius;
        int error = 0;

        while(y >= 0) {
                //SetPixel(hdc,x0 + x, y0 + y,pencol);
                SetPixel(hdc,x0 + x, y0 - y,pencol);
                //SetPixel(hdc,x0 - x, y0 + y,pencol);
                SetPixel(hdc,x0 - x, y0 - y,pencol);
                error = 2 * (delta + y) - 1;
                if(delta < 0 && error <= 0) {
                        ++x;
                        delta += 2 * x + 1;
                        continue;
                }
                error = 2 * (delta - x) - 1;
                if(delta > 0 && error > 0) {
                        --y;
                        delta += 1 - 2 * y;
                        continue;
                }
                ++x;
                delta += 2 * (x - y);
                --y;
        }
}

person PePe    schedule 09.12.2011    source источник


Ответы (2)


Чтобы получить 1/2 круга (в пи), вызовите только одну из ваших подпрограмм SetPixel. Чтобы ваша дуга повернулась на 30 градусов, требуется триггер. Вы можете позволить приведенному выше циклу работать до тех пор, пока ваше отношение x/y не станет равным tan (30 градусов), а затем начать рисовать, пока ваше отношение не достигнет значения, на котором вы хотите остановиться. Не самый эффективный способ, но сработает. Чтобы сделать это лучше, вам нужно предварительно рассчитать начальные 4 значения var. Вы можете взять значения из приведенного выше запуска и вставить их в качестве начальных значений, и это будет очень эффективно.

Вы получили приведенный выше алгоритм от Чёрная книга Майкла Абраша? Если нет, я бы погуглил это как вторую точку отсчета при быстром рисовании круга/дуги.

Ну, увы, эллипсов, которые отрывают главу, там не было. Вот кое-что, что я нашел в Интернете, которое утверждает, что оно от Абраша:


/* One of Abrash's ellipse algorithms  */

void draw_ellipse(int x, int y, int a, int b, int color)
{
    int wx, wy;
    int thresh;
    int asq = a * a;
    int bsq = b * b;
    int xa, ya;

    draw_pixel(x, y+b, color);
    draw_pixel(x, y-b, color);

    wx = 0;
    wy = b;
    xa = 0;
    ya = asq * 2 * b;
    thresh = asq / 4 - asq * b;

    for (;;) {
        thresh += xa + bsq;

        if (thresh >= 0) {
            ya -= asq * 2;
            thresh -= ya;
            wy--;
        }

        xa += bsq * 2;
        wx++;

        if (xa >= ya)
          break;


        draw_pixel(x+wx, y-wy, color);
        draw_pixel(x-wx, y-wy, color);
        draw_pixel(x+wx, y+wy, color);
        draw_pixel(x-wx, y+wy, color);
    }

    draw_pixel(x+a, y, color);
    draw_pixel(x-a, y, color);

    wx = a;
    wy = 0;
    xa = bsq * 2 * a;

    ya = 0;
    thresh = bsq / 4 - bsq * a;

    for (;;) {
        thresh += ya + asq;

        if (thresh >= 0) {
            xa -= bsq * 2;
            thresh = thresh - xa;
            wx--;
        }

        ya += asq * 2;
        wy++;

        if (ya > xa)
          break;

        draw_pixel(x+wx, y-wy, color);
        draw_pixel(x-wx, y-wy, color);
        draw_pixel(x+wx, y+wy, color);
        draw_pixel(x-wx, y+wy, color);
    }
}

Идея в том, что вы рисуете 8-й круг за время x4, а затем переворачиваете, чтобы нарисовать остальные 8-е. Тем не менее, это не дает прямого ответа на ваш вопрос. Работая над этим...

Опять же, ваш код выше должен работать, вам просто нужно тщательно контролировать начальные и конечные условия. Значение y >= 0 должно стать таким, каким будет значение y после завершения длины вашей «дуги», а начальные значения должны быть рассчитаны как начало вашей дуги.

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

person Michael Dorgan    schedule 09.12.2011
comment
Спасибо, я думал, что надо изменить уравнение, но мой вариант не сработал. Не могли бы вы привести пример, пожалуйста? И это не из Черной Книги Майкла Абраша. - person PePe; 09.12.2011
comment
Увы, это было из книги по графическому программированию в главе, не вошедшей в черную книгу. Я вспомнил, что читал, и предположил, что это будет в компиляционной версии. Сейчас поищу в нете... - person Michael Dorgan; 09.12.2011
comment
Спасибо, но я предпочел использовать алгоритм Брезенхема. - person PePe; 09.12.2011

Если вам точно не нужен Bresenham, в этом SO представлен быстрый шаговый метод post, где вы можете установить центральную точку, начальную точку и угол дуги. Критерий остановки ему не нужен, т.к. он уже включен в алгоритм (по углу дуги). Что делает его быстрым, так это предварительный расчет коэффициентов тангенциального и радиального перемещения, а фактический цикл не имеет вызовов триггерных функций, только умножение, сложение и вычитание.

Насколько я знаю, существует три типа методов:
A) Инкрементальный, как у Bresenham
B) Метод подразделения, например this
C) Пошаговый (или сегментный) метод

Я возьму медленный пример пошагового метода (не используйте его, если важна скорость):

// I know the question is tagged c++, but the idea comes clear in javascript
var start_angle = 0.5, end_angle = 1.1, r = 30;
for(var i = start_angle; i < end_angle; i = i + 0.05)
{
  drawpixel(x: 50 + Math.cos(i) * r, y: 100 + Math.sin(i) * r); // center point is (x = 50, y = 100)
}

Медлительность исходит от cos и sin, которые повторяются (излишне) в цикле. Это можно решить путем предварительного расчета cos и sin, как описано в вышеупомянутом посте SO. Это означает огромное ускорение (в среднем 12x в топ-5 javascript-движках).

Я провел неполный сравнительный тест скорости различных алгоритмов рисования кругов и дуг. Bresenham работает быстро, но необходимо добавить логику критерия запуска и остановки, что немного замедляет алгоритм. Если вам действительно нужны Bresenham и arc, у меня нет готового решения для этого и пока не нашел такого. Это возможно. Кстати, пошаговый метод с использованием предварительно рассчитанных триггеров не так уж и плох по производительности по сравнению с Брезенхэмом (по крайней мере, в javascript). Пожалуйста, протестируйте на С++ и сообщите.

person Timo Kähkönen    schedule 10.02.2013