首页 > 编程语言 >Bresenham算法画椭圆

Bresenham算法画椭圆

时间:2023-09-06 09:55:06浏览次数:140  
标签:椭圆 2r int over Bresenham 2y 算法 p1

目录

椭圆特性

  • 椭圆定义

椭圆:平面内到定点F1、F2的距离之和等于常数2a(2a>|F1F2|)的动点P的轨迹。

椭圆数学表达式:

\[\tag{1} |PF1|+|PF2|=2a \]

F1、F2称为椭圆的2个焦点,两焦点之间距离2c(|F1F2|=2c)称为焦距
椭圆与两焦点连线相交所得弦为长轴,长2a,a也称为半长轴的长;椭圆与两焦点垂线相交所得弦为短轴,长2b,b也称为半短轴的长。

平面上动点P轨迹形成椭圆示意图:
b0c3ec57ee58072b735a2afd915143e5.png

  • 椭圆方程

以焦点F1、F2中点为原点,以F1->F2方向为x轴正方向,建立直角坐标系(局部坐标系)。记F2(c,0),则F1(-c, 0)。设P(x, y)为椭圆上任意一点。

根据椭圆定义,有

\[\begin{aligned} & |PF1| + |PF2| = 2a \\\\ & => \sqrt{(x+c)^2+y^2}+\sqrt{(x-c)^2+y^2} = 2a \\\\ & => \sqrt{(x+c)^2+y^2} = 2a - \sqrt{(x-c)^2+y^2}, 两边平方 \\\\ & => (x+c)^2+y^2=4a^2 + (x-c)^2+y^2 - 4a\sqrt{(x-c)^2+y^2} \\\\ & => 2xc=4a^2-2xc-4a\sqrt{(x-c)^2+y^2} \\\\ & => a^2-xc=a\sqrt{(x-c)^2+y^2}, 两边平方 \\\\ & => a^4-2a^2xc+(xc)^2=a^2[(x-c)^2+y^2] \\\\ & => (a^2-c^2)x^2+a^2y^2+a^2c^2=a^4 \\\\ & => (a^2-c^2)x^2+a^2y^2=a^2(a^2-c^2) \\\\ & => x^2/a^2+y^2/(a^2-c^2)=1 \end{aligned} \]

∵\(2a>2c>0\)
∴\(a^2-c^2>0\)
设\(b^2=a^2-c^2\)
则椭圆方程可写为:

\[\tag{2} x^2/a^2+y^2/b^2=1 (a>b>0) \]

a称为椭圆的半长焦距,b称为椭圆的半短焦距

方程(2)是以椭圆的中心点(2焦点中点)为原点,建立的局部坐标系,而在世界坐标系下,原点不一定在中点。设椭圆中心点坐标\((x_c, y_c)\),则椭圆方程:

\[\tag{3} ({x-x_c\over a})^2+({y-y_c\over b})^2=1 \]

可以写成通用形式:

\[\tag{4} Ax^2+By^2+Cxy+Dx+Ey+F=0 \]

其中,常数A,B,C,D,E,F可根据(3)求出。

为了便于下文讨论,我们用\(2r_x\)表示长轴,\(2r_y\)表示短轴。利用极坐标,椭圆方程可写成参数方程形式:

\[\tag{5} \begin{aligned} x=x_c+r_x\cos\theta \\\\ y=y_c+r_y\sin\theta \end{aligned} \]

θ称为椭圆离心角(eccentric angle)。

椭圆离心角的几何意义,见下图:
0b28a08825774e9dea053e174ede1717.png

Bresenham算法画椭圆

类似于Bresenham算法画光栅圆。

给定参数:半焦距\(r_x, r_y\), 中心点\((x_c, y_c)\)。先以中心点为原点建立局部坐标系,得到椭圆上的点(x,y)后,再将其移动到\((x_c, y_c)\)为中心点的椭圆上。如果长轴、短轴没有与x、y轴平行,就绕中心进行旋转,目前这里只考虑平移,不考虑旋转。

局部坐标系下,定义椭圆函数:

\[\tag{6} f_{ellipse}(x, y) = r_y^2x^2+r_x^2y^2-r_x^2r_y^2 \]

椭圆函数可以表示 点(x,y)和椭圆位置关系:

\[\tag{7} f_{ellipse}(x, y)=\begin{cases} < 0, & (x,y)在椭圆边界内 \\\\ = 0, & (x,y)在椭圆边界上 \\\\ > 0, & (x,y)在椭圆边界外 \end{cases} \]

\(f_{ellipse}(x, y)\)可用来作为算法决策参数。

  • 以x轴 or y轴逐像素计算?

本质是看x轴、y轴,哪个方向变化大,就按那个轴方向逐个像素计算点位置。如,画直线时,取决于斜率|m|与1的关系;画圆时,x、y轴方向变化相同,都可以。而画椭圆时,如何判断?

椭圆的切线斜率可以代表沿着曲线的x、y轴方向变化大小。

下图是\(r_y<r_x\)时,根据切削斜率对椭圆第一象限进行划分的示意图:
ace00fafd546c95e8c45b5fe539ecdbe.png

在区域1,斜率0~-1,所以\(|{dy\over dx}|<1\),可按+x轴方向逐像素绘制;
在区域2,斜率-1~-∞,所以\(|{dy\over dx}|>1\),可按-y轴方向逐像素绘制。

椭圆方程:

\[f_{ellipse}(x, y)=r_y^2x^2+r_x^2y^2-r_x^2r_y^2=0 \]

两边对x求导,

\[r_y^2(2x)+r_x^2(2y{dy\over dx}) - 0=0 \\\\ {dy\over dx}=-{r_y^2x \over r_x^2y} \tag{8} \]

区域1、2交接处,切线斜率\({dy\over dx}=-1\),即

\[-{r_y^2x \over r_x^2y}=-1 \\\\ => r_y^2x=r_x^2y \]

区域1:\({dy\over dx}=-{r_y^2x \over r_x^2y}>-1\),即\(r_y^2x < r_x^2y\)
区域2:\(r_y^2>=r_x^2y\)

区域1

  • 计算下一个点位置

因为在第一象限,y随着x增加而减少。当进行到第k步,像素点位置\((x_k, y_k)\),区域1中,下一个像素点位置只能是\((x_k+1, y_k)\) or \((x_k+1, y_k-1)\),可以用2备选点的中点与椭圆位置关系,判断哪个点距离椭圆更近,从而选择近点。
因此,区域1决策参数:

\[\tag{9} \begin{aligned} p1_k &= f_{ellipse}(x_k+1, y_k-1/2) \\\\ &= r_y^2(x_k+1)^2+r_x^2(y_k-1/2)^2-r_x^2r_y^2 \end{aligned} \]

由(7)可知,如果\(p1_k<0\),那么中点位于椭圆内,\(y_k\)点更接近椭圆;否则,\(y_k-1\)点更接近。

取k为k+1,有

\[\begin{aligned} p1_{k+1} &= f_{ellipse}(x_{k+1}, y_{k+1}-1/2) \\\\ & = r_y^2[(x_k+1)+1]^2 + r_x^2(y_{k+1}-1/2)^2 - r_x^2r_y^2 \end{aligned} \]

做差值,可得递推公式:

\[\begin{aligned} p1_{k+1}-p1_k &= 2r_y^2(x_k+1)+r_y^2+r_x^2[(y_{k+1}-{1\over 2})^2 - (y_k-{1\over 2})^2] \\\\ &= 2r_y^2x_{k+1}+r_y^2+r_x^2[(y_{k+1}-{1\over 2})^2-(y_k-{1\over 2})^2] \end{aligned} \]

而\(y_{k_1}\)值取决于\(p_k\):

\[y_{k+1}=\begin{cases} y_k, & y_k更近<=> p1_k<0 \\\\ y_k-1, & y_k-1更近<=> p1_k>=0 \end{cases} \]

因此,决策参数递推公式:

\[p_{k+1}-p_k=\begin{cases} 2x_{k+1}r_y^2+r_y^2, & p1_k<0 \\\\ 2x_{k+1}r_y^2+r_y^2-2y_{k+1}r_x^2, & p1_k >= 0 \end{cases} \]

初值\(p1_0\):起始点\((0, r_y)\),根据(9),可得,

\[\begin{aligned} p1_0 &= f_{ellipse}(1, r_y-{1\over 2} \\\\ & = r_y^2 + r_x^2(r_y-{1\over 2})^2-r_x^2r_y^2 \\\\ & = r_y^2 - r_x^2r_y+{1\over 4}r_x^2 \end{aligned} \]

区域2

从区域1与2的边界(切线斜率=-1)点开始,按-y方向逐像素取样。
假设进行到第k步,对应像素点\((x_k, y_k)\),那么第k+1步可选点:\((x_k, y_k-1)\) or \((x_k+1, y_k-1)\)。

2备选的中点\(f_{ellipse}(x_k+{1\over 2}, y_k-1)\),那么决策参数:

\[\begin{aligned} p2_k &= f_{ellipse}(x_k+{1\over 2}, y_k-1) \\\\ & = r_y^2(x_k+{1\over 2})^2 + r_x^2(y_k-1)^2-r_x^2r_y^2 \end{aligned} \]

若\(p2_k>0\),则中点在椭圆外,下一点选择\(x_k\)点;
若\(p2_k<=0\),则中点在椭圆内,下一点选择\(x_k+1\)点。

k取值k+1,可得

\[\begin{aligned} p2_{k+1} &= f_{ellipse}(x_{k+1}+{1\over 2}, y_{k+1}-1) \\\\ &= r_y^2(x_{k+1}+{1\over 2})^2 + r_x^2(y_{k+1}-1)^2-r_x^2r_y^2 \\\\ &= -2r_x^2(y_k-1)+r_x^2+r_y^2[(x_{k+1}+{1\over 2})^2-(x_k+{1\over 2})^2] \\\\ &= \begin{cases} -2r_x^2y_{k+1}+r_x^2, & x_k更近 <=>p2_k > 0 \\\\ -2r_x^2y_{k+1}+r_x^2+2r_y^2x_{k+1}, & x_k+1更近<=> p2_k<=0 \end{cases} \end{aligned} \]

初值\(p2_0\):区域2的起始点\((x_0, y_0)\)就是区域1的结束点。可以在区域1画完后,记录结束点作为区域2起始点。

  • 对称性求其他3象限椭圆

求出椭圆第一象限部分一点(x, y)后,可用对称性得到另外3个象限的点:(-x, y) (y, -x) (-x, -y)。

算法步骤

  1. 输入\(r_x, r_y\)、椭圆中心\((x_c, y_c)\),得到椭圆(中心在原点)上的第一个点 \((x_0, y_0)=(0, r_y)\);
  2. 计算区域1决策参数初值,

\[p1_0=r_y^2-r_x^2r_y+{1\over 4}r_x^2 \]

  1. 在区域1中,从k=0开始,对每个\(x_k\)位置的像素点进行计算:
    如果\(p1_k<0\),则椭圆下一个点为\((x_k+1, y_k)\),决策参数

\[p1_{k+1}=p1_k+2r_y^2x_{k+1}+r_y^2 \]

如果\(p1_k>=0\),则下一个\((x_k+1, y_k-1)\),决策参数

\[p1_{k+1}=p1_k+2r_y^2x_{k+1}-2r_x^2y_{k+1}+r_y^2 \]

这里\(x_{k+1}=x_k+1, y_{k+1}=y_k-1\)

重复该步骤,直到\(2r_y^2x>=2r_x^2y\)

  1. 记录区域1得到的最后一个点\((x_0, y_0)\),用例计算区域2决策参数初值,

\[p2_0=r_y^2()x_0+{1\over 2}^2+r_x^2(y_0-1)^2-r_x^2r_y^2 \]

  1. 在区域2中,从k=0开始,对每个\(y_k\)位置的像素点进行计算:
    如果\(p2_k>0\),则椭圆下一点\((x_k, y_k-1)\),决策参数

\[p2_{k+1}=p2_k-2r_x^2y_{k+1}+r_x^2 \]

如果\(p2_k<=0\),则椭圆下一点\((x_k+1, y_k-1)\),决策参数

\[p2_{k+1}=p2_k+2r_y^2x_{k+1}-2r_x^2y_{k+1}+r_x^2 \]

重复该步骤,直到y=0(终点为\((r_x, 0)\))
6. 通过对称性获得其他3个象限的对称点;
7. 将得到的椭圆点位置(x, y)由原点为椭圆中心平移到\((x_c, y_c)\),得到最终像素点位置,有\(x=x+x_c, y=y+y_c\)。

算法程序

void setPixel(int x, int y)
{
       glBegin(GL_POINTS);
       glVertex2i(x, y);
       glEnd();
}

// 浮点数向上取整
inline int round(const float a)
{
       return (int)(a + 0.5);
}

// 绘制椭圆上的点(x, y)及其对称点, 椭圆中心(xc, yc)
// (x, y)是原点在椭圆中心的局部坐标系下的坐标
void ellipsePlotPoints(int xc, int yc, int x, int y)
{
       setPixel(xc + x, yc + y);
       // 对称点
       setPixel(xc - x, yc + y);
       setPixel(xc + x, yc - y);
       setPixel(xc - x, yc - y);
}

// 绘制中心点在(xc, yc)的椭圆, x轴半焦距rx, y轴半焦距ry
// 使用Bresenham算法思想, 根据2备选像素点位置的中点与椭圆位置关系, 
// 判断并选择距离椭圆更近的点作为下一个点
void ellipseMidpoint(int xc, int yc, int rx, int ry)
{
       int rx2 = rx * rx;
       int ry2 = ry * ry;
       int p;
       int x = 0, y = ry; // (区域1)初始点
       int px = 0, py = 2 * rx2 * y; // 像素计算终止条件项

       // 绘制椭圆起始点
       ellipsePlotPoints(xc, yc, x, y);

       // 区域1
       p = round(ry2 - rx2 * ry + 0.25 * rx2);
       while (px < py) {
              x++;
              px += 2 * ry2; // 通过累加来计算 2ry^2 x[k+1], 而不是每次都用乘法
              if (p < 0) {
                     p += ry2 + px;
              }
              else {
                     y--;
                     py -= 2 * rx2;
                     p += ry2 + px - py;
              }
              ellipsePlotPoints(xc, yc, x, y);
       }

       // 区域2
       p = round(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * 
ry2);
       while (y > 0) {
              y--;
              py -= 2 * rx2;
              if (p > 0) {
                     p += rx2 - py;
              }
              else {
                     x++;
                     px += 2 * ry2;
                     p += rx2 - py + px;
              }
              ellipsePlotPoints(xc, yc, x, y);
       }
}
```

标签:椭圆,2r,int,over,Bresenham,2y,算法,p1
From: https://www.cnblogs.com/fortunely/p/17681502.html

相关文章

  • 【校招VIP】前端算法考察之链表算法
    考点介绍:链表是一种物理存储结构上非连续的数据结构,数据的逻辑顺序是通过链表中的指针链接次序实现相互勾连。链表相对数组而言有很多不同之处,在特定场景下能发挥独特的优势。例如链表的插入和删除操作比数组效率高,数组需要改变其他元素的位置,而链表只需要改变指针的指向。......
  • 扩展欧几里得算法
    扩展欧几里得算法问题引入求\(ax+by=\gcd(a,b)\)的一组整数解。前置知识欧几里得算法当\(a,b\)为非负整数时,以下等式一定成立。\[\gcd(a,b)=\gcd(b,a\bmodb)\]裴蜀定理对于任意非负整数\(a,b\),一定存在满足的整数对\((x,y)\),使得以下等式成立。\[ax+b......
  • 算法衡量优劣之时间复杂度
     选型我们假定计算机执行算法每一个基本操作的时间是固定的一个时间单位,那么有多少个基本操作就代表会花费多少时间单位,由此可以忽略机器环境的影响而客观的反应算法的时间效率代码执行总时间(T)=操作步骤数量*操作步骤执行时间 算法时间复杂度是用来描述算法在......
  • 代码随想录算法训练营第十四天|二叉树的递归法、迭代法
    二叉树的递归遍历(前中后序遍历-递归法与迭代法)递归三部曲:确定递归函数的参数和返回值确定终止条件确定单层递归的逻辑递归法对二叉树进行前中后序遍历(力扣144.145.94.)//前序遍历·递归·LC144_二叉树的前序遍历classSolution{publicList<Integer>preorderTra......
  • Java实现常见排序算法
    Java实现常见排序算法排序也称排序算法(SortAlgorithm),排序是将一组数据,依指定的顺序进行排列的过程。排序的分类:内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序。外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。常见的排序算法分......
  • 找质数(图算法)、交错字符串(字符串、动态规划)、有效数字(字符串)
    找质数(图算法)找出大于200的最小的质数解答:importjava.util.*;importjava.lang.*;importjava.io.*;classIdeone{publicstaticvoidmain(String[]args)throwsjava.lang.Exception{intn=201;while(true){booleanb=tru......
  • 梯度上升算法
    用梯度上升算法进行Logistic回归$w=w+\nabla{f(w)}$对应代码如下importmatplotlib.pyplotaspltimportnumpyasnpfromsklearn.datasetsimportmake_classificationdata_1,labels=make_classification(n_samples=400,n_features=2,n_informative=2,n_redundant=0,n_......
  • 随机森林算法如何用代码实现
    随机森林是一种集成学习算法,通过组合多个决策树来进行分类和回归任务,从而提高预测的稳定性和准确性。以下是使用Python中的sklearn库实现随机森林算法的基本示例:fromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_splitfromsklearn.ensemb......
  • 深度神经网络中基于卷积操作的自适应学习算法研究
    本文提出了一种基于卷积操作的自适应学习算法,用于深度神经网络中。该算法通过引入复杂的数学公式和高阶张量操作,实现了对复杂模式的准确建模和学习。我们通过对网络架构的改进和参数的优化,提高了模型的泛化能力和性能表现。实验结果表明,我们的算法在多个基准数据集上取得了优于现有......
  • 遗传算法
     遗传算法(GeneticAlgorithm)是一种基于自然选择原理和自然遗传机制的启发式搜索算法。该算法通过模拟自然界中生物遗传进化的自然机制(选择、交叉和变异操作),将好的遗传基因(最优目标)不断遗传给子代,使得后代产生最优解的概率增加示例代码如下:#导入所需的库importrandomimportmat......