連假前上電腦課,老師出了一份作業要我們使用tkinter視窗的canvas來畫一張圖,並且要以雙十國慶為主題作為回家作業,於是我研究了下網路上的國旗畫法,以及numpy的array的用法,畫了一張國旗。先不討論到底是國旗還黨旗,反正這篇就只是分享一下如何畫國旗。

效果圖

file

簡單說明

只要更改初始設定unit的值,就可以按照比例改變國旗的大小。

困難的地方

  1. 不熟悉numpy函數用法,邊做邊惡補
  2. 圖型對應畫布座標的轉換,需要一些數學運算
  3. 幾何圖形要用可行(自己會做)的做,要想怎麼拼接成自己要的圖案

簡單說明

畫面藍紅區塊1:4,整體大小3:2。

十二光芒

參考連結 file 這邊是我覺得比較難的部分,因為想要在tkinter上畫多點的幾何圖形,需要給每個點的座標,然而這種按照特定比例繪製的圖形,要怎麼畫圖呢?我一開始是想要直接連結子、未、巳三點,並且與內圈做一個導航形狀的圖型,這樣只需要旋轉4次,就能完成了,但是困難的點是我的數學太爛,我不知道怎麼算裡面凹進去那點的座標。

建立函數

file

我研究了一陣子後換了作法,改成畫內圈兩端與外圈頂點的圖型,因為我注意到內圈的直徑等於未巳相連的一半,也剛好等於藍色區塊寬的1/4,這樣只需要轉30\degree共12次,就可以轉完圖形。於是我先寫了這樣:

1
flag.create_polygon(x_0, y_0-75, x_0-18.75, y_0, x_0+18.75, y_0, fill="white")

但現在頭大的是,我要怎麼求其他點的具體座標?我想到的是使用旋轉矩陣,但是要怎麼做呢?其實就是按照定義刻出來,然後我另外寫了一個函數來找旋轉\frac{\pi}{6}共12次的點。抓到點後,再對每一項進行座標平移就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
# -- 工具
# 取得12點座標

def get_point(x, y):
point_list = list()
for i in range(12):
point_list.append(coodinate_tp(x, y, (np.pi/6)*i))
return point_list

def coodinate_tp(x=1, y=0, theta=np.pi/6):
new_x = round(x*np.cos(theta) - y*np.sin(theta), 2)
new_y = round(x*np.sin(theta) + y*np.cos(theta), 2)
return [new_x, new_y]

輸出效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>get_point(0,-(w/8))
---
[[0.0, -75.0],
[37.5, -64.95],
[64.95, -37.5],
[75.0, -0.0],
[64.95, 37.5],
[37.5, 64.95],
[0.0, 75.0],
[-37.5, 64.95],
[-64.95, 37.5],
[-75.0, 0.0],
[-64.95, -37.5],
[-37.5, -64.95]]

用自製函數和迴圈窮舉,並且用numpy array做運算

接下來我只需要抓到三個點,並且一起繞著圓心轉12次,就可以了!

1
2
3
4
5
6
7
8
9
10
# 畫12個三角型
a_point = np.array(get_point(0, -1*(w/8)))
b_point = np.array(get_point(-1*(w/32), 0))
c_point = np.array(get_point(w/32, 0))
arr = np.append(a_point, b_point, axis=1)
arr2 = np.append(arr, c_point, axis=1)
arr2[:, 0::2] = arr2[:, 0::2]+x_0
arr2[:, 1::2] = arr2[:, 1::2]+y_0
for i in range(len(arr2)):
flag.create_polygon(arr2[i].tolist(), fill="white")

其中比較囉唆的部分,大概就是np.append必須分開加,如果點一多,可能還是要寫個迴圈來處理。因為是要描點,所以這個list的結構大概是兩個為單位,奇數項(1,3,5…)都是代表縱軸,偶數則代表橫軸。所以需要用到slice來處理陣列,這樣才能讓加入對的原點。原點指的是藍色部分的圓心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> arr2[:,0::2] = arr2[:,0::2]+x_0
arr2[:,1::2] = arr2[:,1::2]+y_0
arr2
---
array([[150. , 25. , 131.25, 100. , 168.75, 100. ],
[187.5 , 35.05, 133.76, 90.63, 166.24, 109.37],
[214.95, 62.5 , 140.62, 83.76, 159.38, 116.24],
[225. , 100. , 150. , 81.25, 150. , 118.75],
[214.95, 137.5 , 159.37, 83.76, 140.63, 116.24],
[187.5 , 164.95, 166.24, 90.62, 133.76, 109.38],
[150. , 175. , 168.75, 100. , 131.25, 100. ],
[112.5 , 164.95, 166.24, 109.37, 133.76, 90.63],
[ 85.05, 137.5 , 159.38, 116.24, 140.62, 83.76],
[ 75. , 100. , 150. , 118.75, 150. , 81.25],
[ 85.05, 62.5 , 140.63, 116.24, 159.37, 83.76],
[112.5 , 35.05, 133.76, 109.38, 166.24, 90.62]])

然後,因為Canvas的create_polygon函數,認得是list,所以還是要把資料從array轉換回list。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> arr2.tolist()
---
[[150.0, 25.0, 131.25, 100.0, 168.75, 100.0],
[187.5, 35.05, 133.76, 90.63, 166.24, 109.37],
[214.95, 62.5, 140.62, 83.76, 159.38, 116.24],
[225.0, 100.0, 150.0, 81.25, 150.0, 118.75],
[214.95, 137.5, 159.37, 83.76, 140.63, 116.24],
[187.5, 164.95, 166.24, 90.62, 133.76, 109.38],
[150.0, 175.0, 168.75, 100.0, 131.25, 100.0],
[112.5, 164.95, 166.24, 109.37, 133.76, 90.63],
[85.05, 137.5, 159.38, 116.24, 140.62, 83.76],
[75.0, 100.0, 150.0, 118.75, 150.0, 81.25],
[85.05, 62.5, 140.63, 116.24, 159.37, 83.76],
[112.5, 35.05, 133.76, 109.38, 166.24, 90.62]]

大概寫到這裡,後面剩下的步驟就只是畫兩個圓,其實像是有些設定的變數有點多餘,例如我一開始設定的值radius完全沒用到,若把w/8換成radius,應該可以讓程式碼更好讀,吧?


完整程式碼

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
from tkinter import *
import numpy as np

# 初始設定
unit = 20
w, h = 30*unit, 20*unit

# 定義重要位置
x_0, y_0 = w/4, h/4 # 藍色區塊中心點
radius = w/8 # 半徑:外圈(w/2)*(1/8)* 6 - (w/2)*(1/8)*2 ,再除二, 剛好是w的1/8

# --- 函數

# -- 工具
# 取得12點座標

def get_point(x, y):
point_list = list()
for i in range(12):
point_list.append(coodinate_tp(x, y, (np.pi/6)*i))
return point_list

def coodinate_tp(x=1, y=0, theta=np.pi/6):
new_x = round(x*np.cos(theta) - y*np.sin(theta), 2)
new_y = round(x*np.sin(theta) + y*np.cos(theta), 2)
return [new_x, new_y]

# build tkinter
double_ten = Tk()
double_ten.title('中華民國國旗')
flag = Canvas(double_ten, width=w, height=h, bg='#FE0000') # 比例3:2
flag.pack()

# 藍天十二光芒
flag.create_rectangle(0, 0, w/2, h/2, fill="#000095") # 藍色底佔國旗的1/4
# 畫12個三角型
a_point = np.array(get_point(0, -1*(w/8)))
b_point = np.array(get_point(-1*(w/32), 0))
c_point = np.array(get_point(w/32, 0))
arr = np.append(a_point, b_point, axis=1)
arr2 = np.append(arr, c_point, axis=1)
arr2[:, 0::2] = arr2[:, 0::2]+x_0
arr2[:, 1::2] = arr2[:, 1::2]+y_0
for i in range(len(arr2)):
flag.create_polygon(arr2[i].tolist(), fill="white")

# 內部小圈圈
flag.create_oval((w/2)*(1/8)*3, (h/4)-((w/2)*(1/8)), (w/2)*(1/8)
* 5, (h/4)+((w/2)*(1/8)), fill="#000095", outline="#000095")
flag.create_oval((w/2)*(1/8)*3+w/120, (h/4)-((w/2)*(1/8))+w/120, (w/2)*(1/8)
* 5-w/120, (h/4)+((w/2)*(1/8))-w/120, fill="#fff", outline="#fff")

# flag.create_oval((w/2)*(1/8)*2, (h/4)-((w/2)*(2/8)), (w/2)*(1/8)
# * 6, (h/4)+((w/2)*(2/8)), outline='white') # 外圈(確認三角位置用)

double_ten.mainloop()

參考資料

Python

旋轉矩陣

國旗規格