绘制圆圈,OpenGL风格

时间:2014-08-29 17:38:40

标签: c++ c algorithm graphics embedded

我有一个13 x 13像素数组,我正在使用一个函数在它们上画一个圆圈。 (屏幕是13 * 13,这可能看起来很奇怪,但它的LED阵列就这样解释了。)

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

这是我想到的第一个实现。 (这是低效的,这是一个特殊问题,因为这是一个嵌入式系统项目,80 MHz处理器。)

// 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,非常慢......因此,我正在寻求有关绘制圆圈的更有效算法的建议。

编辑:通过进行以下修改来管理性能翻倍:

调用绘图的函数看起来像这样:

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倍,但理想情况下,我希望使圆形绘图更有效率,以进一步提高它。这将检查环是否完全位于屏幕之外。

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

作为对Clifford的回应,如果没有内联,实际上会有很多函数调用。

编辑4:drawRing只绘制2个圆圈,首先是模式为ON的外圆,然后是模式为OFF的内圆。我相信有一种更有效的方法来绘制这样的形状,但这会分散注意力。

4 个答案:

答案 0 :(得分: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;
    }
}

答案 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 ;
    }
}

在我的测试中,我根据here的代码使用了下面的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个字节) - 您可能会发现代码空间的字节数更少需要计算圆圈。

答案 2 :(得分:0)

对于只有13x13元素显示的慢速嵌入式设备,您应该只是制作一个查找表。例如:

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

绘图例程使用rMax来确定要使用的LUT元素。例如,如果你有2个元素,其中一个rMax = 1.4f,另一个= 1.7f,则1.4f和1.7f之间的任何半径都将使用该条目。

列元素将指定每行零个,一个或两个线段,可以在每个char的低4位和高4位进行编码。 -1可以用作此行中没有任何内容的标记值。您需要使用多少个查找表条目,但使用13x13网格时,您应该能够对100个条目下的每个像素结果进行编码,并且使用大约10个左右的合理近似值。您也可以折衷压缩以获得绘制速度,例如:将col [13] [2]矩阵放在一个平面列表中并编码定义的行数。

答案 3 :(得分:0)

如果只是他解释了他提出的更好的方法,我会接受MooseBoy的回答。这是我对查找表方法的看法。

使用查找表解决它

13x13显示器非常小,如果您只需要在此像素数内完全可见的圆圈,您将使用非常小的桌子。即使你需要更大的圆圈,如果你需要它更快(并且有ROM来存储它),它应该仍然比任何算法方式更好。

怎么做

您基本上需要定义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显示圆更大。

请注意,对于奇数和偶数直径,以这种方式定义圆可能会或可能不会在您按中心位置输出时吸引您。奇数直径的圆圈似乎在&#34;中间&#34;中间有一个中心。一个像素,而偶数直径的圆圈似乎将其中心放在&#34;角落#34;一个像素。

稍后您可能会发现很好地改进整体方法,因此具有不同表观大小的多个圆,但具有相同的像素半径。取决于你的目标是什么:如果你想要某种流畅的动画,你最终可能会到达那里。

我认为算法解决方案在这里表现不佳,因为在这个有限的显示器表面上,每个像素的状态都非常重要。