Pygame 教程(4):图像传输和绘制文本
本章,你将学会如何传输图像和绘制文本。
导航
上一章:绘制图形
下一章:监测游戏时间
文章目录
- 导航
- 加载图像
- 导出图像
- 绘制文本
- 实例:画板
- 添加常量
- 限制坐标
- 定义属性
- 绘制色板
- 更改线条粗细
- 处理鼠标事件
- 处理键盘事件
- 调用事件处理方法
- 完整代码
- 结语
加载图像
上一章学习了绘制图形,对于一些简单的应用程序来说,这或许够用,但是对于更大型的应用程序,需要用到更复杂的图形的时候,使用pygame.draw
模块进行绘制就会变得烦琐,而且该模块渲染的质量较低,不符合大多数应用程序的要求。
所以,面对这种情况,通常的做法是,把复杂的图形编写在图片文件中,并使用 Pygame 加载它,最后绘制到屏幕上。
Pygame 使用 image
模块进行图像传输的操作。最常用的函数就是用于加载图像文件的pygame.image.load
函数。第一个参数filename
可以是图像的文件名,也可以是一个 Python 类文件对象(file-like object,比如调用open
函数返回的文件对象)。第二个可选参数name_hint
可用于显式指定文件的类型,但在大多数情况下,Pygame 会自动识别文件类型,从数据中创建一个Surface
对象。
Pygame 支持加载的图像文件类型如下(详见官方文档):
- BMP
- GIF (非动画,对于含动画的文件只使用第一帧画面)
- JPEG
- LBM(还有PBM,PGM,PPM)
- PCX
- PNG
- PNM
- SVG(有限支持,使用 Nano SVG)
- TGA(未压缩的)
- TIFF
- WEBP
- XPM
导出图像
与加载文件相反,Pygame 也支持将Surface
对象导出为图像文件。使用pygame.image.save
函数即可。该函数的第一个参数surface
指定要导出的Surface
对象。第二个参数filename
与load
函数相同,可以是图像的文件名,也可以是一个 Python 类文件对象。第三个可选参数name_hint
也同load
函数。
与加载图像文件不同的是,Pygame 中支持导出的文件类型比较少,具体如下(详见官方文档):
- BMP
- JPEG
- PNG
- TGA
image
模块中其他的函数可以阅读官方文档进行学习,再次不做赘述。
绘制文本
为了与用户交互,显示文本也是一项很重要的技术。作为一个 GUI(Graphical User Interface,图形用户界面)程序,使用print
输出文本是不现实的,需要将文本绘制到屏幕上。
为了绘制文本,需要加载对应的字体。Pygame 使用pygame.font
模块加载并渲染字体。
pygame.font.Font
类用于从给定的文件名或 Python 文件对象中加载一个字体。如果对文件名传入None
将会加载 Pygame 的默认字体 freesansbold.ttf
(传入字符串'freesansbold.ttf'
效果相同),你可以在 Python 的安装目录中的Lib/site-packages/pygame
文件夹中找到这个字体文件。参数size
指定字体的高度像素值,这个值在字体创建后将无法被改变。
Font
类中定义的bold
,italic
,underline
等属性可以为字体添加更多的格式,详见官方文档。
如果始终使用文件名导入字体会比较麻烦,因此font
模块中还提供了函数SysFont
以从系统字体中导入文件(与Font
类相反,该函数无法导入字体文件)。该函数依然返回Font
对象。参数size
同Font
类。可选参数bold
和italic
指定是否使用粗体或斜体。
Font
类中还定义了一个重要的方法:render
。它用于使用此字体渲染指定文本,并返回渲染的Surface
对象。
render
函数的完整定义是pygame.font.render(text, antialias, color, background=None) -> Surface
。参数text
指定渲染的文本。参数antialias
指定是否使用抗锯齿技术。color
指定渲染的文本颜色。可选参数background
可以指定渲染文本的背景颜色,默认情况下,背景颜色是透明的。
实例:画板
依照惯例,我们将创建一个画板应用程序。
请新建一个文件,命名为drawing_board.py
,并添加如下代码以创建初始窗口:
import sysimport pygameclass DrawingBoard:def __init__(self):pygame.init()self.screen = pygame.display.set_mode((800, 800))pygame.display.set_caption('Drawing Board')self.screen.fill((255, 255, 255))def process_events(self):for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit()def run(self):while True:self.process_events()pygame.display.update()if __name__ == '__main__':app = DrawingBoard()app.run()
与上一章相似,我们把程序代码封装到了一个类中。略微不同的是,我们把处理事件循环的代码封装到了process_events
方法中,因为这个实例对事件的处理比较繁重,全部堆积在run
方法中并不是一个好办法。
我们在构造函数中就将屏幕填充为白色。因为画板应用程序需要实时更新,如果每一次循环都填充屏幕,那么每一次循环过后用户绘制的内容都将被覆盖。
添加常量
在类的外部添加以下声明常量的代码:
import sysimport pygameCOLORS = ((0, 0, 0),(255, 0, 0),(0, 255, 0),(0, 0, 255),(255, 255, 0),(255, 0, 255),(0, 255, 255),(255, 255, 255)
) # 色板中的所有颜色RADIUS = 15 # 色板中设置颜色的圆圈的半径
GAP = 15 # 色板中相邻的圆圈之间的空隙
CENTER_DIST = RADIUS * 2 + GAP # 色板中相邻的圆圈的中心之间的距离
PALETTE_RECT = pygame.Rect(0, 0, 800, RADIUS * 2 + GAP * 2) # 色板的矩形坐标
PALETTE_HEIGHT = PALETTE_RECT.height # 色板的矩形的高度
LINE_WIDTH_MAX = 20 # 线条的最大粗度class DrawingBoard:
# ...
在编写代码时,对常量的命名可以使用全部大写加下划线的形式,以便与变量区分开。而没有把常量声明为类实例的属性是因为它们与类的关系并不是非常密切。
限制坐标
当鼠标移动到色板上时,不应该继续画在色板上。所以需要限制鼠标的坐标。在代码中添加以下两个全局函数:
def clamp(value, low, high):return low if value < low else (high if value > high else value)def clamp_pos(pos):y = clamp(pos[1], PALETTE_HEIGHT, 800)return (pos[0], y)
函数clamp
用于将指定的数限制在low
和high
之间。该函数使用了三目运算符,其格式为[statement_1] if [expression] else [statement_2]
,即当expression
为True
时,返回statement_1
,反之,返回statement_2
。为了方便,我们还定义了clamp_pos
函数,用于将坐标的y值限制在COLORS_HEIGHT
和800之间。
这两个函数也与类没有直接关系,所以声明为全局函数,而不是类方法。
定义属性
在类的构造函数中添加以下代码:
# ...
pygame.display.set_caption('Drawing Board')self.color = (0, 0, 0) # 画笔的颜色
self.width = 1 # 线条的粗细
self.last_pos = self.pos = (0, 0) # 鼠标上一帧的位置以及当前位置
self.circles = [] # 色板中由圆圈的中心和对应颜色的元组组合而成。
self.font = pygame.font.SysFont('Times New Roman', 20) # 字体
self.drawing = False # 用户是否正在绘画# ...
绘制色板
添加以下的两个类方法:
def draw_colors(self):for i in range(len(COLORS)):center = (i * CENTER_DIST + GAP + RADIUS, GAP + RADIUS)pygame.draw.circle(self.screen, COLORS[i], center, RADIUS)self.circles.append((center, COLORS[i]))pygame.draw.line(self.screen, (0, 0, 0), (0, PALETTE_HEIGHT), (800, PALETTE_HEIGHT))def draw_text(self):surf = self.font.render(f'Line width: {self.width}', True, (0, 0, 0))rect = surf.get_rect()rect.right = PALETTE_RECT.right - GAPrect.centery = PALETTE_RECT.centeryself.screen.blit(surf, rect)
draw_colors
方法遍历所有的颜色,并绘制圆圈,为了使用户区分色板,我们还在下面绘制了一条线段。
draw_text
方法显示当前的线条粗细。它使用render
方法渲染文本,再绘制到屏幕上。为了调整文本的位置,我们做了一些工作:先把矩形的右侧移动到距离色板的右侧一个GAP
的空隙,再使矩形的y中心对齐到色板矩形的y中心。这也是pygame.Rect
的强大之处,多个属性可以使位置的处理更加灵活。
在构造函数的最后,调用这两个方法:
# ...
self.drawing = False # 用户是否正在绘画self.draw_colors()
self.draw_text()# ...
更改线条粗细
本实例的设计为,当用户按下上箭头或下箭头时,更改线条的粗细。这里需要一些处理。因为如果直接地填充屏幕,用户之前所画的图形将消失,虽然可以通过截取子图像(使用pygame.Surface.subsurface
方法,详见拓展)保留用户所画的图形的副本,但是会过于麻烦。本实例选择修改draw_text
方法,在绘制文本之前填充一个白色的矩形覆盖原有文本,修改后的代码如下:
def draw_text(self):surf = self.font.render(f'Line width: {self.width}', True, (0, 0, 0))rect = surf.get_rect()rect.right = PALETTE_RECT.right - GAPrect.centery = PALETTE_RECT.centeryfill_rect = pygame.Rect(rect.left - 5, rect.top - 5, rect.width + 10, rect.height + 10)pygame.draw.rect(self.screen, (255, 255, 255), fill_rect)self.screen.blit(surf, rect)
fill_rect
是由rect
向外延伸5个像素而成的,因为不同的文本渲染的宽度不同,多延伸5个像素可以确保完全地覆盖在原来的文本上。
处理鼠标事件
首先,添加以下全局函数到代码中:
def dist_between_points(point1, point2):return ((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2) ** 0.5
该函数用于计算两点之间的距离,公式为 d=(x1−x2)2+(y1−y2)2d=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}d=(x1−x2)2+(y1−y2)2。
然后,添加以下方法用于处理MOUSEBUTTONDOWN
事件:
def on_mouse_down(self, pos):if PALETTE_RECT.collidepoint(pos):for center, color in self.circles:if dist_between_points(pos, center) < RADIUS:self.color = colorelse:self.last_pos = self.pos = posself.drawing = True
pygame.Rect.collidepoint
方法用于检测指定的点是否在此矩形对象内。在本实例中,如果该方法返回True
,说明用户点击了色板,于是遍历所有的圆圈,判断鼠标是否点击到了圆圈,如果是,则改变画笔的颜色。为了检测一个点是否在圆内,我们判断鼠标与圆心的距离是否小于半径,是,则说明鼠标在圆圈内。如果collidepoint
方法返回了False
,说明用户在画画的区域按下了鼠标,所以将last_pos
和pos
属性都设置为当前鼠标的位置,并设置标志drawing
为True
。
再添加以下方法用于处理MOUSEMOTION
事件:
def on_mouse_move(self, pos):if self.drawing:self.pos = clamp_pos(pos)pygame.draw.line(self.screen, self.color, self.last_pos, self.pos, self.width)self.last_pos = clamp_pos(pos)
该方法先将pos
更新为当前鼠标的坐标,在pos
和last_pos
之间画一条线段,再把last_pos
更新为当前鼠标的坐标。
处理键盘事件
添加以下方法用于处理KEYDOWN
事件:
def on_key_down(self, key):if self.drawing:returnif key == pygame.K_UP:self.width += 1self.width = clamp(self.width, 1, LINE_WIDTH_MAX)self.draw_text()elif key == pygame.K_DOWN:self.width -= 1self.width = clamp(self.width, 1, LINE_WIDTH_MAX)self.draw_text()
return
使用户只能在没有进行绘画时更改线条粗细。这里再次使用了clamp
函数限制线条粗细。
调用事件处理方法
更改process_events
方法,以调用事件处理的方法,代码如下:
def process_events(self):for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit()elif event.type == pygame.MOUSEBUTTONDOWN:self.on_mouse_down(event.pos)elif event.type == pygame.MOUSEMOTION:self.on_mouse_move(event.pos)elif event.type == pygame.MOUSEBUTTONUP:self.drawing = Falseelif event.type == pygame.KEYDOWN:self.on_key_down(event.key)
注意,这里还添加了处理MOUSEBUTTONUP
事件的代码。
完整代码
import sysimport pygameCOLORS = ((0, 0, 0),(255, 0, 0),(0, 255, 0),(0, 0, 255),(255, 255, 0),(255, 0, 255),(0, 255, 255),(255, 255, 255)
) # 色板中的所有颜色RADIUS = 15 # 色板中设置颜色的圆圈的半径
GAP = 15 # 色板中相邻的圆圈之间的空隙
CENTER_DIST = RADIUS * 2 + GAP # 色板中相邻的圆圈的中心之间的距离
PALETTE_RECT = pygame.Rect(0, 0, 800, RADIUS * 2 + GAP * 2) # 色板的矩形坐标
PALETTE_HEIGHT = PALETTE_RECT.height # 色板的矩形的高度
LINE_WIDTH_MAX = 20 # 线条的最大粗度def clamp(value, low, high):return low if value < low else (high if value > high else value)def clamp_pos(pos):y = clamp(pos[1], PALETTE_HEIGHT, 800)return (pos[0], y)def dist_between_points(point1, point2):return ((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2) ** 0.5class DrawingBoard:def __init__(self):pygame.init()self.screen = pygame.display.set_mode((800, 800))self.screen.fill((255, 255, 255))pygame.display.set_caption('Drawing Board')self.color = (0, 0, 0) # 画笔的颜色self.width = 1 # 线条的粗细self.last_pos = self.pos = (0, 0) # 鼠标上一帧的位置以及当前位置self.circles = [] # 色板中由圆圈的中心和对应颜色的元组组合而成。self.font = pygame.font.SysFont('Times New Roman', 20) # 字体self.drawing = False # 用户是否正在绘画self.draw_colors()self.draw_text()def draw_colors(self):for i in range(len(COLORS)):center = (i * CENTER_DIST + GAP + RADIUS, GAP + RADIUS)pygame.draw.circle(self.screen, COLORS[i], center, RADIUS)self.circles.append((center, COLORS[i]))pygame.draw.line(self.screen, (0, 0, 0), (0, PALETTE_HEIGHT), (800, PALETTE_HEIGHT))def draw_text(self):surf = self.font.render(f'Line width: {self.width}', True, (0, 0, 0))rect = surf.get_rect()rect.right = PALETTE_RECT.right - GAPrect.centery = PALETTE_RECT.centeryfill_rect = pygame.Rect(rect.left - 5, rect.top - 5, rect.width + 10, rect.height + 10)pygame.draw.rect(self.screen, (255, 255, 255), fill_rect)self.screen.blit(surf, rect)def on_mouse_down(self, pos):if PALETTE_RECT.collidepoint(pos):for center, color in self.circles:if dist_between_points(pos, center) < RADIUS:self.color = colorelse:self.last_pos = self.pos = posself.drawing = Truedef on_mouse_move(self, pos):if self.drawing:self.pos = clamp_pos(pos)pygame.draw.line(self.screen, self.color, self.last_pos, self.pos, self.width)self.last_pos = clamp_pos(pos)def on_key_down(self, key):if self.drawing:returnif key == pygame.K_UP:self.width += 1self.width = clamp(self.width, 1, LINE_WIDTH_MAX)self.draw_text()elif key == pygame.K_DOWN:self.width -= 1self.width = clamp(self.width, 1, LINE_WIDTH_MAX)self.draw_text()def process_events(self):for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit()elif event.type == pygame.MOUSEBUTTONDOWN:self.on_mouse_down(event.pos)elif event.type == pygame.MOUSEMOTION:self.on_mouse_move(event.pos)elif event.type == pygame.MOUSEBUTTONUP:self.drawing = Falseelif event.type == pygame.KEYDOWN:self.on_key_down(event.key)def run(self):while True:self.process_events()pygame.display.update()if __name__ == '__main__':app = DrawingBoard()app.run()
代码运行截图:
结语
以上,就是本章的所有内容。在下一章,你将学习如何监测游戏时间。
Pygame 教程(4):图像传输和绘制文本相关推荐
- Pygame 教程(3):绘制图形
本章,你将学习如何在 Pygame 中绘制图形. 导航 上一章:重要的概念及对象 下一章:图像传输和绘制文本 文章目录 导航 抗锯齿 draw 模块 实例:跟随鼠标的图形 创建初始窗口 添加变量 捕捉 ...
- Pygame 教程(5):监测游戏时间
本章,你将学习如何监测游戏时间. 导航 上一章:图像传输和绘制文本 下一章:努力更新中-- 文章目录 导航 监测时间 游戏帧速率 实例:绘图性能对比 结语 监测时间 在游戏程序中,时常需要随着时间的流 ...
- Pygame 教程(4)拓展:使用 subsurface 方法
原文:图像传输和绘制文本 在本拓展文章,将展示如何使用pygame.Surface.subsurface方法实现原文中的更改线条粗细步骤. pygame.Surface.subsurface将获取原S ...
- python PyQt5中文教程☞【第十节】PyQt5绘图(绘制文本drawText()、画点drawPoints()、设置颜色、QPen(画笔)绘制线条、QBrush(笔刷)绘制纹理
引用文章:http://code.py40.com/pyqt5/32.html 文章目录 绘制文本 画点 PyQt5颜色 QPen(画笔) QBrush(笔刷) 总结:一发现有事件触发就会更新QWid ...
- HTML5 Canvas中绘制文本
绘制文本 画布中不仅可以绘制图形,还可以绘制文本.绘制文本,既可以使用填充方法,也可以使用勾勒方法: fillText(text, x, y, [maxWidth]) strokeText(text, ...
- 使用WebBrowser控件时在网页元素上绘制文本或其他自定义内容
第一次在CNBlogs上发Post是提出一个有关使用WebBrowser控件时对SELECT网页元素操作的疑惑,这个问题至今也没有解决,后来有朋友在该Post的评论里询问WebBrowser控件如何在 ...
- 【matplotlib教程】绘图样式,文本线型、轴刻度
系列文章目录 [matplotlib教程]简介.安装.示例 [matplotlib教程]绘图样式,文本线型.轴刻度 [matplotlib教程]使用各种类型数据绘图 [matploblib教程]一文带 ...
- pygame教程实例(八)不用3D引擎也可以写3D画面
上一篇:pygame教程实例(七)python实现贪吃蛇自动寻路 目录:pygame游戏教程目录 效果图: 代码参考自:https://github.com/Apress/beg-python-gam ...
- 数据可视化库 matplotlib 入门 8——绘制文本与数学表达式、图形处理
Matplotlib 库使用入门8 绘制文本与数学表达式 绘制文本的函数与功能 绘制数学表达式 图像处理 在前面关于 matploblib 的文章中,笔者分别介绍了: matplotlib 库的安装与 ...
最新文章
- docker 安装 solr搜索引擎
- 台湾积体电路制造公司(简称为台积电(TSMC))的28nm LP、HPM、HPC、HPC+四种不同处理器工艺版本的区别?
- php 警告提示框,关于javascript:php重定向到带有警告对话框的页面
- kubeadmin 安装 k8s集群
- linux ntfs 新建,Linux在NTFS中创建的文件的权限
- 【转】 NSArray copy 问题
- 机器视觉:USB 3.0知识答疑
- 记录报错:java.lang.NullPointerException org.apache.jsp.test_jsp._jspInit(test_jsp.java:23)
- 超全面的权限系统设计方案!(万能通用)
- 集合的洗牌,排序,拆分以及常用遍历方法
- 开关电源环路学习笔记(2)-线性化条件
- 导致页面布局混乱的几个元凶
- Huffman编码解压缩的通俗讲解
- 铜陵市商标注册申请流程以及阶段时间介绍
- Elixir元编程-第三章 编译时代码生成技术进阶
- 苹果宣布推出新的Mac Mini和MacBook Pro与M2 Pro和M2 Max
- Global Malmquist-Luenberger 指数分解及matlab应用,文后有网盘链接
- Python大数据培训班特色优势及工作方向
- c语言采用文件存储数据,C语言读写文件大全 之 基础篇
- Python中用户界面设计(GUI)