计算机图形学——利用MFC库绘制圆和椭圆(中点画圆算法)

计算机图形学——利用MFC库绘制圆和椭圆(中点画圆算法)

写在前面:关于对话框设置和组件ID设置等相关知识见专栏第一篇文章《直线绘制》

一、问题描述

利用中点画圆算法,在MFC库中绘制圆和椭圆

二、算法描述

1. 圆

易知 fcircle(x,y)=x2+y2−r2f_{circle}(x,y)=x^2+y^2-r^2fcircle​(x,y)=x2+y2−r2

利用轴对称,只需要绘制第一象限内的1/4圆弧,然后对称绘制到其他三个象限即可。

第一象限上半部分的1/8圆弧(以x为增量)

我们可以通过检查 pk=fcircle(xk+1,yk−12)p_k=f_{circle}(x_k+1,y_k-\frac{1}{2})pk​=fcircle​(xk​+1,yk​−21​) 的符号来判断下一个点绘制的位置

设置该段圆弧绘制的起始点为 (0,r)(0,r)(0,r) ,并据此计算出 p0=fcircle(0+1,r−12)=54−rp_0=f_{circle}(0+1,r-\frac{1}{2})=\frac{5}{4}-rp0​=fcircle​(0+1,r−21​)=45​−r

计算可知

pk+1−pk={2xk+1+1pk<02xk+1+1−2yk+1pk≥0p_{k+1}-p_k=

\begin{cases}

2x_{k+1}+1 & p_k<0 \\

2x_{k+1}+1-2y_{k+1} & p_k\geq 0

\end{cases}pk+1​−pk​={2xk+1​+12xk+1​+1−2yk+1​​pk​<0pk​≥0​

其中

xk+1=xk+1, yk+1={ykpk<0yk−1pk≥0x_{k+1}=x_k+1, \

y_{k+1}=

\begin{cases}

y_k & p_k<0 \\

y_k-1 & p_k\geq 0

\end{cases}xk+1​=xk​+1, yk+1​={yk​yk​−1​pk​<0pk​≥0​

假设当前 (xk,yk)(x_k,y_k)(xk​,yk​) 确定,那么通过 pkp_kpk​的正负来确定下一个点 (xk+1,yk+1)(x_{k+1},y_{k+1})(xk+1​,yk+1​)

设置循环,从起点开始绘制,终止条件为 x>yx>yx>y (也就是说第一象限上半部分圆弧的绘制必须保证 x≤yx\leq yx≤y )

第一象限下半部分的1/8圆弧(以y为增量)

通过检查 pk=fcircle(xk−12,yk+1)p_k=f_{circle}(x_k-\frac{1}{2},y_k+1)pk​=fcircle​(xk​−21​,yk​+1) 的符号判断下一个点绘制的位置

设置该段圆弧绘制的起始点为 (r,0)(r,0)(r,0) ,并据此计算出 p0=fcircle(r−12,0+1)=54−rp_0=f_{circle}(r-\frac{1}{2},0+1)=\frac{5}{4}-rp0​=fcircle​(r−21​,0+1)=45​−r

计算可知

pk+1−pk={2yk+1+1pk<02yk+1+1−2xk+1pk≥0p_{k+1}-p_k=

\begin{cases}

2y_{k+1}+1 & p_k<0 \\

2y_{k+1}+1-2x_{k+1} & p_k\geq 0

\end{cases}pk+1​−pk​={2yk+1​+12yk+1​+1−2xk+1​​pk​<0pk​≥0​

其中

xk+1={xkpk<0xk−1pk≥0, yk+1=yk+1x_{k+1}=

\begin{cases}

x_k & p_k<0 \\

x_k-1 & p_k\geq 0

\end{cases},\

y_{k+1}=y_k+1xk+1​={xk​xk​−1​pk​<0pk​≥0​, yk+1​=yk​+1

假设当前 (xk,yk)(x_k,y_k)(xk​,yk​) 确定,那么通过 pkp_kpk​的正负来确定下一个点 (xk+1,yk+1)(x_{k+1},y_{k+1})(xk+1​,yk+1​)

设置循环,从起点开始绘制,终止条件为 x

2. 椭圆

易知 fellipsoid=b2x2+a2y2−a2b2f_{ellipsoid}=b^2x^2+a^2y^2-a^2b^2fellipsoid​=b2x2+a2y2−a2b2 ,其中a表示x轴上的半轴长,b表示y轴上的半轴长

利用轴对称,只需绘制第一象限内的1/4椭圆弧,即可对称绘制到另外三个象限

第一象限上半部分椭圆弧(以x为增量)

通过检查 pk=fellipsoid(xk+1,yk−12)p_k=f_{ellipsoid}(x_k+1,y_k-\frac{1}{2})pk​=fellipsoid​(xk​+1,yk​−21​) 的符号判断下一个点绘制的位置

设置该段圆弧绘制的起始点为 (0,b)(0,b)(0,b) ,并据此计算出

p0=fellipsoid(0+1,b−12)=b2+14a2−a2bp_0=f_{ellipsoid}(0+1,b-\frac{1}{2})=b^2+\frac{1}{4}a^2-a^2bp0​=fellipsoid​(0+1,b−21​)=b2+41​a2−a2b

计算可知

pk+1−pk={b2 (2xk+1+1)pk<0b2 (2xk+1+1)−2a2yk+1pk≥0p_{k+1}-p_k=

\begin{cases}

b^2 \ (2x_{k+1}+1) & p_k<0 \\

b^2 \ (2x_{k+1}+1)-2a^2y_{k+1} & p_k\geq 0

\end{cases}pk+1​−pk​={b2 (2xk+1​+1)b2 (2xk+1​+1)−2a2yk+1​​pk​<0pk​≥0​

其中

xk+1=xk+1, yk+1={ykpk<0yk−1pk≥0x_{k+1}=x_k+1, \

y_{k+1}=

\begin{cases}

y_k & p_k<0 \\

y_k-1 & p_k\geq 0

\end{cases}xk+1​=xk​+1, yk+1​={yk​yk​−1​pk​<0pk​≥0​

假设当前 (xk,yk)(x_k,y_k)(xk​,yk​) 确定,那么通过 pkp_kpk​的正负来确定下一个点 (xk+1,yk+1)(x_{k+1},y_{k+1})(xk+1​,yk+1​)

设置循环,从起点 (0,b)(0,b)(0,b) 开始绘制

由于椭圆的切线斜率为 −b2xa2y-\frac{b^2x}{a^2y}−a2yb2x​,当切线斜率为 −1-1−1 时,标志着以x为增量的绘制结束

因此终止条件为 b2x>a2yb^2x>a^2yb2x>a2y(也就是说第一象限上半部分椭圆弧的绘制必须保证 b2x≤a2yb^2x\leq a^2yb2x≤a2y )

第一象限下半部分椭圆弧(以y为增量)

通过检查 pk=fellipsoid(xk−12,yk+1)p_k=f_{ellipsoid}(x_k-\frac{1}{2},y_k+1)pk​=fellipsoid​(xk​−21​,yk​+1) 的符号判断下一个点绘制的位置

设置该段圆弧绘制的起始点为 (a,0)(a,0)(a,0) ,并据此计算出

p0=fellipsoid(a−12,0+1)=a2+14b2−b2ap_0=f_{ellipsoid}(a-\frac{1}{2},0+1)=a^2+\frac{1}{4}b^2-b^2ap0​=fellipsoid​(a−21​,0+1)=a2+41​b2−b2a

计算可知

pk+1−pk={a2 (2yk+1+1)pk<0a2 (2yk+1+1)−2b2xk+1pk≥0p_{k+1}-p_k=

\begin{cases}

a^2 \ (2y_{k+1}+1) & p_k<0 \\

a^2 \ (2y_{k+1}+1)-2b^2x_{k+1} & p_k\geq 0

\end{cases}pk+1​−pk​={a2 (2yk+1​+1)a2 (2yk+1​+1)−2b2xk+1​​pk​<0pk​≥0​

其中

xk+1={xkpk<0xk−1pk≥0, yk+1=yk+1x_{k+1}=

\begin{cases}

x_k & p_k<0 \\

x_k-1 & p_k\geq 0

\end{cases},\

y_{k+1}=y_k+1xk+1​={xk​xk​−1​pk​<0pk​≥0​, yk+1​=yk​+1

假设当前 (xk,yk)(x_k,y_k)(xk​,yk​) 确定,那么通过 pkp_kpk​的正负来确定下一个点 (xk+1,yk+1)(x_{k+1},y_{k+1})(xk+1​,yk+1​)

设置循环,从起点 (a,0)(a,0)(a,0) 开始绘制

类似的,易知终止条件为 a2y>b2xa^2y>b^2xa2y>b2x(也就是说第一象限上半部分椭圆弧的绘制必须保证 a2y≤b2xa^2y\leq b^2xa2y≤b2x )

三、核心代码

关于CircleDlg.h和对于对话框内组件的ID设置,此处略去,仅展示CircleDlg.cpp中的相关代码

1. 圆

// 点击按钮开始绘制圆

void CCircleDlg::ButtonCircle() {

// 从编辑框中获取输入的半径并转换为整型

CString Radius;

GetDlgItemText(IDC_EDIT_CIRCLE_R, Radius);

int R = _ttoi(Radius);

// 获取绘图矩形

CRect AreaCircle;

GetDlgItem(IDC_STATIC_AREA_CIRCLE)->GetClientRect(AreaCircle);

// 检查是否会越界并绘制

if (R > 0 && R <= AreaCircle.Width()/2 && R <= AreaCircle.Height()/2) {

DrawCircle(R);

}

else {

CString message = _T("半径超出绘图框的边界,请重新输入");

CString caption = _T("圆绘制错误");

MessageBox(message, caption, MB_ICONERROR);

}

}

// 画圆

void CCircleDlg::DrawCircle(int r) {

// 获取画板

CStatic* Area = (CStatic*)GetDlgItem(IDC_STATIC_AREA_CIRCLE);

CDC* pdc = Area->GetDC();

// 获取绘图矩形

CRect AreaCircle;

GetDlgItem(IDC_STATIC_AREA_CIRCLE)->GetClientRect(AreaCircle);

double dx = AreaCircle.Width() / 2.0;

double dy = AreaCircle.Height() / 2.0;

// 初始化P0和起始点

double p = 5.0 / 4 - r;

int x = 0, y = r;

// 先绘制1/8个圆弧(第一象限上半部分),并根据轴对称绘制另外3个1/8圆弧

for (int x = 0; x <= y; ) {

// 绘制当前像素点

pdc->SetPixel(x + dx, y + dy, RGB(0, 0, 0));

// 轴对称绘制其他像素点

pdc->SetPixel(-x + dx, y + dy, RGB(0, 0, 0));

pdc->SetPixel(-x + dx, -y + dy, RGB(0, 0, 0));

pdc->SetPixel(x + dx, -y + dy, RGB(0, 0, 0));

// 根据Pk的值决定下一个像素点

if (p < 0) {

x += 1;

p = p + 2.0 * x + 1;

}

else {

x += 1;

y -= 1;

p = p + 2.0 * (x - y) + 1;

}

}

// 绘制第一象限下半部分的1/8圆弧,并根据轴对称绘制剩下3个1/8圆弧

// 起始点选取(r,0),重新初始化起始点和P0

x = r, y = 0;

p = 5.0 / 4 - r;

for (y = 0; y <= x; ) {

// 绘制当前像素点

pdc->SetPixel(x + dx, y + dy, RGB(0, 0, 0));

// 轴对称绘制其他像素点

pdc->SetPixel(-x + dx, y + dy, RGB(0, 0, 0));

pdc->SetPixel(-x + dx, -y + dy, RGB(0, 0, 0));

pdc->SetPixel(x + dx, -y + dy, RGB(0, 0, 0));

// 根据Pk的值决定下一个像素点

if (p <= 0) {

y += 1;

p = p + 2 * y + 1;

}

else {

x -= 1;

y += 1;

p = p + 2 * (y - x) - 1;

}

}

// 释放设备

Area->ReleaseDC(pdc);

}

2. 椭圆

// 椭圆的生成按钮

void CCircleDlg::ButtonEllipsoid() {

// 从编辑框中获取输入的长短轴并转换为整型

CString xAxis, yAxis;

GetDlgItemText(IDC_EDIT_ELLPISOID_A, xAxis);

GetDlgItemText(IDC_EDIT_ELLPISOID_B, yAxis);

int A = _ttoi(xAxis); // x轴方向,记为长轴

int B = _ttoi(yAxis); // y轴方向,记为短轴

// 获取绘图区

CRect AreaEllipsoid;

GetDlgItem(IDC_STATIC_AREA_ELLIPSOID)->GetClientRect(AreaEllipsoid);

// 检查是否会越界并绘制

if (A > 0 && B > 0 && A <= AreaEllipsoid.Width()/2 && B <= AreaEllipsoid.Height()/2) {

DrawEllipsoid(A, B);

}

else {

CString message = _T("轴长超出绘图框的边界,请重新输入");

CString caption = _T("椭圆绘制错误");

MessageBox(message, caption, MB_ICONERROR);

}

}

// 画椭圆

void CCircleDlg::DrawEllipsoid(int a, int b) {

// 获取画板

CStatic* Area = (CStatic*)GetDlgItem(IDC_STATIC_AREA_ELLIPSOID);

CDC* pdc = Area->GetDC();

// 获取绘图矩形

CRect AreaEllipsoid;

GetDlgItem(IDC_STATIC_AREA_ELLIPSOID)->GetClientRect(AreaEllipsoid);

double dx = AreaEllipsoid.Width() / 2.0;

double dy = AreaEllipsoid.Height() / 2.0;

// 初始化P0和起始点(0,b)

double p = b * b - a * a * b + (1.0 / 4) * a * a;

int x = 0, y = b;

// 先绘制第一象限上半部分的椭圆弧,并根据轴对称绘制另外3个椭圆弧

for (int x = 0; b * b * x <= a * a * y; ) {

// 绘制当前像素点

pdc->SetPixel(x + dx, y + dy, RGB(0, 0, 0));

// 轴对称绘制其他像素点

pdc->SetPixel(-x + dx, y + dy, RGB(0, 0, 0));

pdc->SetPixel(-x + dx, -y + dy, RGB(0, 0, 0));

pdc->SetPixel(x + dx, -y + dy, RGB(0, 0, 0));

// 根据Pk的值决定下一个像素点

if (p < 0) {

x += 1;

p = p + b * b * (2 * x + 1);

}

else {

x += 1;

y -= 1;

p = p + b * b * (2 * x + 1) - 2 * a * a * y;

}

}

// 绘制第一象限下半部分的椭圆弧,并根据轴对称绘制剩下3个

// 起始点选取(a,0),重新初始化起始点和P0

x = a, y = 0;

p = a * a - b * b * a + 0.25 * b * b;

for (y = 0; a * a * y <= b * b * x; ) {

// 绘制当前像素点

pdc->SetPixel(x + dx, y + dy, RGB(0, 0, 0));

// 轴对称绘制其他像素点

pdc->SetPixel(-x + dx, y + dy, RGB(0, 0, 0));

pdc->SetPixel(-x + dx, -y + dy, RGB(0, 0, 0));

pdc->SetPixel(x + dx, -y + dy, RGB(0, 0, 0));

// 根据Pk的值决定下一个像素点

if (p < 0) {

y += 1;

p = p + a * a * (2 * y + 1);

}

else {

x -= 1;

y += 1;

p = p + a * a * (2 * y + 1) - 2 * b * b * x;

}

}

// 释放设备

Area->ReleaseDC(pdc);

}

四、实验结果

如下图,分别在输入框中输入不同的值并点击"GENERATE",即可绘制:

若输入的半径超过绘图框边界,则弹出错误提示:

若输入的椭圆轴长超过绘图框边界,类似的弹出错误提示:

相关推荐

Mac 运行缓慢? 如何给运行缓慢的 Mac 电脑提速
beat365官网地址下载

Mac 运行缓慢? 如何给运行缓慢的 Mac 电脑提速

⌛ 08-02 👁️ 598
蓝牙耳机配件
beat365官网地址下载

蓝牙耳机配件

⌛ 07-08 👁️ 8130
凯尔特人
365有没有反水的

凯尔特人

⌛ 07-10 👁️ 9598