计算机图形学学习python实现2 (绘制圆和椭圆) (中点Bresenham画圆法)(完整代码为鼠标绘制)
本文主要用python实现了用Bresenham画圆法绘制圆和椭圆,完整代码为用鼠标实时绘制图像。
圆的对称性
下图表示一个圆,图中点 (x,y)(x,y)(x,y) 位于第1/8象限,利用圆具有的对称性,可以将该点映射到其他的7/8象限上,得到圆周上的其他七个点,即(y,x)(y,x)(y,x)、(y,−x)(y,-x)(y,−x)、(x,−y)(x,-y)(x,−y)、(−x.−y)(-x.-y)(−x.−y)、(−y,−x)(-y,-x)(−y,−x)、(−y,x)(-y,x)(−y,x)、(−x,y)(-x,y)(−x,y)。这样我们只需要扫描计算从x=0x=0x=0到x=yx=yx=y这段圆弧就可以得到整个圆的所有像素点的位置了。
需要注意的是,在画圆心在窗口任意位置的圆时,需要将每个要画的点的坐标进行相应的移动,移动的方向和距离是待画圆圆心和坐标系原点的相对位置和距离。
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
Bresenham画圆法
考虑到圆的对称性,我们只画1/8圆弧。假设当前 P(xi,yi)P(x_i,y_i)P(xi,yi) 为当前最佳逼近圆的像素点,
Bresenham画圆法的思想是:引入 NE 和 E 的中点 M,如果 M 位于圆内,则 NE 点比较接近圆,所以下一理想的像素点应取 NE 点;反之,如果 M 点在圆外,则 E 点比较接近圆,所以下一理想的像素点应取 E 点。
注:图片来源于:黄静.计算机图形学及其实践教程[M].北京:机械工业出版社,2015.5:43
初始值:待画圆的圆心坐标 (x1,y1)(x_1,y_1)(x1,y1),圆的半径 rrr。用 (x0,y0)(x_0,y_0)(x0,y0) 来表示即将要画的点。
将圆的圆心移动到原点上,则圆的直角坐标方程可表示为:
x2+y2=r2 x^2+y^2=r^2 x2+y2=r2
化为一般式:
F(x,y)=x2+y2−r2=0 F(x,y)=x^2+y^2-r^2=0 F(x,y)=x2+y2−r2=0
由数学知识可知:
(1)F(x,y)=0(1) F(x,y)=0(1)F(x,y)=0,点在圆上;
(2)F(x,y)>0(2) F(x,y)>0(2)F(x,y)>0,点在圆外;
(1)F(x,y)<0(1) F(x,y)<0(1)F(x,y)<0,点在圆内。
从圆的上顶点 (0,r)(0,r)(0,r) 开始从左往右绘制,则初始值为:
x0=0y0=rw=F(M)=F(x0+1,y0−0.5)=(0+1)2+(r−0.5)2−r2=1.25−r \begin{align*} x_0&=0\\ y_0&=r\\ w&=F(M)=F(x_0+1,y_0-0.5)\\ &=(0+1)^2+(r-0.5)^2-r^2\\ &=1.25-r \end{align*} x0y0w=0=r=F(M)=F(x0+1,y0−0.5)=(0+1)2+(r−0.5)2−r2=1.25−r
对于任意正在绘制的一点 (x0,y0)(x_0,y_0)(x0,y0),其判别式为:
w=F(M)=F(x0+1,y0−0.5)=(x0+1)2+(y0−0.5)2−r2 \begin{align*} w&=F(M)=F(x_0+1,y_0-0.5)\\ &=(x_0+1)^2+(y_0-0.5)^2-r^2 \end{align*} w=F(M)=F(x0+1,y0−0.5)=(x0+1)2+(y0−0.5)2−r2
当 w<0w<0w<0时,M 在圆内,此时 NE 点更逼近圆,故下一个点取 NE 点,此时:
w=F(x0+2,y0−0.5)=(x0+2)2+(y0−0.5)2−r2=(x0+1)2+(y0−0.5)2−r2+2x0+3=w+2x0+3x0=x0+1 \begin{align*} w&=F(x_0+2,y_0-0.5)\\ &=(x_0+2)^2+(y_0-0.5)^2-r^2\\ &=(x_0+1)^2+(y_0-0.5)^2-r^2+2x_0+3\\ &=w+2x_0+3\\ x_0&=x_0+1 \end{align*} wx0=F(x0+2,y0−0.5)=(x0+2)2+(y0−0.5)2−r2=(x0+1)2+(y0−0.5)2−r2+2x0+3=w+2x0+3=x0+1
当 w≥0w \geq 0w≥0时,M 在圆上或圆外,此时视作 E 点更逼近圆,故下一个点取 E 点,此时:
w=F(x0+2,y0−1.5)=(x0+2)2+(y0−1.5)2−r2=(x0+1)2+(y0−0.5)2−r2+2x0−2y0+5=w+2x0−2y0+5x0=x0+1y0=y0−1 \begin{align*} w&=F(x_0+2,y_0-1.5)\\ &=(x_0+2)^2+(y_0-1.5)^2-r^2\\ &=(x_0+1)^2+(y_0-0.5)^2-r^2+2x_0-2y_0+5\\ &=w+2x_0-2y_0+5\\ x_0&=x_0+1\\ y_0&=y_0-1 \end{align*} wx0y0=F(x0+2,y0−1.5)=(x0+2)2+(y0−1.5)2−r2=(x0+1)2+(y0−0.5)2−r2+2x0−2y0+5=w+2x0−2y0+5=x0+1=y0−1
点(x0,y0)(x_0,y_0)(x0,y0)和原点构成的直线的斜率为:
k=dy0dx0=y0−0x0−0=y0x0 k=\frac{dy_0}{dx_0}=\frac{y_0-0}{x_0-0}=\frac{y_0}{x_0} k=dx0dy0=x0−0y0−0=x0y0
当 k==1k==1k==1 时,即 y0==x0y_0==x_0y0==x0 时,结束绘制。
除去推理过程可得Bresenham画圆法的完整算法流程:
初始值为:
x0=0y0=rw=1.25−r \begin{align*} x_0&=0\\ y_0&=r\\ w&=1.25-r \end{align*} x0y0w=0=r=1.25−r
当 w<0w<0w<0时:
w=w+2x0+3x0=x0+1 \begin{align*} w&=w+2x_0+3\\ x_0&=x_0+1 \end{align*} wx0=w+2x0+3=x0+1
当 w≥0w \geq 0w≥0 时:
w=w+2x0−2y0+5x0=x0+1y0=y0−1 \begin{align*} w&=w+2x_0-2y_0+5\\ x_0&=x_0+1\\ y_0&=y_0-1 \end{align*} wx0y0=w+2x0−2y0+5=x0+1=y0−1
循环以上过程直到 x0==y0x_0==y_0x0==y0。
以下是Bresenham画圆法的代码实现:
# 中点Bresenham画圆法
def gl_draw_circle(x1, y1, r):
"""
这个函数用于画给定终点和半径的圆
:param x1: 圆心横坐标
:param y1: 圆心纵坐标
:param r: 圆半径
:return: None
"""
x0 = 0
y0 = r
w = 1 - y0 # 是1.25 - y0 取整后的结果
glPointSize(1)
while x0 < y0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glEnd()
x0 += 1
if w < 0:
w = w + 2 * x0 + 3
else:
w = w + 2 * x0 - 2 * y0 + 5
y0 -= 1
x0 += 1
if x0 == y0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glEnd()
绘制圆的完整代码
这里鼠标绘制时第一个点为圆的中点,第二个点为圆上任意一点,通过这两个点来确定圆的圆心和半径,通过拖动鼠标可以改变观察将要绘制的圆的大小。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from OpenGL.GL import *
# from OpenGL.GLU import *
from OpenGL.GLUT import *
import math
mx = 200
my = 200
ex = 200
ey = 200
cur_ex = 200
cur_ey = 200
windowsizex = 400
windowsizey = 400
is_cur_begin_draw = 0 # 判断实时绘画线段是否开始绘制
is_begin_draw = 0 # 判断是否开始绘画
is_end_draw = -1 # 判断是否停止绘画
first_left_button_down = 0
lines_list = [] # 保存已经画好了的线
# 中点Bresenham画圆法
def gl_draw_circle(x1, y1, r):
"""
这个函数用于画给定终点和半径的圆
:param x1: 圆心横坐标
:param y1: 圆心纵坐标
:param r: 圆半径
:return: None
"""
x0 = 0
y0 = r
w = 1 - y0 # 是1.25 - y0 取整后的结果
glPointSize(1)
while x0 < y0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glEnd()
if w < 0:
w = w + 2 * x0 + 3
else:
w = w + 2 * x0 - 2 * y0 + 5
y0 -= 1
x0 += 1
if x0 == y0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glEnd()
def mydisplay():
# print("-----")
glClear(GL_COLOR_BUFFER_BIT)
global is_end_draw
global is_cur_begin_draw
global lines_list
if len(lines_list) != 0:
for item in lines_list:
# 绘制图形
r = int(math.sqrt((item[0] - item[2]) * (item[0] - item[2]) + (item[1] - item[3]) * (item[1] - item[3])))
gl_draw_circle(item[0], item[1], r)
pass
if is_end_draw == 1:
# 将起始点和结束点添加进去
lines_list.append([(mx - int(windowsizex / 2)), -(my - int(windowsizey / 2)),
(ex - int(windowsizex / 2)), -(ey - int(windowsizey / 2))])
glutPostRedisplay()
else:
if is_cur_begin_draw == 1:
# 绘制图形
r = int(math.sqrt((mx - cur_ex) * (mx - cur_ex) + (my - cur_ey) * (my - cur_ey)))
gl_draw_circle((mx - int(windowsizex / 2)), -(my - int(windowsizey / 2)), r)
pass
glutSwapBuffers() # 双缓存的刷新模式
def mymouse(button, state, mousex, mousey):
global mx
global my
if button == GLUT_RIGHT_BUTTON and state == GLUT_DOWN:
print("right_button down")
print("x: ", mousex, " y: ", mousey)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0.0, 0.0, 0.0, 0.0) # 将清空颜色为黑色
if button == GLUT_LEFT_BUTTON:
if state == GLUT_DOWN:
global is_end_draw
global first_left_button_down
global ex
global ey
if first_left_button_down == 0:
mx = mousex
my = mousey
print("left_button down")
print("mx: ", mx, " my: ", my)
is_end_draw = 0
first_left_button_down = 1
glutPostRedisplay() # 重画,相当于重新调用display
elif first_left_button_down == 1:
is_end_draw = 1
first_left_button_down = 0
ex = mousex
ey = mousey
glutPostRedisplay() # 重画,相当于重新调用display
def mymousemotion(x1, y1):
global is_end_draw
global cur_ex, cur_ey
global is_cur_begin_draw
if is_end_draw == 0:
cur_ex = x1
cur_ey = y1
is_cur_begin_draw = 1
glutPostRedisplay() # 重画,相当于重新调用display
else:
is_cur_begin_draw = 0
if __name__ == "__main__":
glutInit() # 对GLUT初始化
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA) # 设置显示方式
glutInitWindowSize(windowsizex, windowsizey)
text = glutCreateWindow("MYTEXT") # 创建窗口,窗口被创建后,需要调用glutMainLoop()才能看到
glutDisplayFunc(mydisplay)
glutMouseFunc(mymouse)
glutPassiveMotionFunc(mymousemotion) # 鼠标按钮松开时移动相应函数
glutMainLoop()
glClearColor(0.0, 0.0, 0.0, 0.0) # 将清空颜色为黑色
glClear(GL_COLOR_BUFFER_BIT) # 将窗口的背景设置为当前颜色
绘制圆的效果展示

绘制椭圆
绘制椭圆的思路与绘制圆的思路大致相同,唯一不同的地方在于绘制椭圆时是1/4对称,我们首先绘制第一象限的椭圆,然后将其对称到其他象限。在绘制第一象限的椭圆弧时,当所绘点处的椭圆切线斜率小于-1时,需要将绘制从以x为步长的绘制改为以y为步长的绘制,以求得到更多的椭圆上的点。
初始值:椭圆的圆心 (x1,y1)(x_1,y_1)(x1,y1),椭圆的横半轴长 aaa,纵半轴长 bbb,用 (x0,y0)(x_0,y_0)(x0,y0) 表示正要绘制的顶点坐标。
将椭圆的圆心移动到原点上,则椭圆的直角坐标方程可表示为:
x2a2+y2b2=1 \frac{x^2}{a^2}+\frac{y^2}{b^2}=1 a2x2+b2y2=1
化为一般式:
F(x,y)=b2x2+a2y2−a2b2=0 F(x,y)=b^2x^2+a^2y^2-a^2b^2=0 F(x,y)=b2x2+a2y2−a2b2=0
从椭圆的上顶点 (0,b)(0,b)(0,b) 开始从左往右绘制,则初始值为:
x0=0y0=a \begin{align*} x_0&=0\\ y_0&=a\\ \end{align*} x0y0=0=a
对于任意正要绘制的一点 (x0,y0)(x_0,y_0)(x0,y0),其判别式为:
w=F(M)=F(x0+1,y0−0.5)=b2(x0+1)2+a2(y0−0.5)2−a2b2 \begin{align*} w&=F(M)=F(x_0+1,y_0-0.5)\\ &=b^2(x_0+1)^2+a^2(y_0-0.5)^2-a^2b^2 \end{align*} w=F(M)=F(x0+1,y0−0.5)=b2(x0+1)2+a2(y0−0.5)2−a2b2
为了避免浮点运算,将判别式 www 的值乘以4:
w=4b2(x0+1)2+a2(2y0−1)2−4a2b2 w=4b^2(x_0+1)^2+a^2(2y_0-1)^2-4a^2b^2 w=4b2(x0+1)2+a2(2y0−1)2−4a2b2
当 w<0w<0w<0时,此时:
x0=x0+1w=F(x0+2,y0−0.5)=4b2(x0+2)2+a2(2y0−1)2−4a2b2=w+8b2x0+12b2=w+4b2(2x0+3) \begin{align*} x_0&=x_0+1\\ w&=F(x_0+2,y_0-0.5)\\ &=4b^2(x_0+2)^2+a^2(2y_0-1)^2-4a^2b^2\\ &=w+8b^2x_0+12b^2\\ &=w+4b^2(2x_0+3) \end{align*} x0w=x0+1=F(x0+2,y0−0.5)=4b2(x0+2)2+a2(2y0−1)2−4a2b2=w+8b2x0+12b2=w+4b2(2x0+3)
当 w≥0w \geq 0w≥0时,此时:
x0=x0+1y0=y0−1w=F(x0+2,y0−1.5)=4b2(x0+2)2+a2(2y0−3)2−4a2b2=w+4b2(2x0+3)+4a2(2−2y0) \begin{align*} x_0&=x_0+1\\ y_0&=y_0-1\\ w&=F(x_0+2,y_0-1.5)\\ &=4b^2(x_0+2)^2+a^2(2y_0-3)^2-4a^2b^2\\ &=w+4b^2(2x_0+3)+4a^2(2-2y_0) \end{align*} x0y0w=x0+1=y0−1=F(x0+2,y0−1.5)=4b2(x0+2)2+a2(2y0−3)2−4a2b2=w+4b2(2x0+3)+4a2(2−2y0)
椭圆的方程为:
F(x,y)=b2x2+a2y2−a2b2=0 F(x,y)=b^2x^2+a^2y^2-a^2b^2=0 F(x,y)=b2x2+a2y2−a2b2=0
两边同时对x求导:
2b2x+2a2ydydx=0 2b^2x+2a^2y\frac{dy}{dx}=0 2b2x+2a2ydxdy=0
故:
dydx=−b2xa2y \frac{dy}{dx}=-\frac{b^2x}{a^2y} dxdy=−a2yb2x
当 dydx>−1\frac{dy}{dx}>-1dxdy>−1 时,即 b2x−a2y<0b^2x-a^2y<0b2x−a2y<0 时,以 xxx 为步长绘制椭圆。当 b2x−a2y>0b^2x-a^2y>0b2x−a2y>0 时,开始转化为以 yyy 为步长绘制椭圆,当 y0==0y_0==0y0==0 时结束绘制。以 yyy 为步长的椭圆绘制算法的推演与以 xxx 为步长绘制椭圆的算法推演类似。
下面时绘制椭圆的代码实现:
# 椭圆绘制算法
def gl_draw_ellipse(x1, y1, m_a, m_b):
"""
这是用来绘制椭圆的函数
:param x1: 椭圆中心横坐标
:param y1: 椭圆中心纵坐标
:param m_a: 椭圆横轴长
:param m_b: 椭圆纵轴长
:return: None
"""
x0 = 0
y0 = m_b
m_a2 = m_a * m_a
m_b2 = m_b * m_b
w = 4 * m_b2 * (x0 + 1) * (x0 + 1) + \
m_a2 * (2 * y0 - 1) * (2 * y0 - 1) - \
4 * m_a2 * m_b2 # 判别式,用来判断下一个点的选取
glPointSize(1)
# 判断当前点所在椭圆的切线的斜率,如果斜率小于-1,则不交换
while m_b2 * x0 - m_a2 * y0 < 0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glEnd()
x0 += 1
if w < 0:
w = w + 4 * m_b2 * (2 * x0 + 3)
else:
y0 -= 1
w = w + 4 * m_b2 * (2 * x0 + 3) + 4 * m_a2 * (2 - 2 * y0)
if m_b2 * x0 - m_a2 * y0 == 0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glEnd()
w = m_b2 * (2 * x0 + 1) * (2 * x0 + 1) + \
4 * m_a2 * (y0 - 1) * (y0 - 1) - \
4 * m_a2 * m_b2
while x0 < m_a and y0 >= 0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glEnd()
y0 -= 1
if w < 0:
x0 += 1
w = w + 4 * m_b2 * (2 * x0 + 2) + 4 * m_a2 * (-2 * y0 + 3)
else:
w = w + 4 * m_a2 * (-2 * y0 + 3)
绘制椭圆的完整代码
这里鼠标点击的第一个点是椭圆外切矩形的其中一个顶点,第二个点为和第一个点互为对角的椭圆外切矩形的另一个顶点,从这两个顶点推出椭圆圆心、长半轴长和短半轴长,通过移动鼠标可以观察将要绘制的椭圆的实时变化,以下是绘制椭圆的完整代码实现。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from OpenGL.GL import *
# from OpenGL.GLU import *
from OpenGL.GLUT import *
# import math
mx = 200
my = 200
ex = 200
ey = 200
cur_ex = 200
cur_ey = 200
windowsizex = 400
windowsizey = 400
is_cur_begin_draw = 0 # 判断实时绘画线段是否开始绘制
is_begin_draw = 0 # 判断是否开始绘画
is_end_draw = -1 # 判断是否停止绘画
first_left_button_down = 0
lines_list = [] # 保存已经画好了的线
# 椭圆绘制算法
def gl_draw_ellipse(x1, y1, m_a, m_b):
"""
这是用来绘制椭圆的函数
:param x1: 椭圆中心横坐标
:param y1: 椭圆中心纵坐标
:param m_a: 椭圆横轴长
:param m_b: 椭圆纵轴长
:return: None
"""
x0 = 0
y0 = m_b
m_a2 = m_a * m_a
m_b2 = m_b * m_b
w = 4 * m_b2 * (x0 + 1) * (x0 + 1) + \
m_a2 * (2 * y0 - 1) * (2 * y0 - 1) - \
4 * m_a2 * m_b2 # 判别式,用来判断下一个点的选取
glPointSize(1)
# 判断当前点所在椭圆的切线的斜率,如果斜率小于-1,则不交换
while m_b2 * x0 - m_a2 * y0 < 0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glEnd()
x0 += 1
if w < 0:
w = w + 4 * m_b2 * (2 * x0 + 3)
else:
y0 -= 1
w = w + 4 * m_b2 * (2 * x0 + 3) + 4 * m_a2 * (2 - 2 * y0)
if m_b2 * x0 - m_a2 * y0 == 0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glEnd()
w = m_b2 * (2 * x0 + 1) * (2 * x0 + 1) + \
4 * m_a2 * (y0 - 1) * (y0 - 1) - \
4 * m_a2 * m_b2
while x0 < m_a and y0 >= 0:
glBegin(GL_POINTS)
glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glEnd()
y0 -= 1
if w < 0:
x0 += 1
w = w + 4 * m_b2 * (2 * x0 + 2) + 4 * m_a2 * (-2 * y0 + 3)
else:
w = w + 4 * m_a2 * (-2 * y0 + 3)
def mydisplay():
# print("-----")
# global windowsizex, windowsizey
# windowsizex = glutGet(GLUT_WINDOW_X)
# windowsizey = glutGet(GLUT_WINDOW_Y)
glClear(GL_COLOR_BUFFER_BIT)
global is_end_draw
global is_cur_begin_draw
global lines_list
if len(lines_list) != 0:
for item in lines_list:
# 绘制图形
m_a = int(abs(item[0] - item[2]) / 2)
m_b = int(abs(item[1] - item[3]) / 2)
mid_x = (item[0] + item[2]) / 2
mid_y = (item[1] + item[3]) / 2
gl_draw_ellipse(mid_x, mid_y, m_a, m_b)
pass
if is_end_draw == 1:
# 将起始点和结束点添加进去
lines_list.append([(mx - int(windowsizex / 2)), -(my - int(windowsizey / 2)),
(ex - int(windowsizex / 2)), -(ey - int(windowsizey / 2))])
glutPostRedisplay()
else:
if is_cur_begin_draw == 1:
# 绘制图形
m_a = int(abs(mx - cur_ex)/2)
m_b = int(abs(my - cur_ey)/2)
mid_x = (mx + cur_ex)/2
mid_y = (my + cur_ey)/2
gl_draw_ellipse((mid_x - int(windowsizex / 2)), -(mid_y - int(windowsizey / 2)), m_a, m_b)
pass
glutSwapBuffers() # 双缓存的刷新模式
def mymouse(button, state, mousex, mousey):
global mx
global my
if button == GLUT_RIGHT_BUTTON and state == GLUT_DOWN:
print("right_button down")
print("x: ", mousex, " y: ", mousey)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0.0, 0.0, 0.0, 0.0) # 将清空颜色为黑色
if button == GLUT_LEFT_BUTTON:
if state == GLUT_DOWN:
global is_end_draw
global first_left_button_down
global ex
global ey
if first_left_button_down == 0:
mx = mousex
my = mousey
print("left_button down")
print("mx: ", mx, " my: ", my)
is_end_draw = 0
first_left_button_down = 1
glutPostRedisplay() # 重画,相当于重新调用display
elif first_left_button_down == 1:
is_end_draw = 1
first_left_button_down = 0
ex = mousex
ey = mousey
glutPostRedisplay() # 重画,相当于重新调用display
def mymousemotion(x1, y1):
global is_end_draw
global cur_ex, cur_ey
global is_cur_begin_draw
if is_end_draw == 0:
cur_ex = x1
cur_ey = y1
is_cur_begin_draw = 1
glutPostRedisplay() # 重画,相当于重新调用display
else:
is_cur_begin_draw = 0
if __name__ == "__main__":
glutInit() # 对GLUT初始化
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA) # 设置显示方式
glutInitWindowSize(windowsizex, windowsizey)
text = glutCreateWindow("MYTEXT") # 创建窗口,窗口被创建后,需要调用glutMainLoop()才能看到
glutDisplayFunc(mydisplay)
glutMouseFunc(mymouse)
glutPassiveMotionFunc(mymousemotion) # 鼠标按钮松开时移动相应函数
glutMainLoop()
glClearColor(0.0, 0.0, 0.0, 0.0) # 将清空颜色为黑色
glClear(GL_COLOR_BUFFER_BIT) # 将窗口的背景设置为当前颜色
绘制椭圆的效果展示

结语
本文算法推演部分部分来自于参考文献的摘要,实现的代码未经优化,仅供学习与参考使用,如有错误,敬请谅解。
参考文献
[1] 黄静.计算机图形学及其实践教程[M].北京:机械工业出版社,2015.5:41-44
[2] 徐文鹏.计算机图形学基础(OpenGL版)[M].北京:清华大学出版社,2016.6:50-55
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)