Bresenham算法画椭圆和斜椭圆

CG课程的第一次作业,大四才开始学CG也算是很特别【然后就迟交了一天】。

 

Bresenham算法用于把连续曲线投影到平面像素中,思想是只要能判断x和y哪个增量更大,就可以按x+1(或y+1)之后y(或x)是否+1来画下一个像素。判断是用x还是y的标准是斜率大于1还是小于1,在这个基础上网上能够搜到的椭圆画法做了一些优化,1.只用画一个象限内的曲线,另外三个象限直接对称过去;2.以x增量更大(斜率小于1)的情况为例,判断y是否+1的方法不是把y和y+1都算一遍,而是用y+0.5在圆内还是圆外判断;3.再进一步,每次都带入方程去算d=F(x,y+0.5)也很慢,可以考虑用算增量代替。这部分我觉得讲得比较好的是华中科技大学的这篇博客

 

比较麻烦的是斜椭圆的情况,首先,按照前面的思路,要重新把公式推一遍,斜椭圆的公式是在椭圆的基础上把x和y用旋转变换替换,你博能不能插入公式啊,试试:

椭圆公式:

$$ \frac{x^{2}}{a^{2}}+\frac{y^{2}}{b^{2}}=1 $$

旋转变换:

$$
\left[\begin{array}{cc}
\cos \beta & -\sin \beta \\
\sin \beta & \cos \beta
\end{array}\right]\left[\begin{array}{l}
x \\
y
\end{array}\right]=\left[\begin{array}{l}
x \cos \beta-y \sin \beta \\
x \sin \beta+y \cos \beta
\end{array}\right]
$$

代入得到斜椭圆公式:

$$
\frac{(x \cos \beta-y \sin \beta)^{2}}{a^{2}}+\frac{(x \sin \beta+y \cos \beta)^{2}}{b^{2}}=1
$$

在这里推荐Mathpix Snipping Tool,远离手打latex从我做起【不是】

得到曲线函数之后,按照算法核心思想,首先要判断的是切线斜率,在这里要用到隐函数求导:

$$
\frac{d y}{d x}=-\frac{F_{x}}{F_{y}}
$$

其中:

$$
F(x,y)=\frac{(x \cos \beta-y \sin \beta)^{2}}{a^{2}}+\frac{(x \sin \beta+y \cos \beta)^{2}}{b^{2}}-1
$$

为了方便编程,我去掉了上面对椭圆曲线做的三个优化,仅考虑最简单的Bresenham算法,那么公式的推导就可以到此为止了。对于优化1,在斜椭圆本来就不成立,斜椭圆不再有四个象限的对称性质,只画第一象限不能完成曲线;但是有中心对称,所以我只画了第一象限和第四象限,另外两个象限对称处理。对于优化2,最大的问题是斜椭圆在同一象限内的凹凸性不再保持不变,而F(x,y+0.5)在圆外代表我们该取y还是y+1其实是和凹凸性有关的,画个图应该很好理解。如果想要保留这个优化,就需要去计算凹凸性,也就是求隐函数二阶导,太麻烦了,我选择每次计算一下F(x,y)和F(x,y+1)。

 

最终代码如下,前面两个函数是助教给的生成ppm文件的代码

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<math.h>
//#include<cstdlib>
using namespace std;

void ppmRead(char* filename, unsigned char* data, int* w, int* h) {
    char header[1024];
    FILE* fp = NULL;
    int line = 0;

    fp = fopen(filename, "rb");
    while (line < 2) {
        fgets(header, 1024, fp);
        if (header[0] != '#') {
            ++line;
        }
    }
    sscanf(header, "%d %d\n", w, h);
    fgets(header, 20, fp);
    fread(data, (*w)*(*h) * 3, 1, fp);

    fclose(fp);
}
void ppmWrite(const char* filename, unsigned char* data, int w, int h) {
    FILE* fp;
    fp = fopen(filename, "wb");

    fprintf(fp, "P6\n%d %d\n255\n", w, h);
    fwrite(data, w*h * 3, 1, fp);

    fclose(fp);
}

void draw_ellipse(int ex,int ey,float ra,float rb)
{
    unsigned char final_data[400*300*3] = { 0 };
    char data[400][300];
    int x=0;
    int y=rb;
    float d1=rb*rb+ra*ra*(-rb+0.25);
    while(rb*rb*((float)x+1)<ra*ra*((float)y-0.5))
    {
        data[x+ex][y+ey]=255;
//        cout<<x<<" "<<y<<endl;
        data[-x+ex][y+ey]=255;
        data[-x+ex][-y+ey]=255;
        data[x+ex][-y+ey]=255;
        if(d1<=0)
        {
            d1=d1+rb*rb*(2*(float)x+3);
            x=x+1;
        }
        else
        {
            d1=d1+rb*rb*(2*(float)x+3)+ra*ra*(-2*(float)y+2);
            x=x+1;
            y=y-1;
        }
    }
    
    float d2=rb*rb*((float)x+0.5)*((float)x+0.5)+ra*ra*((float)y-1)*((float)y-1)-ra*ra*rb*rb;
    while(y>=0)
    {
        data[x+ex][y+ey]=255;
        data[-x+ex][y+ey]=255;
        data[-x+ex][-y+ey]=255;
        data[x+ex][-y+ey]=255;
        if(d2<=0)
        {
            d2=d2+rb*rb*(2*(float)x+2)+ra*ra*(-2*(float)y+3);
            x=x+1;
            y=y-1;
        }
        else
        {
            d2=d2+ra*ra*(-2*(float)y+3);
            y=y-1;
        }
    }
    for(int i=0;i<300;i++)
     for(int j=0;j<400;j++)
     {
          final_data[(i*400+j)*3]=data[j][i];
        final_data[(i*400+j)*3+1]=data[j][i];
        final_data[(i*400+j)*3+2]=data[j][i];
     }
    ppmWrite("test.ppm", final_data, 400, 300);
}

float cal_fx(float ra,float rb,float the,float x,float y)
{
    return 2*rb*rb*(x*cos(the)-y*sin(the))*cos(the)+2*ra*ra*(x*sin(the)+y*cos(the))*sin(the);
}

float cal_fy(float ra,float rb,float the,float x,float y)
{
    return -2*rb*rb*(x*cos(the)-y*sin(the))*sin(the)+2*ra*ra*(x*sin(the)+y*cos(the))*cos(the);
}

float cal_slope(float ra,float rb,float the,float x,float y)
{
    return -cal_fx(ra,rb,the,x,y)/cal_fy(ra,rb,the,x,y);
}

float cal_f(float ra,float rb,float the,float x,float y)
{
    return rb*rb*(x*cos(the)-y*sin(the))*(x*cos(the)-y*sin(the))+
            ra*ra*(x*sin(the)+y*cos(the))*(x*sin(the)+y*cos(the))-ra*ra*rb*rb;
}

void draw_ellipse_bonus(int ex,int ey,float ra,float rb,float the)
{
    unsigned char final_data[400*300*3] = { 0 };
    char data[400][300];
    int x=0;
    int y=(int)(sqrt(1.0/(sin(the)*sin(the)/ra/ra+cos(the)*cos(the)/rb/rb))+0.5);
    float s1=cal_slope(ra,rb,the,x,y);
    while(y>=0)
    {
//        system("pause");
        data[x+ex][y+ey]=255;
        data[-x+ex][-y+ey]=255;
        s1=cal_slope(ra,rb,the,x,y);
        cout<<x<<" "<<y<<" "<<s1<<endl;
        if(abs(s1)<1)
        {
            x=x+1;
            if(abs(cal_f(ra,rb,the,x,y-1))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x,y-1))<abs(cal_f(ra,rb,the,x,y+1)))
            {
                y=y-1;
            }
            if(abs(cal_f(ra,rb,the,x,y+1))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x,y+1))<abs(cal_f(ra,rb,the,x,y-1)))
            {
                y=y+1;
            }
        }
        else {
            y=y-1;
            if(abs(cal_f(ra,rb,the,x-1,y))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x-1,y))<abs(cal_f(ra,rb,the,x+1,y)))
            {
                x=x-1;
            }
            if(abs(cal_f(ra,rb,the,x+1,y))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x+1,y))<abs(cal_f(ra,rb,the,x-1,y)))
            {
                x=x+1;
            }
        }
    }
    x=0;
    y=-(int)(sqrt(1.0/(sin(the)*sin(the)/ra/ra+cos(the)*cos(the)/rb/rb))+0.5);
    s1=cal_slope(ra,rb,the,x,y);
    while(y<=0)
    {
//        system("pause");
        data[x+ex][y+ey]=255;
        data[-x+ex][-y+ey]=255;
        s1=cal_slope(ra,rb,the,x,y);
        cout<<x<<" "<<y<<" "<<s1<<endl;
        if(abs(s1)<1)
        {
            x=x+1;
            if(abs(cal_f(ra,rb,the,x,y-1))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x,y-1))<abs(cal_f(ra,rb,the,x,y+1)))
            {
                y=y-1;
            }
            if(abs(cal_f(ra,rb,the,x,y+1))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x,y+1))<abs(cal_f(ra,rb,the,x,y-1)))
            {
                y=y+1;
            }
        }
        else {
            y=y+1;
            if(abs(cal_f(ra,rb,the,x-1,y))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x-1,y))<abs(cal_f(ra,rb,the,x+1,y)))
            {
                x=x-1;
            }
            if(abs(cal_f(ra,rb,the,x+1,y))<abs(cal_f(ra,rb,the,x,y))
                &&abs(cal_f(ra,rb,the,x+1,y))<abs(cal_f(ra,rb,the,x-1,y)))
            {
                x=x+1;
            }
        }
    }
    for(int i=0;i<300;i++)
     for(int j=0;j<400;j++)
     {
          final_data[(i*400+j)*3]=data[j][i];
        final_data[(i*400+j)*3+1]=data[j][i];
        final_data[(i*400+j)*3+2]=data[j][i];
     }
    ppmWrite("test.ppm", final_data, 400, 300);
}

int main()
{
     draw_ellipse(200,150,60,70);
//    draw_ellipse_bonus(200,150,60,70,0.5);
    return 0;
}

得到的ppm图片,博客园不支持上传,那就不传啦。

 

其实这个代码多半还是有bug的,bug在于在第一象限遍历的方式是从x=0出发(取大于0的y值),x++或者y++直到y小于等于0;在第四象限遍历的方式是从x=0出发(取小于0的y值),x++或者y--直到y大于等于0。脑跑了一下觉得好像很ok,但是总觉得并不是很放心。不过当时已经过ddl快24小时了,就先一两个小时rush一个能交的出来了。。。。

上一篇:图片上传接口写法


下一篇:TC13986SubRectangles