[译][Tkinter 教程08] Canvas 图形绘制

原系列地址: Python Tkinter

简介

Canvas 为 Tkinter 提供了绘图功能. 其提供的图形组件包括 线形, 圆形, 图片, 甚至其他控件. Canvas 控件为绘制图形图表, 编辑图形, 自定义控件提供了可能.
在第一个例子里, 我们将演示如何画一条直线. create_line(coords, options) 方法用来绘制一条直线. coords 为以整形表示的四个坐标参数: x1, y1, x2, y2 . 这表示所要绘制的直线连接了 (x1, y1) 和 (x2, y2) 这两个点. 除坐标外, 该方法还接受其他可选的 options 参数. 在下面的例子里我们用 options 参数指定颜色为我们网站的主题色: fill=#476042 .
因为是第一个例子, 所以我们尽量做了简化: 创建一个 canvas 对象然后在其上绘制一条水平直线. 这条直线将 canvas 分割为上下两部分.
在传入坐标参数时, y = int(canvas_height / 2) 这种强制转换整形的表达式是没有必要的, 因为 create_line() 方法也接受 float 类型作为坐标参数, float 坐标数值将被自动转为整形. 下面是第一个例子的代码:

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 参数依然表示两个点的坐标: 第一个点为左上角坐标, 第二个点为右下角坐标.

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

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 上绘制文字. 该方法的头两个参数表示所要绘制的文字的坐标. 默认情况下, 文字将以此坐标为中心进行绘制. 当然, 你也可以复写 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" 字样的方框:


本例中我们改用变量存储坐标等参数, 这使得改动变的方便. 比如, 要将整个画布的宽高设为 90 * 190, 将 box[0] 的宽高比设为 0.3, 在本例中将很容易做到, 但在之前的例子中却要修改很多代码.
本例运行后显示如下窗口:


绘制 Oval

图形 oval 是一个蛋形的曲线. 它形似椭圆, 但并不是椭圆. 事实上, oval 这个概念没有太明确的定义. 很多不同的曲线都被叫做 oval, 他们都有如下共同点:

  • 都是可微分的简单 (非自相交) 凸闭曲线
  • 他们比椭圆曲线简单
  • 至少有一条对称轴

oval 这个词源自拉丁语中的 ovum, 意为 "蛋", 这很好的描述了它: 一条描述蛋形状的曲线. 一个 oval 由两条半径不同的弧线组成. 下图是一个特殊的 oval:


我们可以使用如下方法在 canvas 中创建一个 oval:

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

该方法的返回值为所创建的 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

交互式绘图

我们想要创建一个可在 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()

运行后显示为如下窗口:


或许你在读到这篇教程时圣诞节 马上就到了/为时尚早. 这里我们用 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()

上例非常没有技术含量. 如果我们要改变星星的大小或胖瘦, 该怎么办? 上例中我们只能重新指定所有点的坐标, 这种做法乏味且易出错. 因此, 我们用了更多的编程技巧改造了上例. 首先, 我们将星星的绘制放在一个方法体中, 并用星星的原点及两个长度指定星星的具体形状:


经过改造的代码如下:

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 上绘制图片. 该方法不能直接接受图片路径等作为参数, 而是接受一个 PhotoImage 对象作为图片参数. 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容