使用海龟绘图绘制一些植物

在 2019 年 10 月 1 日的时候,我尝试使用海龟绘图绘制了一面五星红旗,参考我的另外一篇博文:使用海龟绘图绘制一面五星红旗 ,我觉得挺好玩的,还想进一步了解一下相关知识。后来,我又探索了一些绘图内容,发现可以绘制一些植物,例如树木、花草,核心就是要定义好绘制曲线。本文记录几个常见的植物:樱花树、火树银花、玫瑰花。

提前声明,下文中涉及的 Python 脚本已经被我上传至 GitHub,读者可以提前下载查看:绘制植物脚本 ,脚本命名使用英文单词作为前缀。

樱花树

画樱花树的整体思路就是先绘制樱花树,再绘制地上的落叶。

其中,绘制樱花树使用了递归的方式,从主干开始绘制,绘制主干完成后分为左右两侧的枝干,不停递归绘制,对于长度比较长的枝干,仍旧按照主干的方式绘制,直到长度比较短的枝干,作为树枝末端存在,会有不同的颜色、粗细。

代码示例如下,里面包含了注释,很容易就能看懂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env python
# coding=utf-8
# 画一棵樱花树(模拟)
# 导入 turtle 模块
import turtle
# 导入 random 模块,每次绘制的樱花树形状随机
import random
from turtle import *
from time import sleep

# 画樱花的躯干,传入躯干长度、画布
# 这里面会有递归调用
# 先画主干,然后递归画树枝,树枝越来越短,颜色会随机生成
def draw_tree (branchLen, t):
sleep (0.0005)
if branchLen > 3:
# 末端的树枝
if 8 <= branchLen <= 12:
# 随机生成画笔的颜色,用来画末端的树枝
if random.randint (0,2) == 0:
# 白色
t.color ('snow')
else:
# 淡珊瑚色
t.color ('lightcoral')
# 画笔的线条粗细
t.pensize (branchLen / 3)
elif branchLen < 8:
if random.randint (0,1) == 0:
t.color ('snow')
else:
t.color ('lightcoral') # 淡珊瑚色
t.pensize (branchLen / 2)
else:
# 树干的颜色赭 (zhě) 色、粗细 6
t.color ('sienna')
t.pensize (branchLen / 10)
# 向前移动 branchLen 个像素
t.forward (branchLen)
# 随机生成右转的角度
a = 1.5 * random.random ()
t.right (20 * a)
# 递归画樱花树
b = 1.5 * random.random ()
draw_tree (branchLen - 10 * b, t)
# 左转,递归画樱花树
t.left (40 * a)
draw_tree (branchLen - 10 * b, t)
# 画笔回正方向,向前移动
t.right (20 * a)
t.up ()
t.backward (branchLen)
t.down ()

# 掉落的花瓣,传入个数、画布
def draw_petal (m, t):
# 循环绘制 m 个花瓣
for i in range (m):
# 生成随机的移动像素个数,a 用来控制左右的移动,b 用来控制上下的移动
# a 大一点,b 小一点,总体可以让花瓣看起来有透视立体感
a = 200 - 400 * random.random ()
b = 10 - 20 * random.random ()
# 以下就是到达花瓣位置
# 提起画笔
t.up ()
# 向前移动 b 个像素
t.forward (b)
# 左转 90 度角度
t.left (90)
# 向前移动 a 个像素
t.forward (a)
# 放下画笔
t.down ()
# 淡珊瑚色,花瓣的颜色
t.color ('lightcoral')
# 以下是绘制一个花瓣
# 绘制一个圆
t.circle (1)
# 以下就是回到中心点
# 提起画笔
t.up ()
# 向后移动 a 个像素
t.backward (a)
# 右转 90 度角度
t.right (90)
# 向后移动 b 个像素
t.backward (b)

def draw_cherry ():
# 海龟绘图区域
t = turtle.Turtle ()
# 画布
w = turtle.Screen ()
# 设置大小,4 个参数:宽度、高度、起始值 x 轴、起始值 y 轴
w.setup (1000, 600, 200, 100)
# 设置背景为小麦颜色
w.bgcolor ('wheat')
# 隐藏画笔
t.hideturtle ()
# 获取屏幕,并追踪
t.getscreen ().tracer (5, 0)
t.left (90)
t.up ()
t.backward (200)
t.down ()
# 1、画樱花的躯干
draw_tree (60, t)
# 2、画掉落的花瓣
draw_petal (200, t)
# 3、点击退出
w.exitonclick ()

# 程序入口
if __name__=="__main__":
print (' 开始绘制樱花树 & apos;)
draw_cherry ()
print (' 结束绘制樱花树 & apos;)
# input (' 暂停,等待输入(输入任意内容按回车键可退出):')

运行结果如下,由于角度是随机生成的,所以每次运行结果都会不一样:

运行结果 1

运行结果 2

火树银花

绘制火树银花的思路和上面的樱花树一致,只不过火树银花这个名字比较酷,树枝没有区分粗细,只区分长度、颜色,整个画面采用黑色背景,看起来非常闪耀。

需要注意的是,运行一次耗时比较长,大概需要 4-5 分钟。

代码内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# !/usr/bin/python3
# -*-coding:UTF-8-*-
# 火树银花

# 导入海龟作图模块
import turtle
# 导入随机数模块
import random as rm

# 角度
angle = [15, 5, 10, 20, 25, 30]
# 颜色数组,多种颜色供绘制时随机选择,青、红、粉、蓝、绿、黄
color = ['yellow', 'green', 'blue', 'red', 'pink', 'cyan']

# 绘制树干,传入长度、画布对象
# 绘制思路:根据长度的不同,生成的角度不同,树干会分为 2 个树枝,然后树枝再递归分叉
# 直到树枝的长度过小,变为树枝末梢,不再分叉
def draw_tree (branch_len, t, cr):
# 树干颜色
t.color (cr)
# 设置画笔的粗细
t.pensize (1)
# 随机选择颜色,不等于树干颜色,用于分叉树枝
new_color = color [:]
new_color.remove (cr)
new_cr = rm.choice (new_color)
# 随机转动角度,用于分叉树枝
ag1 = rm.choice (angle)
ag2 = rm.choice (angle)
# 分叉树枝的长度,默认等于树干的长度
new_branch_len = branch_len
# 分叉树枝的长度重新计算,越来越短
if branch_len > 120:
new_branch_len = branch_len - 20
elif branch_len >= 60:
new_branch_len = branch_len - 15
elif branch_len >= 20:
new_branch_len = branch_len - 10
else:
new_branch_len = branch_len - 5
# 开始绘制
if 10 >= branch_len:
# 树枝太短,无需绘制,递归结束
pass
else:
# 向前移动,绘制树干
t.forward (branch_len)
# 右转指定角度 1,分叉
t.right (ag1)
# 递归画树干,可以理解成子树
draw_tree (new_branch_len, t, new_cr)
# 左转指定角度 2,分叉
t.left (ag1 + ag2)
draw_tree (new_branch_len, t, new_cr)
# 角度回正,右转指定角度 2
t.right (ag2)
# 恢复颜色并后退
t.color (cr)
t.backward (branch_len)

# 开始绘制整棵树
def draw_fire_cilver ():
t = turtle.Turtle ()
w = turtle.Screen ()
# 设置背景为黑色
w.bgcolor ('black')
# 设置弹框大小,4 个参数:宽度、高度、起始值 x 轴、起始值 y 轴
w.setup (1200, 800, 200, 50)
# 加快速度
t.speed (10)
# 调整画笔的位置,开始的位置在中间偏下方
t.left (90)
t.up ()
t.backward (400)
t.down ()
# 跟踪画笔,可以看到整个绘制轨迹
turtle.tracer (5)
# 垂直位置绘制 1 棵,左右边再各绘制 3 棵,共 7 棵
t.left (15)
for i in range (0,7):
# 绘制 1 棵,右转 5 度
print ('==== 绘制第 [' + str (i + 1) + '] 棵树 & apos;)
draw_tree (150, t, 'cyan')
t.right (5)
# 单机退出
w.exitonclick ()

# 主程序入口
if __name__ == '__main__':
print (' 开始绘制火树银花 & apos;)
draw_fire_cilver ()
print (' 结束绘制火树银花 & apos;)
# input (' 暂停,等待输入(输入任意内容按回车键可退出):')

运行结果如下图,由于角度、颜色也是随机生成的,所以每次运行结果是不一致的。

运行结果

在网络上找到的示例,看起来更好看一些。

网络上找到的示例

玫瑰花

玫瑰花比较有意思,会涉及到非规则图形,花瓣的形状怎么绘制、绿叶的形状怎么绘制等。

简单思路:

  • 先绘制花瓣的边框,包括填充颜色
  • 再绘制花瓣中的线条,凸显出花瓣的层次
  • 绘制花枝主干
  • 绘制两片绿叶,包括绿叶的枝条

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# 导入海龟绘图模块 
import turtle as t

# 定义一个曲线绘制函数
# 思路就是画多个小圆弧,构成曲线
# n 表示画多少次圆弧,n 越大画的曲线越长
# r 表示圆弧半径,r 越大则曲线越平滑
# d=1 则是左弯的圆弧,d=-1 则是右弯的圆弧(由于屏幕的分辨率不同,有时候看不出来明显的弯度)
def degree_curve (n, r, d=1):
for i in range (n):
t.left (d)
# r 是半径,abs (d) 是夹角
t.circle (r, abs (d))

# 绘制玫瑰花
def draw_rose (s):
# 设置画笔速度
t.speed (100)
# 提起画笔,移动到指定位置
t.penup ()
t.goto (0, 900 * s)
# 放下画笔
t.pendown ()

# 开始填充,并绘制花朵形状
t.begin_fill ()
# 起步花蕊的曲线,30 度圆弧,一层椭圆下侧
t.circle (200 * s, 30)
# 左弯曲线,60 次半径为 10 的圆弧,一层椭圆右侧
degree_curve (60, 50 * s)
# 和起步花蕊的曲线对称,30 度圆弧
t.circle (200 * s, 30)
# 左弯曲线,4 次半径为 20 的圆弧,为了调整角度
degree_curve (4, 100 * s)
# 50 度圆弧,一层椭圆上侧
t.circle (200 * s, 50)
# 左弯曲线,50 次半径为 10 的圆弧,一层椭圆左侧下侧
degree_curve (50, 50 * s)
# 65 度圆弧,一层椭圆下侧
t.circle (350 * s, 65)
# 左弯曲线,40 次半径为 14 的圆弧,二层椭圆右侧
degree_curve (40, 70 * s)
# 50 度圆弧,二层椭圆右侧上侧
t.circle (150 * s, 50)
# 右弯曲线,20 次半径为 10 的圆弧,二层椭圆上侧
degree_curve (20, 50 * s, -1)
# 60 度圆弧,二层椭圆上侧
t.circle (400 * s, 60)
# 右弯曲线,18 次半径为 10 的圆弧,二层椭圆左侧
degree_curve (18, 50 * s)
# 前进 125,直线,二层椭圆左侧连接处
t.fd (250 * s)
# 右转 150 度
t.right (150)
# 12 度圆弧,顺时针画圆,右弯曲线
t.circle (-500 * s, 12)
# 左转 140 度
t.left (140)
# 110 度圆弧,左侧花瓣边缘
t.circle (550 * s, 110)
# 左转 27 度
t.left (27)
# 100 度圆弧,右侧花瓣边缘
t.circle (650 * s, 100)
# 左转 130 度
t.left (130)
# 20 度圆弧,顺时针画圆
t.circle (-300 * s, 20)
# 右转 123 度
t.right (123)
# 57 度圆弧,连接到二层椭圆右侧
t.circle (220 * s, 57)
# 至此图形封闭,颜色填充完成
t.end_fill ()

# 绘制花枝形状,包括勾勒花瓣中间的线条
# 左转 120 度
t.left (120)
# 前进 140
t.fd (280 * s)
# 左转 115 度
t.left (115)
# 33 度圆弧,连接到右侧花瓣边缘
t.circle (300 * s, 33)
# 左转 180 度
t.left (180)
# 33 度圆弧,顺时针,为了回到上一步画圆弧之前的位置
t.circle (-300 * s, 33)
# 右弯曲线,70 次半径为 113 的圆弧,右侧花瓣线条
degree_curve (70, 225 * s, -1)
# 104 度圆弧,右侧花瓣线条
t.circle (350 * s, 104)
# 左转 90 度
t.left (90)
# 105 度圆弧,左侧花瓣线条
t.circle (200 * s, 105)
# 63 度弧度,顺时针,左侧花瓣线条,至此花瓣线条完成
t.circle (-500 * s, 63)
# 提起画笔,移动到指定位置,花瓣与花枝连接处
t.penup ()
t.goto (170 * s, -30 * s)
# 放下画笔
t.pendown ()
# 左转 160 度,朝向调整为朝下
t.left (160)
# 左弯曲线,20 次半径为 1250 的圆弧,花枝
degree_curve (20, 2500 * s)
# 右弯曲线,220 次半径为 125 的圆弧,花枝
degree_curve (220, 250 * s, -1)

# 下面开始绘制 2 片绿叶
# 绘制一个绿色叶子,上方的
t.fillcolor ('green')
# 提起画笔移动到指定位置,叶尖
t.penup ()
t.goto (670 * s, -180 * s)
t.pendown ()
# 右转 140 度,调整角度
t.right (140)
# 开始填充
t.begin_fill ()
# 120 度弧度,绿叶上侧
t.circle (300 * s, 120)
# 左转 60 度
t.left (60)
# 120 度弧度,绿叶下侧
t.circle (300 * s, 120)
# 完成填充
t.end_fill ()
t.penup ()
# 移动到绿叶枝条起始处
t.goto (180 * s, -550 * s)
t.pendown ()
# 右转 85 度
t.right (85)
# 40 度圆弧,绿叶枝条
t.circle (600 * s, 40)

# 绘制另一个绿色叶子,下方的
# 提笔,移动到叶尖
t.penup ()
t.goto (-150 * s, -1000 * s)
t.pendown ()
t.begin_fill ()
# 右转 120 度,调整角度
t.rt (120)
# 115 度圆弧,叶子下侧
t.circle (300 * s, 115)
# 左转 75 度
t.left (75)
# 100 度弧度,叶子上侧
t.circle (300 * s, 100)
t.end_fill ()
t.penup ()
# 移动到绿叶枝条起始处
t.goto (430 * s, -1070 * s)
t.pendown ()
# 右转 30 度,调整角度
t.right (30)
# 35 度圆弧,右弯,叶子枝条
t.circle (-600 * s, 35)
# 等待退出
t.exitonclick ()

# 程序入口
if __name__=="__main__":
print (' 开始绘制玫瑰花 & apos;)
# 比例设定
s = 0.2
# 设置弹窗大小
t.setup (500 * 5 * s, 750 * 5 * s)
# 背景颜色,小麦色
t.bgcolor ('wheat')
# 设置画笔颜色,黑色
t.pencolor ("black")
# 设置填充颜色为红色,绘制花朵
t.fillcolor ("red")
draw_rose (s)
print (' 结束绘制玫瑰花 & apos;)
# input (' 暂停,等待输入(输入任意内容按回车键可退出):')

这里面的重点就是 degree_curve (n, r, d=1) 方法,它是为了绘制不规则图形而定义的。此外用的次数比较多的就是海龟绘图内置的 circle 方法,用来绘制标准的圆弧。

运行结果如下图,包含花瓣、绿叶。

玫瑰花运行结果

参考

Python 官方文档:Python3 文档说明

虾丸派 wechat
扫一扫添加博主,进技术交流群,共同学习进步
永不止步
0%