Рисование круга, стиль OpenGL

0

У меня есть пиксель размером 13 х 13 пикселей, и я использую функцию для рисования круга. (Экран 13 * 13, что может показаться странным, но его массив светодиодов, что объясняет это.)

unsigned char matrix[13][13];
const unsigned char ON = 0x01;
const unsigned char OFF = 0x00;

Вот первая реализация, которую я придумал. (Это неэффективно, что является особой проблемой, так как это проект встроенных систем, процессор на 80 МГц).

// Draw a circle
// mode is 'ON' or 'OFF'
inline void drawCircle(float rad, unsigned char mode)
{
    for(int ix = 0; ix < 13; ++ ix)
    {
        for(int jx = 0; jx < 13; ++ jx)
        {
            float r; // Radial
            float s; // Angular ("theta")
            matrix_to_polar(ix, jx, &r, &s); // Converts polar coordinates
                                             // specified by r and s, where
                                             // s is the angle, to index coordinates
                                             // specified by ix and jx.
                                             // This function just converts to
                                             // cartesian and then translates by 6.0.
            if(r < rad)
            {
                matrix[ix][jx] = mode; // Turn pixel in matrix 'ON' or 'OFF'
            }
        }
    }
}

Надеюсь, это ясно. Это довольно просто, но потом я запрограммировал его, чтобы я знал, как он должен работать. Если вам нужна дополнительная информация/объяснение, я могу добавить еще несколько кодов/комментариев.

Можно считать, что рисование нескольких кругов, например 4-6, происходит очень медленно... Поэтому я прошу совета по более эффективному алгоритму рисования кругов.

EDIT: удалось удвоить производительность, выполнив следующие изменения:

Функция, вызывающая рисунок, который выглядит так:

for(;;)
{
    clearAll(); // Clear matrix

    for(int ix = 0; ix < 6; ++ ix)
    {
        rad[ix] += rad_incr_step;
        drawRing(rad[ix], rad[ix] - rad_width);
    }

    if(rad[5] >= 7.0)
    {
        for(int ix = 0; ix < 6; ++ ix)
        {
            rad[ix] = rad_space_step * (float)(-ix);
        }

    }

    writeAll(); // Write 
}

Я добавил следующую проверку:

if(rad[ix] - rad_width < 7.0)
    drawRing(rad[ix], rad[ix] - rad_width);

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

EDIT 2: Аналогичным образом добавление обратной проверки увеличивает производительность.

if(rad[ix] >= 0.0)
    drawRing(rad[ix], rad[ix] - rad_width);

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

Редактировать 3: Матрица для полярности:

inline void matrix_to_polar(int i, int j, float* r, float* s)
{
    float x, y;
    matrix_to_cartesian(i, j, &x, &y);
    calcPolar(x, y, r, s);
}

inline void matrix_to_cartesian(int i, int j, float* x, float* y)
{
    *x = getX(i);
    *y = getY(j);  
}

inline void calcPolar(float x, float y, float* r, float* s)
{
    *r = sqrt(x * x + y * y);
    *s = atan2(y, x);
}

inline float getX(int xc)
{
    return (float(xc) - 6.0);
}

inline float getY(int yc)
{
    return (float(yc) - 6.0); 
}

В ответ на Клиффорд, что на самом деле много вызовов функций, если они не включены.

Редактировать 4: drawRing просто рисует 2 круга, во-первых, внешний круг с включенным режимом, а затем внутренний круг с выключенным режимом. Я достаточно уверен, что есть более эффективный метод рисования такой формы, но это отвлекает от вопроса.

  • 2
    Взгляните на алгоритм окружности средней точки , который не требует никакой тригонометрии.
  • 0
    Достаточно ли памяти для кэширования наборов точек? Это, вероятно, легче осуществить, чем круг Брезенхэма.
Показать ещё 14 комментариев
Теги:
algorithm
graphics
embedded

4 ответа

2

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

Не делая ничего необычного, что-то вроде этого должно быть хорошим началом:

int intRad = (int)rad;
int intRadSqr = (int)(rad * rad);

for (int ix = 0; ix <= intRad; ++ix)
{
    for (int jx = 0; jx <= intRad; ++jx)
    {
        if (ix * ix + jx * jx <= radSqr)
        {
            matrix[6 - ix][6 - jx] = mode;
            matrix[6 - ix][6 + jx] = mode;
            matrix[6 + ix][6 - jx] = mode;
            matrix[6 + ix][6 + jx] = mode;
        }
    }
}

Это делает всю математику в целочисленном формате и использует симметрию круга.

Изменение вышеизложенного, основываясь на отзывах в комментариях:

int intRad = (int)rad;
int intRadSqr = (int)(rad * rad);

for (int ix = 0; ix <= intRad; ++ix)
{
    for (int jx = 0; ix * ix + jx * jx <= radSqr; ++jx)
    {
        matrix[6 - ix][6 - jx] = mode;
        matrix[6 - ix][6 + jx] = mode;
        matrix[6 + ix][6 - jx] = mode;
        matrix[6 + ix][6 + jx] = mode;
    }
}
  • 0
    Мне нравится эта идея, но с двумя дополнительными модификациями: во-первых, я предпочитаю matrix[-ix + 6][+jx + 6] поскольку мне кажется более понятным, тот факт, что 6 добавляется каждый раз, заключается в перемещении позиции круга на экран. Во-вторых, я добавил else { break; } поскольку нет смысла проверять значения индекса y после того, как они были найдены за пределами круга, так как все последующие точки также будут находиться за пределами круга.
  • 1
    Это хорошая идея, чтобы закончить цикл раньше. Я добавил вторую версию, делая это.
Показать ещё 1 комментарий
1

Не стоит недооценивать стоимость даже базовой арифметики с использованием плавающей запятой на процессоре без FPU. Кажется маловероятным, что плавающая точка необходима, но детали ее использования скрыты в реализации matrix_to_polar().

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

Используя уравнение y = cy ± √ [rad 2 - (x-cx) 2 ], где cx, cy - центр (7, 7 в этом случае) и подходящая реализация целочисленного квадратного корня, круг может быть составлен таким образом:

void drawCircle( int rad, unsigned char mode )
{
    int r2 = rad * rad ;
    for( int x = 7 - rad; x <= 7 + rad; x++ )
    {
        int dx = x - 7 ;
        int dy = isqrt( r2 - dx * dx ) ;

        matrix[x][7 - dy] = mode ;
        matrix[x][7 + dy] = mode ;
    }
}

В моем тесте я использовал isqrt() ниже на основе кода здесь, но учитывая, что максимальный r 2 необходим - 169 (13 2 вы могли бы реализовать 16 или даже 8-битную оптимизированную версию, если это необходимо.Если ваш процессор 32 бит, это, наверное, хорошо.

uint32_t isqrt(uint32_t n)
{
   uint32_t root = 0, bit, trial;
   bit = (n >= 0x10000) ? 1<<30 : 1<<14;
   do
   {
      trial = root+bit;
      if (n >= trial)
      {
         n -= trial;
         root = trial+bit;
      }
      root >>= 1;
      bit >>= 2;
   } while (bit);

   return root;
}

Все, что было сказано, на таком устройстве с низким разрешением, вы, вероятно, получите более качественные круги и более высокую производительность, вручную создавая таблицы поиска растровых изображений для каждого необходимого радиуса. Если память является проблемой, для одного круга требуется всего 7 байтов для описания квадранта 7 x 7, который вы можете отразить во всех трех квадрантах, или для большей производительности вы можете использовать 7 x 16 бит слов для описания полукруга (поскольку реверсивный порядок бит дороже реверсивного доступа к массиву - если вы не используете ARM Cortex-M с битовым диапазоном). Используя полукруглые образы, 13 кругов нуждались бы в 13 x 7 x 2 байтах (182 байта), квадранты выглядели бы 7 x 8 x 13 (91 байт) - вы можете обнаружить, что меньше байтов, что кодовое пространство для вычисления кругов.

  • 0
    Снова глядя на исходный код, кажется, что вы рисуете заполненный круг . Это не то, что делает мой код - он рисует круг. Я оставлю это в ожидании разъяснения требований.
  • 0
    Еще раз спасибо за вашу помощь, я действительно не учел отсутствие процессора с плавающей запятой. Ответ заключается в том, что используемый мной чип не имеет блока с плавающей запятой.
Показать ещё 1 комментарий
0

Я бы согласился с ответом MooseBoy, если только он объяснил метод, который он предлагает лучше. Здесь мой подход к подходу таблицы поиска.

Решите его с помощью таблицы поиска

Дисплей 13x13 довольно мал, и если вам нужны только круги, которые полностью видны в пределах этого количества пикселей, вы будете обходиться с довольно маленькой таблицей. Даже если вам нужны большие круги, он должен быть лучше, чем любой алгоритмический способ, если вам нужно, чтобы он был быстрым (и у него есть ПЗУ для его сохранения).

Как это сделать

В основном вам нужно определить, как выглядит каждый возможный круг на дисплее 13x13. Недостаточно просто создавать моментальные снимки для дисплея 13x13, так как, скорее всего, вы хотели бы построить круги в произвольных положениях. Моя запись для записи таблицы будет выглядеть так:

struct circle_entry_s{
    unsigned int diameter;
    unsigned int offset;
};

Запись будет отображать заданный диаметр в пикселях в смещения в большой таблице байтов, содержащей форму кругов. Например, для диаметра 9 последовательность байтов будет выглядеть так:

0x1CU, 0x00U, /* 000111000 */
0x63U, 0x00U, /* 011000110 */
0x41U, 0x00U, /* 010000010 */
0x80U, 0x80U, /* 100000001 */
0x80U, 0x80U, /* 100000001 */
0x80U, 0x80U, /* 100000001 */
0x41U, 0x00U, /* 010000010 */
0x63U, 0x00U, /* 011000110 */
0x1CU, 0x00U, /* 000111000 */

Диаметр определяет, сколько байтов таблицы принадлежит кругу: одна строка пикселей генерируется из (diameter + 7) >> 3 байта, а количество строк соответствует диаметру. Коды вывода могут быть сделаны довольно быстро, в то время как таблица поиска достаточно компактна, чтобы получить даже больше, чем отображаемые в ней круги 13x13, если это необходимо.

Обратите внимание, что определение кругов таким образом для нечетного и четного диаметров может или не может понравиться вам при выводе по центру. Круги нечетного диаметра, по-видимому, будут иметь центр в "середине" пикселя, в то время как круги с четным диаметром будут иметь свой центр в "углу" пикселя.

Впоследствии вы также можете с пользой уточнить общий метод, чтобы иметь несколько кругов разных видимых размеров, но с одинаковым радиусом пикселей. В зависимости от вашей цели: если вы хотите какой-то гладкой анимации, вы можете попасть туда в конце концов.

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

0

Для медленного встроенного устройства, имеющего только элемент 13x13, вы должны просто составить справочную таблицу. Например:

struct ComputedCircle
{
    float rMax;
    char col[13][2];
};

Если процедура draw использует rMax для определения того, какой элемент LUT использовать. Например, если у вас есть 2 элемента с одним rMax = 1.4f, другое = 1.7f, то любой радиус между 1.4f и 1.7f будет использовать эту запись.

Элементы столбца будут указывать нулевой, один или два сегмента строки на строку, которые могут быть закодированы в нижнем и верхнем 4 битах каждого символа. -1 может использоваться как контрольное значение для ничего-на-этой-строке. Вам нужно, сколько записей в таблице поиска использовать, но с сеткой 13x13 вы должны иметь возможность кодировать все возможные исходы пикселей с более чем 100 записями и разумное приближение, используя только 10 или около того. Вы также можете обменивать сжатие на скорость рисования, например, помещая матрицу col [13] [2] в плоский список и кодируя количество строк.

  • 0
    Я не понимаю, как это будет работать, не могли бы вы предоставить больше информации?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню