[譯][Tkinter 教程08] Canvas 圖形繪制

原系列地址: Python Tkinter

簡介

Canvas 為 Tkinter 提供了繪圖功能. 其提供的圖形組件包括 線形, 圓形, 圖片, 甚至其他控件. Canvas 控件為繪制圖形圖表, 編輯圖形, 自定義控件提供了可能.
在第一個例子里, 我們將演示如何畫一條直線. create_line(coords, options) 方法用來繪制一條直線. coords 為以整形表示的四個坐標參數(shù): x1, y1, x2, y2 . 這表示所要繪制的直線連接了 (x1, y1) 和 (x2, y2) 這兩個點. 除坐標外, 該方法還接受其他可選的 options 參數(shù). 在下面的例子里我們用 options 參數(shù)指定顏色為我們網(wǎng)站的主題色: fill=#476042 .
因為是第一個例子, 所以我們盡量做了簡化: 創(chuàng)建一個 canvas 對象然后在其上繪制一條水平直線. 這條直線將 canvas 分割為上下兩部分.
在傳入坐標參數(shù)時, y = int(canvas_height / 2) 這種強制轉換整形的表達式是沒有必要的, 因為 create_line() 方法也接受 float 類型作為坐標參數(shù), float 坐標數(shù)值將被自動轉為整形. 下面是第一個例子的代碼:

from tkinter import *
master = Tk()

canvas_width = 80
canvas_height = 40
w = Canvas(master, 
           width=canvas_width,
           height=canvas_height)
w.pack()

y = int(canvas_height / 2)
w.create_line(0, y, canvas_width, y, fill="#476042")

mainloop()

上述代碼在 Python3 下會有如下顯示:


使用 create_rectangle(coords, options) 方法可以繪制矩形. coords 參數(shù)依然表示兩個點的坐標: 第一個點為左上角坐標, 第二個點為右下角坐標.

上面的窗口是由以下示例代碼生成的:

from tkinter import *

master = Tk()

w = Canvas(master, width=200, height=100)
w.pack()

w.create_rectangle(50, 20, 150, 80, fill="#476042")
w.create_rectangle(65, 35, 135, 65, fill="yellow")
w.create_line(0, 0, 50, 20, fill="#476042", width=3)
w.create_line(0, 100, 50, 80, fill="#476042", width=3)
w.create_line(150,20, 200, 0, fill="#476042", width=3)
w.create_line(150, 80, 200, 100, fill="#476042", width=3)

mainloop()

下圖闡釋了上面兩個例子中 create_lines()create_rectangle() 這兩個方法中, 用到的各個坐標的含義:

繪制文字

接下來我們將說明如何在 canvas 上繪制文字. 我們將直接修改上面的例子以作為新的示例. create_text() 方法用來在 canvas 上繪制文字. 該方法的頭兩個參數(shù)表示所要繪制的文字的坐標. 默認情況下, 文字將以此坐標為中心進行繪制. 當然, 你也可以復寫 anchor 屬性來改變文字繪制的對齊方式. 比如, anchor = NW 即為指定該點坐標為所繪文字的左上角. text 屬性用以指定具體繪制在 canvas 上的文字.

from tkinter import *

canvas_width = 200
canvas_height = 100

colours = ("#476042", "yellow")
box=[]

for ratio in ( 0.2, 0.35 ):
   box.append( (canvas_width * ratio,
                canvas_height * ratio,
                canvas_width * (1 - ratio),
                canvas_height * (1 - ratio) ) )

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

for i in range(2):
    w.create_rectangle(box[i][0], box[i][1],box[i][2],box[i][3], fill=colours[i])

w.create_line(0, 0,                 # canvas 原點
              box[0][0], box[0][1], # box[0] 的左上角坐標
              fill=colours[0], 
              width=3)
w.create_line(0, canvas_height,     # canvas 的左下角坐標
              box[0][0], box[0][3], # box[0] 的左下角坐標
              fill=colours[0], 
              width=3)
w.create_line(box[0][2],box[0][1],  # box[0] 的右上角坐標
              canvas_width, 0,      # canvas 的右上角坐標
              fill=colours[0], 
              width=3)
w.create_line(box[0][2], box[0][3], # box[0] 的右下角坐標
              canvas_width, canvas_height, # canvas 的右下角坐標
              fill=colours[0], width=3)

w.create_text(canvas_width / 2,
              canvas_height / 2,
              text="Python")
mainloop()

雖然從代碼上來看, 我們對之前的例子做了很大的改動, 但其所輸出的結果卻與前例相差不大, 僅僅在窗口的中間多了一個顯示 "Python" 字樣的方框:


本例中我們改用變量存儲坐標等參數(shù), 這使得改動變的方便. 比如, 要將整個畫布的寬高設為 90 * 190, 將 box[0] 的寬高比設為 0.3, 在本例中將很容易做到, 但在之前的例子中卻要修改很多代碼.
本例運行后顯示如下窗口:


繪制 Oval

圖形 oval 是一個蛋形的曲線. 它形似橢圓, 但并不是橢圓. 事實上, oval 這個概念沒有太明確的定義. 很多不同的曲線都被叫做 oval, 他們都有如下共同點:

  • 都是可微分的簡單 (非自相交) 凸閉曲線
  • 他們比橢圓曲線簡單
  • 至少有一條對稱軸

oval 這個詞源自拉丁語中的 ovum, 意為 "蛋", 這很好的描述了它: 一條描述蛋形狀的曲線. 一個 oval 由兩條半徑不同的弧線組成. 下圖是一個特殊的 oval:


我們可以使用如下方法在 canvas 中創(chuàng)建一個 oval:

id = C.create_oval ( x0, y0, x1, y1, option, ... )

該方法的返回值為所創(chuàng)建的 oval 對象在當前 canvas 上的 ID.
下面的代碼繪制了一個圓心在 (75, 75), 半徑為 25 的正圓形:

from tkinter import *

canvas_width = 190
canvas_height =150

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

w.create_oval(50,50,100,100)

mainloop()

我們可以定義一個專門用來畫正圓形的方法:

def circle(canvas, x, y, r):
    id = canvas.create_oval(x-r, y-r, x+r, y+r)
    return id

交互式繪圖

我們想要創(chuàng)建一個可在 canvas 上手動繪圖的應用, 但 canvas 并未提供畫單個點的方法. 我們可以通過繪制小的 oval 圖形來解決這個問題:

from tkinter import *

canvas_width = 500
canvas_height = 150

def paint( event ):
   python_green = "#476042"
   x1, y1 = ( event.x - 1 ), ( event.y - 1 )
   x2, y2 = ( event.x + 1 ), ( event.y + 1 )
   w.create_oval( x1, y1, x2, y2, fill = python_green )

master = Tk()
master.title( "Painting using Ovals" )
w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack(expand = YES, fill = BOTH)
w.bind( "<B1-Motion>", paint )

message = Label( master, text = "Press and Drag the mouse to draw" )
message.pack( side = BOTTOM )
    
mainloop()

繪制多邊形

如果要繪制一個多邊形, 可以使用 create_polygon(x0, y0, x1, y1, x2, y2, ...) 方法. 至少要傳入三個點的坐標才可以繪制一個多邊形.
下例用該方法繪制了一個三角形:

from tkinter import *

canvas_width = 200
canvas_height =200
python_green = "#476042"

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

points = [0,0,canvas_width,canvas_height/2, 0, canvas_height]
w.create_polygon(points, outline=python_green, 
            fill='yellow', width=3)

mainloop()

運行后顯示為如下窗口:


或許你在讀到這篇教程時圣誕節(jié) 馬上就到了/為時尚早. 這里我們用 Python 和 Tkinter 做一些星星來裝點我們的圣誕樹. 第一課星星幾乎沒有用到任何編程技巧:

from tkinter import *

canvas_width = 200
canvas_height =200
python_green = "#476042"

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

points = [100, 140, 110, 110, 140, 100, 110, 90, 100, 60, 90, 90, 60, 100, 90, 110]

w.create_polygon(points, outline=python_green, 
            fill='yellow', width=3)

mainloop()

上例非常沒有技術含量. 如果我們要改變星星的大小或胖瘦, 該怎么辦? 上例中我們只能重新指定所有點的坐標, 這種做法乏味且易出錯. 因此, 我們用了更多的編程技巧改造了上例. 首先, 我們將星星的繪制放在一個方法體中, 并用星星的原點及兩個長度指定星星的具體形狀:


經(jīng)過改造的代碼如下:

from tkinter import *

canvas_width = 400
canvas_height =400
python_green = "#476042"

def polygon_star(canvas, x,y,p,t, outline=python_green, fill='yellow', width = 1):
   points = []
   for i in (1,-1):
      points.extend((x,       y + i*p))
      points.extend((x + i*t, y + i*t))
      points.extend((x + i*p, y))
      points.extend((x + i*t, y - i * t))

   print(points)

   canvas.create_polygon(points, outline=outline, 
                         fill=fill, width=width)

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

p = 50
t = 15

nsteps = 10
step_x = int(canvas_width / nsteps)
step_y = int(canvas_height / nsteps)

for i in range(1, nsteps):
   polygon_star(w,i*step_x,i*step_y,p,t,outline='red',fill='gold', width=3)
   polygon_star(w,i*step_x,canvas_height - i*step_y,p,t,outline='red',fill='gold', width=3)

mainloop()

這個例子的運行結果更像一個X形. 很顯然, 用逐個指定坐標點的方法繪制這個圖形將無比麻煩.


繪制Bitmap

create_bitmap() 方法用來繪制 bitmap. 以下 bitmap 在所有平臺上都可用:
"error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question", "warning"
下例將這些 bitmap 全部都繪制在一個 canvas 上:

from tkinter import *

canvas_width = 300
canvas_height =80

master = Tk()
canvas = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
canvas.pack()

bitmaps = ["error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question", "warning"]
nsteps = len(bitmaps)
step_x = int(canvas_width / nsteps)

for i in range(0, nsteps):
   canvas.create_bitmap((i+1)*step_x - step_x/2,50, bitmap=bitmaps[i])

mainloop()

結果如下:


繪制圖片

使用 create_image(x0, x0, options ...) 用來在 canvas 上繪制圖片. 該方法不能直接接受圖片路徑等作為參數(shù), 而是接受一個 PhotoImage 對象作為圖片參數(shù). PhotoImage 類用于讀取圖片, 但其只能讀取 GIF 和 PGM/PPM 格式的圖片.

from tkinter import *

canvas_width = 300
canvas_height =300

master = Tk()

canvas = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
canvas.pack()

img = PhotoImage(file="rocks.ppm")
canvas.create_image(20,20, anchor=NW, image=img)

mainloop()

上面的代碼運行后輸出如下窗口:


小練習

編寫一個方法繪制一個棋盤: checkered(canvas, line_distance), 其中: "canvas" 即 Canvas 對象, 棋盤在其上繪制; "line_distance" 為線間距.

代碼如下:

from tkinter import *

def checkered(canvas, line_distance):
   # vertical lines at an interval of "line_distance" pixel
   for x in range(line_distance,canvas_width,line_distance):
      canvas.create_line(x, 0, x, canvas_height, fill="#476042")
   # horizontal lines at an interval of "line_distance" pixel
   for y in range(line_distance,canvas_height,line_distance):
      canvas.create_line(0, y, canvas_width, y, fill="#476042")


master = Tk()
canvas_width = 200
canvas_height = 100 
w = Canvas(master, 
           width=canvas_width,
           height=canvas_height)
w.pack()

checkered(w,10)

mainloop()

上面的代碼運行后輸出如下窗口:



譯者水平有限, 如有疏漏, 歡迎指正.
已獲得原作者授權. 原文地址: Canvas Widgets

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市采转,隨后出現(xiàn)的幾起案子故慈,更是在濱河造成了極大的恐慌,老刑警劉巖干签,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筒严,死亡現(xiàn)場離奇詭異情萤,居然都是意外死亡,警方通過查閱死者的電腦和手機娶视,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門肪获,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人较木,你說我怎么就攤上這事青柄≈驴” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵虹蒋,是天一觀的道長飒货。 經(jīng)常有香客問我魄衅,道長,這世上最難降的妖魔是什么塘辅? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任徐绑,我火速辦了婚禮,結果婚禮上莫辨,老公的妹妹穿的比我還像新娘傲茄。我一直安慰自己,他們只是感情好沮榜,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布盘榨。 她就那樣靜靜地躺著蟆融,像睡著了一般草巡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上型酥,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天山憨,我揣著相機與錄音,去河邊找鬼弥喉。 笑死郁竟,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的由境。 我是一名探鬼主播棚亩,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼蓖议,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讥蟆?” 一聲冷哼從身側響起勒虾,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘸彤,沒想到半個月后修然,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡质况,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年低零,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拯杠。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖啃奴,靈堂內(nèi)的尸體忽然破棺而出潭陪,到底是詐尸還是另有隱情,我是刑警寧澤最蕾,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布依溯,位于F島的核電站,受9級特大地震影響瘟则,放射性物質(zhì)發(fā)生泄漏黎炉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一醋拧、第九天 我趴在偏房一處隱蔽的房頂上張望慷嗜。 院中可真熱鬧,春花似錦丹壕、人聲如沸庆械。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缭乘。三九已至,卻和暖如春琉用,著一層夾襖步出監(jiān)牢的瞬間堕绩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工邑时, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奴紧,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓晶丘,卻偏偏與公主長得像绰寞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容