Python-2-6-图形秘籍-全-

Python 2.6 图形秘籍(全)

原文:zh.annas-archive.org/md5/dba691c3243e1b563393e4685a29c516

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Python 2.6 图形烹饪书是一本收集了直接食谱和说明截图的书籍,用于使用 Python 语言创建和动画图形对象。这本书通过在图形工作区工作,使开发图形的过程变得有趣和娱乐,无需掌握复杂语言定义和晦涩的示例。

本书涵盖内容

第一章,启动引擎: 本章解释了如何获取和安装 Python 解释器,无论是针对 MS Windows 还是 Linux,以及如何验证 Python 是否正确安装。本章解释了如何创建可以在没有安装 Python 的客户计算机上运行的完整工作程序。

第二章,绘制基本形状: 这展示了如何创建所有基本图形元素,包括线条、圆圈、椭圆形、矩形、多边形和复杂曲线。提供了简单示例来演示如何绘制基本形状。这些示例也提供了供以后参考的现成资料。

第三章,处理文本: 本章演示了如何使用在特定操作系统上安装的任何字体类型来控制字体大小、颜色和位置。展示了发现和演示操作系统上所有可用字体的一种简单方法。

第四章,动画原理: 本章从不同位置的圆的简单序列示例开始,系统地进展到在重力场内弹性球平滑移动的动画。

第五章,颜色的魔法: 本章从使用 Python 可识别的颜色名称组装调色板开始。解释了使用数字构建颜色,以混合控制的红、绿和蓝色量的方式。构建了匹配任何样本颜色的工具。本章演示了如何将一种颜色的阴影变化到另一种颜色。

第六章,处理图片: 本章揭示了如何获取和使用 Python 图像库来操作照片图像。它还展示了图像格式转换、调整大小、旋转、颜色转换和复杂过滤的方法。

第七章,结合矢量图像和光栅图像: 本章演示了将动画矢量图形与照片图像结合以产生复杂动画的方法。

第八章, 数据输入和输出: 本章从将文件存储和检索到硬盘的基本操作开始,进而构建用于创建、存储和检索使用鼠标绘制的自由形状的程序。

第九章, 与 Tkinter 形状交换 Inkscape SVG 绘图: 本章详细说明了如何使用 Inkscape 绘图工具将来自照片图像的形状转换为一系列点,这些点在 Python 中重现形状。一旦一条线被表示为 Python 序列,它就可以以多种方式进行数值转换。

第十章, GUI 构建:第一部分: 本章提供了创建按钮、数据输入框、下拉菜单、列表框和文本标签的基本示例。它还涵盖了如何自定义按钮外观。

第十一章, GUI 构建:第二部分: 这里解释和演示了 Grid 布局管理器和 Pack 布局管理器。提供了单选按钮、复选按钮、滚动条、框架和按键事件编码的示例。它还展示了如何使用画布上的图形元素构建小部件。

附录, 在 Microsoft Windows 中运行 Python 程序的快速技巧: 这提供了如何克服新 Python 程序员在尝试在 Windows 中使用 Python 时可能遇到的一些困难的解释。

你需要这本书的内容

为了运行这本书中的代码,读者需要一个 Linux 操作系统或 Microsoft Windows,以及从互联网上下载 Python、Python Imaging Library 和 Inkscape 的方法。所有这些应用程序都是免费和开源的。代码是在 Linux Ubuntu 版本 9.04、Microsoft Windows XP 和 Windows 7 上开发的。

这本书的读者对象

这本书是为想要使用 Python 进行图形编程的简单、清晰的示例的 Python 程序员而写的。示例旨在帮助任何想要在 Python 程序中使用图形元素和图像的人,以最少的复杂性。目标读者包括学者和教师,以及工程师和技术人员。

习惯用法

在这本书中,你会找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码词如下所示: "这里的新特性是函数 detect_Wall_Collision()."

代码块如下设置:

posn_x = 1 # x position of box containing the ball (bottom)
posn_y = 1 # y position of box containing the ball (left edge)
shift_x = 3 # amount of x-movement each cycle of the 'for' loop
shift_y = 2 # amount of y-movement each cycle of the 'for' loop

新术语重要词汇以粗体显示。你会在屏幕上看到这些词汇,例如在菜单或对话框中,文本中会像这样显示: "在 Windows 中,你只需访问网站并点击下载按钮,它就会安装并立即可以使用"。

注意

警告或重要注意事项以这样的框出现。

注意

小技巧和技巧看起来像这样。

读者反馈

读者反馈始终欢迎。告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正从中受益的标题非常重要。

要发送一般反馈,只需将电子邮件发送到< feedback@packtpub.com>,并在邮件主题中提及书籍标题。

如果您有一本书希望我们出版,请通过www.packtpub.com上的建议书名表单或通过电子邮件< suggest@packtpub.com>发送给我们。

如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们关于www.packtpub.com/authors的作者指南。

客户支持

现在您是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。

小贴士

下载本书的示例代码

您可以从www.PacktPub.com上下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.PacktPub.com/support,注册以将文件直接通过电子邮件发送给您。

勘误

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现了错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/support,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的勘误部分下的现有勘误列表中。任何现有勘误都可以通过从www.packtpub.com/support选择您的标题来查看。

侵权

互联网上版权材料的侵权是一个跨所有媒体持续存在的问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以追究补救措施。

请通过< copyright@packtpub.com>与我们联系,并提供涉嫌侵权材料的链接。

我们感谢您在保护我们作者和我们提供有价值内容的能力方面提供的帮助。

问题

如果你在本书的任何方面遇到问题,可以通过< questions@packtpub.com>联系我们,我们将尽力解决。

第一章. 启动你的引擎

在本章中,我们将涵盖:

  • 最短的 Python 程序

  • 确保 Python 模块存在

  • 使用 Tkinter 的基本 Python GUI

  • 在 Linux 下创建编译可执行文件

  • 在 MS Windows 下创建编译可执行文件

简介

本书是使用神奇的 Python 语言创建和动画图形对象的代码食谱集合。为了创建和操作图形对象,Python 使用 Tkinter 模块。使用 Python 和 Tkinter 的先决条件显然是两者都已安装。它们都是免费和开源的,获取和安装它们的说明在网络上大量可用。只需在 Google 上搜索类似“安装 Python”的短语,你将有很多选择。

我们的首要任务是证明 Python 和 Tkinter 已安装且在我们的计算机上运行正常。在这本书中,我们使用 Python 版本 2.6。2010 年发布的 Python 3.0 需要一些语法上的更改,我们在这本书中不会使用。

让我们看看一些简单的测试来检查 Python 是否已安装。如果我们下载并安装 Windows 上的 Python,它将自动包括 Tkinter 作为其中一个基本模块,因此我们不需要单独获取和安装它。

运行最短的 Python 程序

我们需要一个单行 Python 程序,以证明 Python 解释器已安装且在我们的计算机平台上运行。

如何操作...

  1. 创建一个名为 construction_work 或简称 constr 的文件夹(目录)。你将把所有的 Python 程序放在这个目录中。在一个文本编辑器中,例如 Linux 上的 gedit 或 Windows 上的记事本。如果我们正在 Windows 上工作,有一个名为 "Context" 的优秀编辑器可以免费从 www.contexteditor.org/ 下载。Context 对 Python 语法敏感,并具有一个有用的搜索和替换功能。

  2. 输入以下行:

    Print 'My wereld is "my world" in Dutch'
    
    
  3. 将其保存为名为 simple_1.py 的文件,位于名为 constr 的目录中。

  4. 如果你使用 MS Windows,请打开 X 终端或 DOS 窗口。

  5. 切换到 constr 目录 - simple_1.py 所在的位置。

  6. 输入python simple_1.py,你的程序将会执行。结果应该看起来像下面的截图:如何操作...

  7. 这证明了你的 Python 解释器工作正常,你的编辑器工作正常,并且你理解了运行本书中所有程序所需的所有内容。恭喜你。

    """
    Program name: simplest_1.py
    Objective: Print text to the screen.
    Keywords: text, simplest
    =========================
    Printed "mywereld" on terminal.
    Author: Mike Ohlson de Fine
    """
    Print 'mywereld is "my world" in Dutch'
    
    

它是如何工作的...

你在 Linux X 终端或 MS Windows 的 DOS 终端中输入的任何指令都被视为操作系统命令。通过从你的 Python 程序存储的同一目录中启动这些命令,你不需要告诉 Python 和操作系统在哪里搜索你的代码。你也可以将代码存储在另一个目录中,但那时你需要在程序名称前加上路径。

还有更多...

尝试以下程序中显示的相同基本打印指令的较长版本。

所有在 """(三重引号)之间的文本纯粹是为了良好的文档记录。它是为程序员准备的,包括你。唉,人类的记忆是不完美的。痛苦的经验会说服你,在程序中提供相当完整的信息作为标题以及程序内的注释是明智的。

然而,为了节省空间和避免干扰,本书的其余部分省略了这些标题注释。

确保 Python 模块存在

这里是上一个程序的稍长版本。然而,以下模块在我们的程序中被命令“报到”以备使用,尽管它们在这个时候实际上并没有被使用:Tkinter, math, random, time, tkFont

我们需要确保我们稍后将要使用的所有 Python 模块都存在并且可以被 Python 以及我们的代码访问。每个模块都是一个包含代码函数和对象的独立库,这些函数和对象经常被程序中的命令调用。

如何做...

  1. 在文本编辑器中输入以下代码行。

  2. 将其保存为名为 simple_2.py 的文件,放在之前提到的 constr 目录中。

  3. 如前所述,如果你使用的是 MS Windows,请打开 X 终端或 DOS 窗口。

  4. 切换到 constr 目录 - simple_1.py 所在的位置。

  5. 输入 python simple_2.py,我们的程序应该执行。结果应该看起来像以下截图:如何做...

    • 这证明了你的 Python 解释器可以访问它将需要的必要库函数。

      """
      Program name: simplest_2.py
      Objective: Send more than one line of text to the screen.
      Keywords: text, simple
      ======================================
      Author: Mike Ohlson de Fine
      """
      import Tkinter
      import math
      import random
      import time
      import tkFont
      print "======================================="
      print "A simple, apparently useless program like this does useful things:"
      print "Most importantly it checks whether your Python interpreter and "
      print "the accompanying operating system environment works"
      print " - including hardware and software"
      print "======================================"
      print " No matter how simple it is always a thrill"
      print " to get your first program to run"
      
      

它是如何工作的...

print 命令是一条指令,用于将引号之间的任何文本(如“显示 这些单词”)写入或打印到连接到你的计算机的监视器屏幕上。它还会打印在 print 后面输入的任何命名变量或表达式的值。

例如:打印 "狗的 名字:",dog_name。其中 dog_name 是用于存储一些数据的变量的名字。

print 命令在调试复杂代码序列时非常有用,因为即使执行因错误而未能完成,到达错误之前遇到的任何 print 命令都将被尊重。因此,通过在代码中巧妙地放置各种打印语句,你可以聚焦于导致程序崩溃的原因。

还有更多...

当你第一次编写一段 Python 代码时,你通常有点不确定你对逻辑的理解是否完全正确。因此,我们希望以探索的方式观察指令执行的进度。能够看到至少部分代码是有效的非常有帮助。Python 的一个主要优势是它一次执行一条指令,并逐步执行。它只会在达到终点或编程错误阻止进度时停止。如果我们有一个 20 行的程序,只有前五行是正确的,其余的都是无法执行的垃圾,Python 解释器至少会执行前五行。这就是print命令真正强大的地方。

这就是如何使用 print 和 Python 解释器。当我们遇到代码问题,它就是无法工作,我们正在努力找出原因时,我们可以在程序的各个选定的点上插入 print 语句。这样,你可以得到一些变量的中间值作为你自己的私有状态报告。当我们想要关闭我们的 print 监视器时,我们只需在前面输入一个 hash(#)符号,这样就可以将它们转换成被动注释。稍后,如果你改变主意,想要再次激活 print,你只需移除前面的 hash 符号。

一个基本的 Tkinter 程序

在这里,我们尝试在 Python 程序中执行一个 Tkinter 命令。Tkinter 指令将创建一个画布,然后在上面画一条直线。

如何做到这一点...

  1. 在文本编辑器中,输入以下代码。

  2. 将其保存为名为simple_line_1.py的文件,再次放在名为constr的目录中。

  3. 如前所述,如果你使用的是 MS Windows,请打开一个 X 终端或 DOS 窗口。

  4. 切换到constr目录 - simple_line_1.py所在的位置。

  5. 输入python simple_line_1.py,你的程序应该执行。命令终端的结果应类似于以下截图:如何做到这一点...

  6. Tkinter 画布输出应类似于以下截图:如何做到这一点...

  7. 这证明了你的 Python 解释器工作正常,你的编辑器工作正常,Tkinter 模块也工作正常。这不是一个微不足道的成就,你肯定准备好迎接伟大的事情。做得好。

    #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    from Tkinter import *
    root = Tk()
    root.title('Very simple Tkinter line')
    canvas_1 = Canvas(root, width=300, height=200, background="#ffffff")
    canvas_1.grid(row=0, column=0)
    canvas_1.create_line(10,20 , 50,70)
    root.mainloop()
    #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
    
它是如何工作的...

要画一条线,我们只需要给出起点和终点。

起点是canvas_1.create_line(10,20, 50,70)中的第一对数字。另一种方式,起点由坐标x_start=10y_start=20给出。线的终点由第二对数字指定x_end=50y_end=70。计量单位是像素。像素是我们屏幕上可以显示的最小点。

对于所有其他属性,如线条粗细或颜色,默认的create_line()方法值被使用。

然而,如果你想要更改颜色或粗细,你只需通过指定设置来完成。

在 Windows 和 Linux 下创建一个编译后的可执行文件

我们如何创建和执行一个.exe 文件,使其能够运行我们编译后的 Python 和 Tkinter 程序?我们能否制作一个自包含的文件夹,在 MS Windows 或 Linux 发行版上运行,而这些发行版使用的 Python 版本与我们使用的不同?这两个问题的答案都是肯定的,实现这一目标的工具是一个名为cx_Freeze的开源程序。我们通常希望将我们的工作 Python 程序放在 U 盘上或在网络上下载,并且能够向朋友、同事或客户展示,而无需在客户端系统上下载 Python。cx_Freeze允许我们创建我们 Python 图形程序的发行版本。

准备工作

您需要从cx-freeze.sourceforge.net/下载cx_Freeze。我们需要选择与我们所使用的 Python 版本相同版本的版本。目前,有从 2.4 版到 3.1 版的版本可供选择。

  1. MS Windows:下载cx_Freeze-4.2.win32-py2.6.msi,这是 Python 2.6 的 Windows 安装程序。如果我们有其他 Python 版本,那么我们必须从cx-freeze.sourceforge.net/选择适当的安装程序。

  2. 保存并运行此安装程序。

  3. Windows 安装成功完成后,我们将看到一个名为cx_Freeze的文件夹,位于\Python26\Lib\site-packages\中。

  4. 在终端中运行命令apt-get install cx-freeze

  5. 如果这不起作用,我们可能需要首先通过运行命令apt-get install python-dev安装一个具有开发能力的 Python 版本。然后返回并重复步骤 1。

  6. 通过在命令终端中输入python来调用 Python 解释器进行成功测试。

  7. 然后,在>>>提示符后,输入import cx_Freeze。如果解释器返回新行和>>>提示符,且没有任何抱怨,我们就成功了。

  8. 如果我们想要打包为可执行文件的文件名为walking_birdy_1.py,位于名为/constr的文件夹中,那么我们准备一个特殊的设置文件如下。

    #setup.py
    from cx_Freeze import setup, Executable
    setup(executables=[Executable("/constr/walking_birdy_1.py")])
    
    
  9. 将其保存为setup.py

  10. 然后,在命令终端中运行

    python /constr/setup.py build
    
    
  11. 我们会在命令终端中看到许多系统编译命令滚动下来,最终会停止而不会出现错误信息。

  12. 我们将在名为build的文件夹中找到完整的自包含可执行文件。在 Linux 下,我们将在家目录下的/build/exe.linux-i686-2.6中找到它。在 MS Windows 下,我们将在C:\Python26\build\exe.win-py2.6中找到它。

  13. 我们只需要将包含所有内容的build文件夹复制到我们想要运行自包含程序的地方。

它是如何工作的...

提醒一下。如果我们使用代码中的外部文件,如图片,那么文件的路径地址必须是绝对路径,因为它们被编码到或冻结到我们的 Python 程序的可执行版本中。有设置搜索路径的方法,可以在cx-freeze.sourceforge.net/cx_Freeze.html查看。

例如,假设我们想在程序中使用一些 GIF 图片,并在其他计算机上展示它们。首先,我们将一个名为,例如,/birdy_pics 的文件夹放置在 U 盘上。在原始程序 walking_birdy_1.py 中,确保路径地址指向 U 盘上的 /birdy_pics 文件夹。编译后,将 build 文件夹复制到 U 盘上。现在,当我们双击可执行文件 walking_birdy_1 时,它可以在需要时定位到 U 盘上的图片。这些文件包含了程序所需的所有内容,你应该将整个目录内容分发给任何想要运行你的程序但不需要安装 Python 或 Tkinter 的用户。

另有一个名为 py2exe 的程序,它也能创建在 MS Windows 上运行的可执行文件。然而,它无法创建在 Linux 下运行的独立二进制可执行文件,而 cx_Freeze

第二章:绘制基本形状

在本章中,我们将涵盖:

  • 直线和坐标系统

  • 绘制虚线

  • 带箭头和端盖的样式各异的线条

  • 带有尖锐弯曲的两段线条

  • 带有弯曲的线条

  • 绘制复杂的存储形状 - 卷须

  • 绘制矩形

  • 绘制重叠的矩形

  • 绘制同心正方形

  • 从椭圆得到的圆

  • 从弧得到的圆

  • 三个椭圆

  • 最简单的多边形

  • 星形多边形

  • 复制星星的艺术

简介

图形都是关于图片和绘制的。在计算机程序中,线条不是由手持铅笔的手绘制的,而是通过在屏幕上操作数字来绘制的。本章提供了本书其余部分所需的精细细节或原子结构。在这里,我们以最简单的形式阐述了最基本的图形构建块。最有用的选项在自包含的程序中展示。如果您愿意,可以使用代码而不必详细了解其工作原理。您可以边做边学。您可以边玩边学,而玩耍是无技能动物为了学习几乎一切生存所需的东西而进行的严肃工作。

您可以复制粘贴代码,它应该无需修改即可正常工作。代码很容易修改,并鼓励您对其进行调整,修改绘图方法中的参数。您调整得越多,理解得就越多。

在 Python 中,绘制线条和形状的屏幕区域是画布。当执行 Tkinter 方法 canvas() 时创建画布。

使用数字描述线条和形状的核心是一个坐标系统,它说明了线条或形状的起始点和结束点。在 Tkinter 中,就像在大多数计算机图形系统中一样,屏幕或画布的左上角是起点,而右下角是终点,最大的数字描述了位置。这个系统在下一张图中展示,它是通用的计算机屏幕坐标系统。

简介

直线和坐标系统

在画布上绘制一条直线。重要的是要理解,坐标系统的起点始终位于画布的左上角,如图中所示。

如何操作...

  1. 在文本编辑器中输入出现在两个 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 分隔符之间的以下行。

  2. 将其保存为名为 line_1.py 的文件,再次放在名为 constr 的目录中。

  3. 如前所述,如果您使用的是 MS Windows,请打开一个 X 终端或 DOS 窗口。

  4. 切换目录(命令 cd /constr)到 constr 目录 - 其中包含 line_1.py

  5. 输入 python line_1.py 并运行您的程序。结果应该看起来像下面的截图:如何操作...

    # line_1.py
    #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    from Tkinter import *
    root = Tk()
    root.title('Basic Tkinter straight line')
    cw = 800 # canvas width, in pixels
    ch = 200 # canvas height, in pixels
    canvas_1 = Canvas(root, width=cw, height=ch)
    canvas_1.grid(row=0, column=1) # placement of the canvas
    x_start = 10 # bottom left
    y_start = 10
    x_end = 50 # top right
    y_end = 30
    canvas_1.create_line(x_start, y_start, x_end,y_end)
    root.mainloop()
    #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
    

如何工作...

我们为线条编写的坐标与上一章中的方式不同,因为我们想将符号赋值引入到 create_line() 方法中。这是使我们的代码可重用的初步步骤。指定定义线条位置的点的位置有多种方法。最整洁的方法是定义一个名为 Python 列表或元组的名称,然后只需将此列表名称作为 create_line() 方法的参数插入即可。

例如,如果我们想画两条线,一条从 (x=50, y=25) 到 (x=220, y=44),另一条线从 (x=11, y=22) 到 (x=44, y=33),那么我们可以在程序中写下以下几行:

  • line_1 = 50, 25, 220, 44 # 这是一个元组,永远不能改变

  • line_2 = [11, 22, 44, 33] # 这是一个列表,可以随时更改。

  • canvas_1.create_line(line_1)

  • canvas_1.create_line(line_2)

注意,尽管 line_1 = 50, 25, 220, 44 在语法上是正确的 Python 代码,但它被认为是一种较差的 Python 语法。更好的写法是 line_1 = (50, 25, 220, 44),因为这更加明确,因此对阅读代码的人来说更清晰。另一个需要注意的点是 canvas_1 是我给特定大小的画布实例所取的一个任意名称。你可以给它取任何你喜欢的名字。

还有更多...

大多数形状都可以由以多种方式连接在一起的线段组成。Tkinter 提供的一个极其有用的属性是将直线序列转换为平滑曲线的能力。这种线条属性可以用令人惊讶的方式使用,并在第 6 个菜谱中进行了说明。

画一条虚线

画一条直线虚线,线宽为三像素。

如何做到...

在上一个示例中使用的说明仍然适用。唯一的变化是 Python 程序的名称。这次你应该使用名称 dashed_line.py 而不是 line_1.py

# dashed_line.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Dashed line')
cw = 800 # canvas width
ch = 200 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch)
canvas_1.grid(row=0, column=1)
x_start = 10
y_start = 10
x_end = 500
y_end = 20
canvas_1.create_line(x_start, y_start, x_end,y_end, dash=(3,5), width = 3)
root.mainloop()#

它是如何工作的...

这里新增加的是为线条添加一些样式规范。

dash=( 3,5) 表示应该有三个实像素后跟五个空白像素,width = 3 指定线宽为 3 像素。

还有更多...

你可以指定无限多种虚线-空格模式。一个指定为 dash = (5, 3, 24, 2, 3, 11) 的虚线-空格模式将导致一条线,其模式在整个线长上重复三次。模式将包括五个实像素后跟三个空白像素。然后会有 24 个实像素后跟仅两个空白像素。第三种变化是三个实像素后跟 11 个空白像素,然后整个三模式集再次开始。虚线-空格对的列表可以无限延长。偶数长度的规范将指定实像素的长度。

注意

在不同的操作系统上,虚线属性可能会有所不同。例如,在 Linux 操作系统上,它应该遵守线与空间距离的指令,但在 MS Windows 上,如果虚线长度超过十像素,则不会尊重实线-虚线指令。

带有箭头和端盖的样式各异的线条

画了四条不同风格的线条。我们看到如何获得颜色和端形状等属性。使用虚线属性的说明,通过 Python 的for 循环制作了一个有趣的图案。此外,画布背景的颜色已被设置为绿色。

如何做...

应再次使用配方 1 中的说明。

当你编写、保存和执行这个程序时,只需使用名称4lines.py

已经将箭头和端盖引入到线规格中。

#4lines.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Different line styles')
cw = 280 # canvas width
ch = 120 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="spring \ green")
canvas_1.grid(row=0, column=1)
x_start, y_start = 20, 20
x_end, y_end = 180, 20
canvas_1.create_line(x_start, y_start, x_end,y_end,\
dash=(3,5), arrow="first", width = 3)
x_start, y_start = x_end, y_end
x_end, y_end = 50, 70
canvas_1.create_line(x_start, y_start, x_end,y_end,\
dash=(9,5), width = 5, fill= "red")
x_start, y_start = x_end, y_end
x_end, y_end = 150, 70
canvas_1.create_line(x_start, y_start, x_end,y_end, \
dash=(19,5),width= 15, caps="round", \ fill= "dark blue")
x_start, y_start = x_end, y_end
x_end, y_end = 80, 100
canvas_1.create_line(x_start, y_start, x_end,y_end, fill="purple")
#width reverts to default= 1 in absence of explicit spec.
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

要画线,你只需要给出起点和终点。

它是如何工作的...

上一张截图显示了在 Ubuntu Linux 上执行的结果。

在这个例子中,我们通过重新使用之前的线位置说明节省了一些工作。请看接下来的两张截图。

它是如何工作的...

上一张截图显示了在 MS Windows XP 上执行的结果。

还有更多...

这里你可以看到 Linux 和 MS Windows 使用 Tkinter 绘制虚线的能力差异。虚线的实线部分被指定为 19 像素长。在 Linux(Ubuntu9.10)平台上,这个指定被尊重,但 Windows 忽略了指令。

一个有两个段且弯曲尖锐的线条

线不必是直的。更一般类型的线可以由许多直线段连接而成。你只需决定你想连接多段线各部分的位置以及它们应该连接的顺序。

如何做...

指令与配方 1 相同。当你编写、保存和执行这个程序时,只需使用名称sharp_bend.py

只需列出定义每个点的x,y对,并将它们按你想要连接的顺序排列。列表可以任意长。

#sharp_bend.py
#>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Sharp bend')
cw = 300 # canvas width
ch = 200 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
x1 = 50
y1 = 10
x2 = 20
y2 = 80
x3 = 150
y3 = 60
canvas_1.create_line(x1,y1, x2,y2, x3,y3)
root.mainloop()

它是如何工作的...

为了清晰起见,只定义了三个点:第一个点为=(x1,y1),第二个点为=(x2,y2),第三个点为=(x3, y3)。然而,指定顺序点的数量并没有限制。

它是如何工作的...

上一张截图显示了带有尖锐弯曲的线条。

还有更多...

最终,你可以在某些存储设备上的文件中存储复杂的图形,作为长序列的点。例如,你可能想制作类似卡通条的东西。

你可以构建一个从不同角度看到的身体部位和面部特征的库。可能会有不同形状的嘴和眼睛。组装你的漫画条的任务可以部分自动化。你需要考虑的一件事是如何调整组件的大小,以及如何将它们放置在不同的位置,甚至将它们旋转到不同的角度。所有这些想法都在这本书中得到了发展。

尤其是查看以下示例,了解复杂形状如何以相对紧凑的形式存储和处理。用于绘图操作的SVG缩放矢量图形)标准,尤其是在网页上,用于表示形状的约定与 Tkinter 类似但不同。由于 SVG 和 Tkinter 都得到了很好的定义,这意味着你可以构建代码以将一种形式转换为另一种形式。

有关此内容的示例请见第六章,

一条带有弯曲的线

最有趣的线条是弯曲的。将上一个例子中的直线、两段线改为与每段线端平行拟合的平滑曲线。Tkinter 使用 12 段直线来制作曲线。12 段是默认数量。然而,你可以将其更改为任何其他合理的数字。

如何做到...

canvas_1.create_line(x1,y1, x2,y2, x3,y3)这一行替换为canvas_1.create_line(x1,y1, x2,y2, x3,y3, smooth="true")

线现在弯曲了。这在制作我们只需要指定少量点的绘图时非常有用,Tkinter 会将其拟合成曲线形状。

如何工作...

smooth="true"属性被设置时,程序输出结果将在下一张截图显示。smooth='true'属性隐藏了大量在幕后进行的严肃数学曲线制造过程。

要将曲线拟合到一对相交的直线,曲线和直线在开始和结束时需要平行,但在中间则使用一种称为样条拟合的完全不同的过程。结果是这种曲线平滑处理在计算上非常昂贵,如果你做得太多,程序执行速度会减慢。这影响了哪些动作可以被成功动画化。

如何工作...

还有更多...

我们稍后要做的是使用曲线属性来制作更令人愉悦和令人兴奋的形状。最终,你可以为自己积累一个形状库。如果你这样做,你将重新创建一些可以从网络上免费获取的矢量图形。看看 www.openclipart.org。从这个网站上免费下载的图片是 SVG(缩放矢量图形)格式。如果你在文本编辑器中查看这些图片的代码,你会看到一些代码行,它们与这些 Tkinter 程序指定点的方略有几分相似。在 第六章 中将演示从现有的 SVG 图片中提取有用形状的一些技术,

绘制复杂的形状——卷曲的藤蔓

这里的任务是绘制一个复杂的形状,以便你可以将其用作框架,产生无限多样性和美丽。

我们开始用铅笔和纸绘制一个卷曲生长的藤蔓形状,并以最简单、最直接的方式将其转换成一些代码,以绘制它。

这是一个非常重要的例子,因为它揭示了 Python 和 Tkinter 的基本优雅性。Python 的核心启发设计理念可以用两个词来概括:简单和清晰。这就是 Python 成为有史以来最好的计算机编程语言之一的原因。

准备工作

当他们想要创建一个全新的设计时,大多数图形艺术家都会从铅笔和纸草图开始,因为这给了他们无杂乱的潜意识自由。对于这个例子,需要一个复杂的曲线,这种有机设计用于在古董书中装裱图片。

用铅笔在纸上画出平滑的线条,并在大约均匀间隔的地方用 X 标记。使用毫米刻度尺,测量每个 x 到左边和纸张底部的距离,大约测量。由于线条的曲线性质会补偿小的缺陷,所以不需要高精度。

如何做到这一点...

这些测量值,Tkinter 画布的 x 和 y 方向上各有 32 个,被输入到单独的列表中。一个叫做 x_vine 的用于 x 坐标,另一个叫做 y_vine 的用于 y 坐标。

除了这种手工制作原始形状的方式之外,其余的步骤与所有之前的示例相同。

# vine_1.py
#>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Curley vine ')
cw = 180 # canvas width.
ch = 160 # canvas height.
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
# The curly vine coordinates as measured from a paper sketch.
vine_x = [23, 20, 11, 9, 29, 52, 56, 39, 24, 32, 53, 69, 63, 47, 35, 35, 51,\
82, 116, 130, 95, 67, 95, 114, 95, 78, 95, 103, 95, 85, 95, 94.5]
vine_y = [36, 44, 39, 22, 16, 32, 56, 72, 91, 117,125, 138, 150, 151, 140, 123, 107,\
92, 70, 41, 5, 41, 66, 41, 24, 41, 53, 41, 33, 41, 41, 39]
#=======================================
# The merging of the separate x and y lists into a single sequence.
#=======================================
Q = [ ]
# Reference copies of the original vine lists - keep the originals # intact
X = vine_x[0:]
Y = vine_y[0:]
# Name the compact, merged x & y list Q
# Merge (alternate interleaves of x and y) into a single polygon of # points.
for i in range(0,len(X)):
Q.append(X[i]) # append the x coordinate
Q.append(Y[i]) # then the y - so they alternate and you end # with a Tkinter polygon.
canvas_1.create_line(Q, smooth='true')
root.mainloop()
#>>>>>>>>>>>>

它是如何工作的...

结果在下一张屏幕截图中显示,这是一条由 32 段直线组成的平滑线条。

如何工作...

这个任务中的关键技巧是创建一个数字列表,该列表可以精确地放入 create_line() 方法中。它必须是一个不间断的序列,由逗号分隔,包含我们想要绘制的复杂曲线的匹配的 x 和 y 位置坐标对。

因此,我们首先创建一个空列表 Q[],我们将向其中追加 x 和 y 坐标的交替值。

因为我们希望保留原始列表 x_viney_vine 的完整性(可能用于其他地方的重用),所以我们使用以下方式创建工作副本:

X = vine_x[0:]
Y = vine_y[0:]

最后,通过以下方式将魔法交错合并到一个列表中:

for i in range(0,len(X)):
Q.append(X[i]) # append the x coordinate
Q.append(Y[i]) # then the y

for in range() 组合及其后的代码块以循环方式遍历代码,从 i=0 开始,每次增加一个,直到达到最后一个值 len(X)。然后退出代码块,执行继续到块下方。len(X) 是一个返回(在程序员术语中称为“返回”)X 中元素数量的函数。Q 从这里产生,非常适合立即在 create_line(Q) 中绘制。

如果你省略了 smooth='true' 属性,你将看到来自原始论文绘制和测量过程的原始连接点。

还有更多...

通过以各种方式复制和变换卷曲的藤蔓,在第六章中产生了诸如卷曲烟雾、炭笔和发光霓虹灯等有趣的效果,

绘制一个矩形

通过指定位置、形状和颜色属性作为命名变量来绘制基本矩形。

如何操作...

应使用配方 1 中使用的说明。

写作、保存和执行此程序时,只需使用名称 rectangle.py

# rectangle.py
#>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Basic Rectangle')
cw = 200 # canvas width
ch =130 # canvas height
canvas_1 = Canvas(root, width=cw, height=200, background="white")
canvas_1.grid(row=0, column=1)
x_start = 10
y_start = 30
x_width =70
y_height = 90
kula ="darkblue"
canvas_1.create_rectangle( x_start, y_start,\
x_start + x_width, y_start + y_height, fill=kula)
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

如何操作...

结果在下一张屏幕截图给出,显示了一个基本矩形。

如何工作...

在绘制矩形、圆形、椭圆和弧时,你指定围绕要绘制的图形的边界框的起点(左下角)和终点(右上角)。在矩形和正方形的情况下,边界框与图形重合。但在圆形、椭圆和弧的情况下,边界框当然更大。

通过这个配方,我们尝试了一种新的定义矩形形状的方法。我们将起点指定为 [x_start, y_start],然后我们只声明我们想要的宽度为 [x_width, y_height]。这样,终点就是 [x_start + x_width, y_start + y_height]。这样,如果你想创建具有相同高度和宽度的多个矩形,你只需要声明新的起点。

还有更多...

在下一个示例中,我们使用一个常见的形状来绘制一系列相似但不同的矩形。

绘制重叠矩形

通过改变定义其位置、形状和颜色变量的数值,绘制三个重叠的矩形。

如何操作...

如前所述,应使用配方 1 中使用的说明。

写作、保存和执行此程序时,只需使用名称 3rectangles.py

# 3rectangles.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Overlapping rectangles')
cw = 240 # canvas width
ch = 180 # canvas height
canvas_1 = Canvas(root, width=cw, height=200, background="green")
canvas_1.grid(row=0, column=1)
# dark blue rectangle - painted first therefore at the bottom
x_start = 10
y_start = 30
x_width =70
y_height = 90
kula ="darkblue"
canvas_1.create_rectangle( x_start, y_start,\
x_start + x_width, y_start + y_height, fill=kula)
# dark red rectangle - second therefore in the middle
x_start = 30
y_start = 50
kula ="darkred"
canvas_1.create_rectangle( x_start, y_start,\
x_start + x_width, y_start + y_height, fill=kula)
# dark green rectangle - painted last therefore on top of previous # ones.
x_start = 50
y_start = 70
kula ="darkgreen"
canvas_1.create_rectangle( x_start, y_start,\
x_start + x_width, y_start + y_height, fill=kula)
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

如何操作...

结果在下一张屏幕截图给出,显示了按顺序绘制的重叠矩形。

如何工作...

矩形的高度和宽度保持不变,但它们的起始位置已经移动到不同的位置。此外,一个名为kula的通用变量被用作每个create-rectangle()方法中的通用属性。在绘制每个矩形之间,kula被分配一个新的值,以使每个连续的矩形具有不同的颜色。

这里只是对颜色做一个简短的评论。最终,Tkinter 代码中使用的颜色是数值,每个数值指定了混合多少红色、绿色和蓝色。然而,在 Tkinter 库中,有一些浪漫命名的颜色集合,如“玫瑰粉”、“草绿色”和“矢车菊蓝”。每个命名的颜色都被分配了一个特定的数值,以创建与名称建议的颜色。有时你会看到这些颜色被称为网络颜色。有时你给颜色起一个名字,但 Python 解释器会拒绝它,或者只使用灰色调。这个棘手的话题在第五章中得到了解决,

还有更多...

规定绘制形状属性的方式可能看起来很冗长。如果我们只是将参数的绝对数值放入绘制函数的方法中,程序将会更短、更整洁。在前面的例子中,我们可以将矩形表示为:

canvas_1.create_rectangle( 10, 30, 70 ,90, , fill='darkblue')
canvas_1.create_rectangle( 30, 50, 70 ,90, , fill='darkred')
canvas_1.create_rectangle( 50, 70, 70 ,90, , fill='darkgreen')

在方法之外指定属性值有很好的理由。

  • 它允许你创建可重用的代码,可以重复使用,而不管变量的具体值如何。

  • 当你使用x_start而不是数字时,这使得代码更加自解释。

  • 它允许你以受控的系统性方式改变属性值。后面有很多这样的例子。

绘制同心正方形

通过改变定义位置、形状和颜色变量的数值,绘制三个同心正方形。

如何做到这一点...

应该使用菜谱 1 中使用的指令。

当你编写、保存和执行这个程序时,只需使用名称3concentric_squares.py

# 3concentric_squares.py
#>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Concentric squares')
cw = 200 # canvas width
ch = 400 # canvas height
canvas_1 = Canvas(root, width=cw, height=200, background="green")
canvas_1.grid(row=0, column=1)
# dark blue
x_center= 100
y_center= 100
x_width= 100
y_height= 100
kula= "darkblue"
canvas_1.create_rectangle( x_center - x_width/2, \
y_center - y_height/2,\
x_center + x_width/2, y_center + y_height/2, fill=kula)
#dark red
x_width= 80
y_height= 80
kula ="darkred"
canvas_1.create_rectangle( x_center - x_width/2, \
y_center - y_height/2,\
x_center + x_width/2, y_center + y_height/2, fill=kula)
#dark green
x_width= 60
y_height= 60
kula ="darkgreen"
canvas_1.create_rectangle( x_center - x_width/2, \
y_center - y_height/2,\
x_center + x_width/2, y_center + y_height/2, fill=kula)
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

结果将在下一张屏幕截图给出。

如何工作...

在这个菜谱中,我们指定了矩形的几何中心所在的位置。在每个实例中,这是 [x_center, y_center] 位置。每当你想要绘制同心形状时,你需要这样做。通常,通过操纵底右角来尝试定位某个绘制图形的中心总是很尴尬。当然,这也意味着在计算边界框的左下角和右上角时需要进行一些算术运算,但这是为了你获得的艺术自由而付出的微小代价。你只需使用这种技术一次,它就会永远听从你的召唤。

从椭圆形到圆形

画圆的最佳方式是使用 Tkinter 的create_oval()方法,该方法来自画布小部件。

如何做到这一点...

应使用第一个菜谱中使用的说明。

编写、保存和执行此程序时,只需使用名称 circle_1.py

#circle_1.py
#>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('A circle')
cw = 150 # canvas width
ch = 140 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
# specify bottom-left and top-right as a set of four numbers named # 'xy'
xy = 20, 20, 120, 120
canvas_1.create_oval(xy)
root.mainloop()

如何工作...

结果将在下一张截图给出,显示一个基本的圆。

如何工作...

圆只是一个高度和宽度相等的椭圆。在这里的例子中,我们使用一个非常紧凑的语句创建了一个圆:canvas_1.create_oval(xy)

紧凑性来自于将维度属性指定为 Python 元组 xy = 20, 20, 420, 420 的技巧。实际上,在其他情况下,使用列表如 xy = [ 20, 20, 420, 420 ] 可能会更好,因为列表允许你更改单个成员变量的值,而元组是一个不可变的常量值序列。元组被称为不可变。

还有更多...

将圆作为椭圆的特殊情况绘制确实是绘制圆的最佳方式。Tkinter 的不熟练用户可能会被诱惑使用圆弧来完成这项工作。这是一个错误,因为如下一道菜谱所示,create_arc() 方法的行为不允许绘制无瑕疵的圆。

从圆弧创建圆

制作圆的另一种方法是使用 create_arc() 方法。这种方法可能看起来是制作圆的更自然方式,但它不允许你完全完成圆。如果你尝试这样做,圆就会消失。

如何操作...

应使用第一个示例中使用的说明。

编写、保存和执行此程序时,只需使用名称 arc_circle.py

# arc_circle.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Should be a circle')
cw = 210 # canvas width
ch = 130 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
xy = 20, 20, 320, 320 # bounding box from x0,y0 to x1, y1
# The Arc is drawn from start_angle, in degrees to finish_angle.
# but if you try to complete the circle at 360 degrees it evaporates.
canvas_1.create_arc(xy, start=0, extent=359.999999999, fill="cyan")
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

如何工作...

结果将在下一张截图给出,显示由于 create_arc() 导致的失败圆圈。

如何工作...

通常,create_arc() 方法不是制作完整圆的最佳方法,因为从 0 度到 360 度的尝试会导致圆从视图中消失。相反,使用 create_oval() 方法。然而,有时你需要 create_arc() 方法的属性来创建特定的颜色分布。参见后续章节中的颜色轮,这是一个很好的例子。

还有更多...

create_arc() 方法非常适合制作企业演示中喜欢的饼图。create_arc() 方法绘制圆的一段,弧的端点通过径向线与中心相连。但如果我们只想画一个圆,那些径向线是不需要的。

三个圆弧椭圆

绘制了三个椭圆弧。

如何操作...

应使用菜谱 1 中使用的说明。

编写、保存和执行此程序时,只需使用名称 3arc_ellipses.py

# 3arc_ellipses.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('3arc ellipses')
cw = 180 # canvas width
ch = 180 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch)
canvas_1.grid(row=0, column=1)
xy_1 = 20, 80, 80, 20
xy_2 = 20, 130, 80, 100
xy_3 = 100, 130, 140, 20
canvas_1.create_arc(xy_1, start=20, extent=270, fill="red")
canvas_1.create_arc(xy_2, start=-50, extent=290, fill="cyan")
canvas_1.create_arc(xy_3, start=150, extent=-290, fill="blue")
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

如何工作...

结果将在下一张截图给出,显示行为良好的 create_arc() 椭圆。

如何工作...

这里需要注意的要点是,就像矩形和椭圆形一样;绘制对象的总体形状由边界框的形状决定。起始和结束(即范围)角度以传统度数表示。请注意,如果将要使用三角函数,则圆形度量必须是弧度而不是度数。

还有更多...

create_arc() 方法通过要求以度数而不是弧度进行角度测量,使其对用户更加友好,因为大多数人更容易可视化度数而不是弧度。但是,你需要知道,在 math 模块使用的任何函数中,角度测量并不是这种情况。所有像正弦、余弦和正切这样的三角函数都使用弧度角度测量,这只是一个小的便利。math 模块提供了易于使用的转换函数。

多边形

绘制一个多边形。多边形是一个封闭的、多边形的图形。这些边由直线段组成。点的指定与多段线的指定相同。

如何操作...

应该使用配方 1 中使用的说明。

当你编写、保存和执行此程序时,只需使用名称 triangle_polygon.py

# triangle_polygon.py
#>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('triangle')
cw = 160 # canvas width
ch = 80 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
# point 1 point 2 point 3
canvas_1.create_polygon(140,30, 130,70, 10,50, fill="red")
root.mainloop()

它是如何工作的...

结果将在下一张截图给出,显示一个多边形三角形。

它是如何工作的...

create_polygon() 方法在作为方法参数指定的点之间绘制一系列直线段。最后一个点自动与第一个点相连以闭合图形。由于图形是闭合的,你可以用颜色填充内部。

星形多边形

使用命名变量来指定多边形属性,以使用单个起始位置定义星的所有点或顶点或尖端。我们称这个位置为锚点位置。

如何操作...

应该使用配方 1 中使用的说明。

当你编写、保存和执行此程序时,只需使用名称 star_polygon.py

# star_polygon.py
#>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title(Polygon')
cw = 140 # canvas width
ch = 80 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
# blue star, anchored to an anchor point.
x_anchor = 15
y_anchor = 50
canvas_1.create_polygon(x_anchor, y_anchor,\
x_anchor + 20, y_anchor - 40,\
x_anchor + 30, y_anchor + 10,\
x_anchor, y_anchor - 30,\
x_anchor + 40, y_anchor - 20,\
fill="blue")
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

结果将在下一张截图给出,一个多边形星形。

它是如何工作的...

星的第一位置是点 [x_anchor, y_anchor]。所有其他点都是相对于锚点位置的正值或负值。这个概念在三个重叠矩形的配方中已经介绍过。这种以一对命名变量定义的点为参考绘制复杂形状的想法非常有用,并且在本书的后半部分被广泛使用。

为了提高代码的可读性,定义每个点的 x 和 y 变量的成对排列是垂直的,利用了行续字符 \ (反斜杠)。

克隆和调整星形大小

展示了一种同时重新定位和调整一组星形大小的技术。

如何操作...

应该使用配方 1 中使用的说明。

当你编写、保存和执行此程序时,只需使用名称 clone_stars.py

# clone_stars.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Re-sized and re-positioned polygon stars')
cw = 200 # canvas width
ch = 100 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
# blue star, anchored to an anchor point.
x_anchor = 15
y_anchor = 150
size_scaling = 1
canvas_1.create_polygon(x_anchor, y_anchor,\
x_anchor + 20 * size_scaling, y_anchor - \ 40* size_scaling,\
x_anchor + 30 * size_scaling, y_anchor + \ 10* size_scaling,\
x_anchor, y_anchor - 30* size_scaling,\
x_anchor + 40 * size_scaling, y_anchor - \ 20* size_scaling,\
fill="green")
size_scaling = 2
x_anchor = 80
y_anchor = 120
canvas_1.create_polygon(x_anchor, y_anchor,\
x_anchor + 20 * size_scaling, y_anchor - \ 40* size_scaling,\
x_anchor + 30 * size_scaling, y_anchor + \ 10* size_scaling,\
x_anchor, y_anchor - 30* size_scaling,\
x_anchor + 40 * size_scaling, y_anchor - \ 20* size_scaling,\
starsresizingfill="darkgreen")
size_scaling = 3
x_anchor = 160
y_anchor = 110
canvas_1.create_polygon(x_anchor, y_anchor,\
x_anchor + 20 * size_scaling, y_anchor - \ 40* size_scaling,\
x_anchor + 30 * size_scaling, y_anchor + \ 10* size_scaling,\
x_anchor, y_anchor - 30* size_scaling,\
x_anchor + 40 * size_scaling, y_anchor - \ 20* size_scaling,\
fill="lightgreen")
size_scaling = 3
x_anchor = 160
y_anchor = 110
canvas_1.create_polygon(x_anchor, y_anchor,\
x_anchor + 20 * size_scaling, y_anchor - \ 40* size_scaling,\
x_anchor + 30 * size_scaling, y_anchor + \ 10* size_scaling,\
x_anchor, y_anchor - 30* size_scaling,\
x_anchor + 40 * size_scaling, y_anchor - \ 20* size_scaling,\
fill="forestgreen")
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

下一个屏幕截图显示了大小变化的星串。

如何工作...

除了多边形星形的可变和方便重新分配的锚点外,我们还引入了一个放大因子,可以改变任何特定星形的大小而不会扭曲它。

还有更多...

最后三个例子已经展示了用于绘制任何大小和位置的预定义形状的一些重要和基本思想。在这个阶段,将这些效果分开在不同的例子中是很重要的,这样单独的动作就更容易理解。稍后,当这些效果被组合使用时,理解正在发生的事情就变得困难,尤其是如果涉及到额外的变换,如旋转。如果我们对生成图像的代码进行动画处理,那么理解几何关系会容易得多。通过动画,我指的是以类似于电影中处理图像的方式,通过短时间间隔显示连续图像。这种时间调节的动画,出人意料地,提供了检查图像生成代码行为的方法,这对人类大脑来说更加直观和清晰。这个想法在后面的章节中得到了发展。

第三章。处理文本

在本章中,我们将介绍:

  • 简单文本

  • 文本字体类型、大小和颜色

  • 文本北、南、东、西的位置

  • 文本右对齐和左对齐的位置

  • 平台上的可用字体

简介

文本可能很棘手。我们需要能够操作字体家族、大小、颜色和位置。位置反过来又要求我们指定文本必须开始的位置以及它应该被限制在哪些区域。

在本章中,我们专注于处理画布上的文本。

简单文本

这是如何在画布上放置文本的方法。

如何操作...

  1. 在文本编辑器中,输入以下代码。

  2. 将其保存为名为 text_1.py 的文件,位于名为 constr 的目录中。

  3. 如前所述,如果您使用的是 MS Windows,请打开一个 X 终端或 DOS 窗口。

  4. 切换到 constr 目录 - text_1.py 所在的位置。

  5. 输入 text_1.py,你的程序应该会执行。

    # text_1.py
    #>>>>>>>>>>
    from Tkinter import *
    root = Tk()
    root.title('Basic text')
    cw = 400 # canvas width
    ch = 50 # canvas height
    canvas_1 = Canvas(root, width=cw, height=ch, background="white")
    canvas_1.grid(row=0, column=1)
    xy = 150, 20
    canvas_1.create_text(xy, text=" The default text size looks \ about 10.5 point")
    root.mainloop()
    
    

它是如何工作的...

结果如下截图所示:

如何工作...

由于字体高度和字符间距以及文本窗口尺寸的相互干扰,将文本精确放置在屏幕上的位置可能很棘手。你可能需要花一些时间进行实验,才能得到你想要的文本。

还有更多...

将文本放置到画布上提供了作为常用 print 函数调试工具的有用替代品。您可以将许多变量的值发送到画布上进行显示,并观察它们的值变化。

正如将在动画章节中演示的那样,观察复杂数值关系相互作用的最简单方法是以某种方式对它们进行动画处理。

文本字体类型、大小和颜色

与为线条和形状指定属性的方式非常相似,字体类型、大小和颜色由 create_text() 方法的属性控制。

准备工作

这里不需要任何东西。

如何操作...

应使用第 1 个菜谱中的说明。

在编写、保存和执行此程序时,只需使用名称 4color_text.py

# 4color_text.py
#>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Four color text')
cw = 500 # canvas width
ch = 140 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
xy = 200, 20
canvas_1.create_text(200, 20, text=" text normal SansSerif 20", \ fill='red',\
width=500, font='SansSerif 20 ')
canvas_1.create_text(200, 50, text=" text normal Arial 20", \ fill='blue',\
width=500, font='Arial 20 ')
canvas_1.create_text(200, 80, text=" text bold Courier 20", \ fill='green',\
width=500, font='Courier 20 bold')
canvas_1.create_text(200, 110, text=" bold italic BookAntiqua 20",\
fill='violet', width=500, font='Bookantiqua 20 bold')
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

结果如下截图所示:

如何工作...

在指定字体时存在的一个困难是决定哪些字体最适合你的需求。一旦你选择了字体,你可能会发现你的特定操作系统不支持该字体。幸运的是,Tkinter 的设计者通过在指定的字体不可用时选择合适的默认字体,使它变得相对防弹。

还有更多...

文本北、南、东、西的位置。

我们使用 Tkinter 可用的位置指定符在画布上放置文本。锚点位置、文本 x、y 位置、字体大小、列宽和文本对齐方式都相互作用,以控制页面上的文本外观。以下截图显示了定位文本时使用的罗盘命名法:

还有更多...

准备工作

在我们理解 Tkinter 使用的导航系统之前,将文本放置到画布上是棘手的。以下是它是如何工作的。所有文本都进入一个不可见的框中。这个框就像一个空的画框,放在板上的钉子上。Tkinter 画布是板,空的框架是我们输入的文本将要适应的框。钉子是 x 和 y 的位置。空的框架可以被移动,使得钉子在左上角(西北)或右下角(东南)或中心或其他角落或侧面。以下截图显示了包含文本的画布上的想象框架:

准备中

如何操作...

执行代码并观察各种文本位置指定符

控制文本的外观。

# anchor_align_text_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Anchoring Text North, South, East, West')
cw = 650 # canvas width
ch = 180 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
orig_x = 220
orig_y = 20
offset_y = 30
# 1\. DEFAULT CENTER JUSTIFICATION
# width is maximum line length.
canvas_1.create_text(orig_x, orig_y , \
text="1===|===10", fill='red', width=700, font='SansSerif 20 ')
canvas_1.create_text(orig_x, orig_y + offset_y, \
text="1\. CENTER anchor", fill='red', width=700, font='SansSerif 20 \ ')
canvas_1.create_text(orig_x, orig_y + 2 *offset_y, \
text="text Arial 20", fill='blue', width=700, font='SansSerif 20 ')
#===================================================================
orig_x = 380
# 2\. LEFT JUSTIFICATION
canvas_1.create_text(orig_x, orig_y, text="1===|===10",\
fill='black', anchor = NW, width=700, font='SansSerif 16 ')
canvas_1.create_text(orig_x, orig_y + offset_y, text="2\. NORTH-WEST \ anchor",\
fill='black', anchor = NW, width=700, font='SansSerif 16 ')
canvas_1.create_text(orig_x, orig_y + 2 *offset_y, fill='black',\
text="text SansSerif 16", anchor = NW, width=700, font='SansSerif \ 16 ')
#==================================================================
# 3\. DEFAULT TOP-LEFT (NW) JUSTIFICATION
orig_x = 170
orig_y = 102
offset_y = 20
canvas_1.create_text(orig_x, orig_y , anchor = NW ,text="1===|===10",\
fill='green', width=500, font='SansSerif 10 ')
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NW ,\
text="3\. NORTH-WEST anchor", fill='green', width=500, \ font='SansSerif 10 ')
#canvas_1.create_text(orig_x, orig_y + 2 * offset_y, anchor = NW,\
#text="abc", fill='green', width=700, font='SansSerif 10 ')
canvas_1.create_text(orig_x, orig_y + 2 * offset_y, anchor = NW, \
text="abcde", fill='green', width=500, font='Bookantiqua 10 bold')
#===================================================================
# 4\. DEFAULT Top-right (SE) JUSTIFICATION
canvas_1.create_text(orig_x, orig_y , anchor = NE ,\
text="1===|===10", fill='violet', width=500, font='SansSerif 10 ')
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NE ,\
text="4\. NORTH-EAST anchor", fill='violet', width=500, \ font='SansSerif 10 ')
#canvas_1.create_text(orig_x, orig_y + 2 * offset_y, anchor = NE, \
#text="abc",fill='violet', width=700, font='SansSerif 10 ')
canvas_1.create_text(orig_x, orig_y + 2 * offset_y, anchor = NE,\
text="abcdefghijklmno", fill='violet', width=500, font='Bookantiqua 10 bold')
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

结果如下所示:

它是如何工作的...

文本左对齐和右对齐的对齐方式

我们现在特别关注文本对齐与列锚点位置之间的交互。

准备中

以下代码包含一个段落,太长以至于无法适应单行。这就是我们看到术语对齐如何让我们决定是否希望文本对齐到列的右侧或左侧,或者甚至居中。列宽度以像素为单位指定,然后文本被调整以适应。

如何操作...

运行以下代码并观察列的高度仅限于画布的高度,但宽度、锚点位置、对齐方式和字体大小决定了文本如何在画布上布局。

# justify_align_text_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('North-south-east-west Placement with LEFT and RIGHT \ justification of Text')
cw = 850 # canvas width
ch = 720 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
orig_x = 220
orig_y = 20
offset_y = 20
jolly_text = "And now ladies and gentlemen she will attempt - for the very first time in the history of 17 Shoeslace street - a one handed right arm full toss teacup fling. Yes you lucky listners you are about to witness what, in recorded history, has never been attempted before without the aid of hair curlers and fluffy slippers."
# width is maximum line length.
#=================================================================
# 1\. Top-left (NE) ANCHOR POINT, no column justification specified.
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NE \ ,text="1\. \
NORTH-EAST anchor, no column justification", fill='blue', width=200, \ font='Arial 10')
canvas_1.create_text(orig_x, orig_y + 3 * offset_y, anchor = NE, \ text=jolly_text,\
fill='blue', width=150, font='Arial 10')
#==================================================================
# 2\. Top-right (NW) ANCHOR POINT, no column justification specified.
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NW \ ,text="2\. \
NORTH-WEST ancho, no column justification", fill='red', width=200, \ font='Arial 10')
canvas_1.create_text(orig_x, orig_y + 3 * offset_y, anchor = NW, \ text= jolly_text,\
fill='red', width=200, font='Arial 10')
#==================================================================
orig_x = 600
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NE \ ,text="3\. \
SOUTH-EAST anchor, no column justification",fill='black', width=200, \ font='Arial 10')
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NW \ ,text="4\. \
SOUTH-WEST anchor, no column justification", fill='#666666', \ width=200, font='Arial 10')
#============================================================
orig_x = 600
orig_y = 280
# 3\. BOTTOM-LEFT (SW) JUSTIFICATION, no column justification # specified.
canvas_1.create_text(orig_x, orig_y + 2 * offset_y, anchor = SW, \ text=jolly_text,\
fill='#666666', width=200, font='Arial \ 10')
#==================================================================
# 4\. TOP-RIGHT (SE) ANCHOR POINT, no column justification specified.
canvas_1.create_text(orig_x, orig_y + 2 * offset_y, anchor = SE, \ text=jolly_text,\
fill='black', width=150, font='Arial 10')
#===================================================================
orig_y = 350
orig_x = 200
# 5\. Top-right (NE) ANCHOR POINT, RIGHT column justification # specified.
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NE , \ justify=RIGHT,\
text="5\. NORTH-EAST anchor, justify=RIGHT", fill='blue', width=200, \ font='Arial 10 ')
canvas_1.create_text(orig_x, orig_y + 3 * offset_y, anchor = NE, \ justify=RIGHT, \
text=jolly_text, fill='blue', width=150, font='Arial 10')
#===================================================================
# 6\. TOP-LEFT (NW) ANCHOR POINT, RIGHT column justification specified.
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NW \ ,text="6.\
NORTH-WEST anchor, justify=RIGHT", fill='red', width=200, \ font='Arial 10 ')
canvas_1.create_text(orig_x, orig_y + 3 * offset_y, anchor = NW, \ justify=RIGHT,\
text=jolly_text, fill='red', width=200, font='Arial 10')
#===================================================================
orig_x = 600
# Header lines for 7\. and 8.
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NE \ ,text="7\. \
SOUTH-EAST anchor, justify= CENTER", fill='black', width=160, \ font='Arial 10 ')
canvas_1.create_text(orig_x, orig_y + 1 * offset_y, anchor = NW , \ text="8.\
SOUTH-WEST anchor, justify= CENTER", fill='#666666', width=200, \ font='Arial 10 ')
#==================================================================
orig_y = 600
# 7\. TOP-RIGHT (SE) ANCHOR POINT, CENTER column justification # specified.
canvas_1.create_text(orig_x, orig_y + 4 * offset_y, anchor = SE, \ justify= CENTER,\
text=jolly_text, fill='black', width=150, font='Arial 10')
#===================================================================
# 8\. BOTTOM-LEFT (SW) ANCHOR POINT, CENTER column justification # specified.
canvas_1.create_text(orig_x, orig_y + 4 * offset_y, anchor = SW, \ justify= CENTER,\
text=jolly_text, fill='#666666', width=200, font='Arial 10')
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

列宽度、锚点位置和对齐方式之间的交互是复杂的,最清晰解释结果的方式是通过执行后画布显示的注释图片。以下截图显示了右上角(NE)锚点,未指定对齐方式(默认左对齐)。

它是如何工作的...

以下截图显示了右上角(SE)锚点,未指定对齐方式:

它是如何工作的...

以下截图显示了右下角(SE)锚点,指定了居中对齐:

它是如何工作的...

你电脑上所有可用的字体

发现你特定计算机上可用的字体,然后按默认大小打印每种字体的样本,全部按字母顺序排列。

解决选择合适字体问题的一个方案是执行一个可靠的程序来列出你使用平台上可用的字体,并将每种类型的示例打印到屏幕上。这就是下一个示例所做的事情。

如何操作...

应使用配方 1 中使用的说明。

当你编写、保存和执行此程序时,只需使用名称 fonts_available.py

# fonts_available.py
# ==================================
from Tkinter import *
import tkFont
root = Tk()
root.title('Fonts available on this Computer')
canvas = Canvas(root, width =930, height=830, background='white')
fonts_available = list( tkFont.families() )
fonts_available.sort()
text_sample = ' : abcdefghij_HIJK_12340'
# list the font names on the system console first.
for this_family in fonts_available :
print this_family
print '============================='
# Show first half on left half of screen .
for i in range(0,len(fonts_available)/2):
print fonts_available[i]
texty = fonts_available[i]
canvas.create_text(50,30 + i*20, text= texty + text_sample,\
fill='black', font=(texty, \ 12), anchor= "w")
# Show second half on right half of screen .
for i in range(len(fonts_available)/2,len(fonts_available)):
print fonts_available[i]
texty = fonts_available[i]
canvas.create_text(500,30 + (i-len(fonts_available)/2 )*20, \
text= texty+ text_sample, fill='black', \
font=(texty, 12),anchor= "w")
canvas.pack()
root.mainloop()

它是如何工作的...

结果如下所示,显示了特定操作系统上 Python 可用的所有字体。

工作原理...

当您想要选择令人愉悦且合适的字体时,这个程序非常有用。可用的字体在不同的平台上可能会有很大的差异。因此,在这里我们利用属于 tkFont 模块的 families() 方法,将字体家族的名称放入名为 fonts_available 的列表中。该列表使用 fonts_available.sort() 方法按字母顺序排序。

最后,使用了两个方便的功能。

首先,通过使用 create_text 方法的 anchor= "w" 属性将文本锚定在西部或左侧,使得字体列表看起来整洁。

其次,是 len() 函数在 len(fonts_available) 中的非常有用。

此函数会返回给您(在编程术语中称为“返回”)列表中的项目数量。当您不知道这个数字将会是多少时,它非常方便,用于定义 for 循环迭代应该进行多少次。在这个例子中,我们需要为列表中尚未发现的每个字体名称编写字体名称和文本样本的代码。

第四章。动画原理

在本章中,我们将涵盖:

  • 球的静态移动

  • 球的定时移动

  • 动画定时绘制和擦除循环

  • 两个无阻碍移动的球

  • 一个弹跳的球

  • 在重力场中的弹跳

  • 带有追踪轨迹的碰撞球

  • 弹性球与球碰撞

  • 动态调试

  • 轨迹追踪

  • 旋转线条和重要的三角学

  • 旋转线条,旋转线条

  • 数字花

简介

动画是关于在屏幕上使图形对象平滑移动。创建平滑动态动作感觉的方法很简单:

  1. 首先向观众展示一幅图画。

  2. 允许图像在视图中停留大约二十分之一秒。

  3. 以最小的延迟,展示另一个图像,其中对象已经通过一个小量移动,并重复此过程。

除了在屏幕上制作动画图形供娱乐的明显应用外,动画化计算机代码的结果能让你深入了解代码在详细层面的工作方式。动画为程序员的调试工具箱提供了一个额外的维度。它为你提供了一个全面的、整体的观点,关于正在进行的软件执行,这是其他任何东西都无法提供的。

球的静态移动

我们制作了一个小彩色圆盘的图像,并在不同的位置绘制它。

如何做到这一点...

以与第二章、“绘制基本形状”中所有示例完全相同的方式执行程序,你会看到一排整齐的彩色圆盘从左上角到右下角依次排列。这个想法是展示我们将反复使用的方法,即系统的位置移动方法。

# moveball_1.py
#>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("shifted sequence")
cw = 250 # canvas width
ch = 130 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
# The parameters determining the dimensions of the ball and its # position.
# ==========================================
posn_x = 1 # x position of box containing the ball (bottom)
posn_y = 1 # y position of box containing the ball (left edge)
shift_x = 3 # amount of x-movement each cycle of the 'for' loop
shift_y = 2 # amount of y-movement each cycle of the 'for' loop
ball_width = 12 # size of ball - width (x-dimension)
ball_height = 12 # size of ball - height (y-dimension)
color = "violet" # color of the ball
for i in range(1,50): # end the program after 50 position shifts
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

一个简单的球在画布上按顺序绘制,一层叠在另一层之上。对于每一步,球的位置根据shift_x的大小移动三个像素。同样,shift_y的值减少两个像素,以实现向下移动。shift_xshift_y只指定移动量,但它们并不直接导致移动。使移动发生的是两个命令posn_x += shift_xposn_y += shift_yposn是位置(position)的缩写。这里需要解释一下这种记法,因为我们会在整本书中经常使用它。它既整洁又方便。

posn_x += shift_x意味着“将变量posn_x增加一个shift_x的量。”它与posn_x = posn_x + shift_x相同。

另一个需要注意的细微之处是使用行续字符,即反斜杠“\”。当我们想要将相同的 Python 命令延续到下一行以方便阅读时,我们会使用它。严格来说,对于括号内的文本“(…)”,这并不是必需的。在这个特定情况下,你可以简单地插入一个回车字符。然而,反斜杠使得阅读你代码的人清楚地知道你的意图。

还有更多...

本食谱中的球图像系列是在几微秒内绘制的。为了创建看起来不错的动画,我们需要能够通过适当的方式减慢代码的执行速度。我们需要将相当于电影帧的内容绘制到屏幕上,并保持一段时间,然后移动到下一个略微偏移的图像。这个过程将在下一个食谱中完成。

通过时间控制移动球

在这里,我们介绍了时间控制函数 canvas.after(milliseconds)canvas.update() 函数,后者刷新画布上的图像。这些是 Python 动画的基础。

通过标准 Python 库中的时间模块,我们可以控制代码何时执行。

如何做...

按照之前的方式执行程序。你将看到一条对角线排列的圆盘,在更新之间有五分之一秒(200 毫秒)的短暂延迟。结果在下面的屏幕截图中显示,显示了球在规律间隔内的移动。

如何做...

# timed_moveball_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Time delayed ball drawing")
cw = 300 # canvas width
ch = 130 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 200 # time between fresh positions of the ball
# (milliseconds).
# The parameters determining the dimensions of the ball and it's # position.
posn_x = 1 # x position of box containing the ball (bottom).
posn_y = 1 # y position of box containing the ball (left edge).
shift_x = 3 # amount of x-movement each cycle of the 'for' loop.
shift_y = 3 # amount of y-movement each cycle of the 'for' loop.
ball_width = 12 # size of ball - width (x-dimension).
ball_height = 12 # size of ball - height (y-dimension).
color = "purple" # color of the ball
for i in range(1,50): # end the program after 50 position shifts.
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200 # milliseconds.
root.mainloop()

它是如何工作的...

这个食谱与上一个食谱相同,只是增加了 canvas.after(...)canvas.update() 方法。这两个函数来自 Python 库。第一个允许你通过指定延迟来控制代码执行时间。第二个强制画布完全重新绘制所有应该存在的对象。虽然还有更复杂的方法来刷新屏幕的特定部分,但它们会带来困难,所以这里不会处理。

canvas.after(your-chosen-milliseconds) 方法只是简单地使代码执行产生定时暂停。在所有前面的代码中,暂停执行的速度尽可能快,然后当遇到由 canvas.after() 方法触发的暂停时,执行将暂停指定的毫秒数。暂停结束后,执行将继续,就像什么都没发生过一样。

canvas.update() 方法强制画布上的所有内容立即重新绘制,而不是等待某个未指定的事件导致画布刷新。

还有更多...

在有效动画的下一步中,在画布上绘制新的、偏移的克隆图像之前,需要短暂擦除动画对象的先前图像。这将在下一个示例中发生。

Tkinter 的健壮性

值得注意的是,Tkinter 非常健壮。当你给出超出画布范围的坐标时,Python 不会崩溃或冻结。它只是继续绘制“超出页面”的对象。Tkinter 画布可以看作是进入几乎无限视觉空间的一个小窗口。我们只有在对象移动到 Tkinter 画布的视野中时才能看到对象。

使用绘制-移动-暂停-擦除循环完成完整动画

这个菜谱提供了整个动画过程。在这个例子中,所有必要的人类大脑将视网膜上的图像解释为移动对象的行为都存在。动画的全部工艺以及基于此的百万美元电影在这里以最简单和最纯粹的形式展示。

如何做...

按照我们之前的方式执行此程序。注意这次我们将计时暂停缩短到 50 毫秒,即每秒 20 次。这接近电影中使用的标准 24 帧每秒。然而,如果没有图形卡,这次暂停时间会变得不那么准确,因为指定的暂停时间更短。此外,球在位置变化之间的移动距离已经减少到一像素。

# move_erase_cycle_1.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("move-and-erase")
cw = 230 # canvas width
ch = 130 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 50 # time between new positions of the ball
# (milliseconds).
# The parameters determining the dimensions of the ball and its
# position.
posn_x = 1 # x position of box containing the ball (bottom).
posn_y = 1 # y position of box containing the ball (left edge).
shift_x = 1 # amount of x-movement each cycle of the 'for' loop.
draw-move-pause-erase cyclesusing, in animationshift_y = 1 # amount of y-movement each cycle of the 'for' loop.
ball_width = 12 # size of ball - width (x-dimension).
ball_height = 12 # size of ball height (y-dimension).
color = "hot pink" # color of the ball
for i in range(1,500): # end the program after 500 position shifts.
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200 # milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas.
root.mainloop()
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

在这个自包含动画中的新元素是canvas.delete(ALL)方法,它可以清除画布上绘制的一切。可以通过使用标识标签来擦除屏幕上的特定对象。现在这不需要了。使用标签进行选择性对象删除将在本章的最后三个菜谱中使用。

更多...

pause()方法的计时精度如何?

使用现代计算机,五毫秒的暂停是现实的,但随着暂停时间的缩短,动画变得不流畅。

多个移动对象

我们希望能够开发出包含多个独立图形对象共存并按照某些规则相互作用的程序。大多数计算机游戏就是这样工作的。飞行员训练模拟器和严肃的工程设计模型也是基于相同的原则设计的。我们通过创建一个最终有两个球在重力损失和相互碰撞的影响下反弹到墙上的应用程序来开始这个过程。

如何做...

以下代码与上一个菜谱中的代码非常相似,只是创建了两个相似的对象。它们彼此独立,并且以任何方式都不相互作用。

# two_balls_moving_1.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Two balls")
cw = 200 # canvas width
ch = 130 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 100 # time between new positions of the ball
# (milliseconds).
# The parameters defining ball no 1.
posn_x_1 = 1 # x position of box containing the ball (bottom).
posn_y_1 = 1 # y position of box containing the ball (left edge).
shift_x_1 = 1 # amount of x-movement each cycle of the 'for' loop.
shift_y_1 = 1 # amount of y-movement each cycle of the 'for' loop.
ball_width_1 = 12 # size of ball - width (x-dimension).
ball_height_1 = 12 # size of ball - height (y-dimension).
color_1 = "blue" # color of ball #1
# The parameters defining ball no 2.
posn_x_2 = 180 # x position of box containing the ball (bottom).
multiple objectsmovingposn_y_2 = 180 # x position of box containing the ball (left # edge).
shift_x_2 = -2 # amount of x-movement each cycle of the 'for' # loop.
shift_y_2 = -2 # amount of y-movement each cycle of the 'for' # loop.
ball_width_2 = 8 # size of ball - width (x-dimension).
ball_height_2 = 8 # size of ball - height (y-dimension).
color_2 = "green" # color of ball #2.
for i in range(1,100): # end the program after 50 position shifts.
posn_x_1 += shift_x_1
posn_y_1 += shift_y_1
posn_x_2 += shift_x_2
posn_y_2 += shift_y_2
chart_1.create_oval(posn_x_1, posn_y_1, posn_x_1 + ball_width_1,\
posn_y_1 + ball_height_1, fill=color_1)
chart_1.create_oval(posn_x_2, posn_y_2, posn_x_2 + ball_width_2,\
posn_y_2 + ball_height_2, fill=color_2)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 100 # milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas
root.mainloop()
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

需要注意的主要点是,这些程序,以及本书中的许多其他程序,分为五个部分:

  1. 创建对象存在的环境。

  2. 定义单个对象及其属性。

  3. 定义对象之间的交互规则。

  4. 创建对象。

  5. 使用循环通过改变位置等属性来模拟时间的流逝,这些属性的变化速率模仿实时运动。

  6. 控制对象存在的环境。

在我们的大多数例子中,环境是 Tkinter 画布。在这个例子中,将在画布环境中存在的对象是两个彩色球。交互规则是它们将不会对彼此产生任何影响,并且它们也不会受到画布边缘的影响。另一个交互规则是每次执行for循环时它们的位置将如何变化。

最后,环境由受时间调节的canvas.update()canvas.delete(ALL)方法控制。

还有更多...

这个配方中展示的基本思想是,我们可以创建多个相似但不同的对象,并且它们可以独立地反应。这引发了面向对象编程的想法。

Python 提供了多种使用面向对象编程思想的方法。在这本书中,我们使用三种方式来创建对象:列表、字典和类。

一个弹跳的球

现在,以及接下来的三个例子中,我们添加了越来越复杂的交互规则。总体目标是向我们的虚拟世界引入行为和交互,使其更像现实世界。我们使用数字、计算和图形绘制来表示我们所知的现实世界的各个方面。

第一个新行为是,我们的彩色圆盘将在 Tkinter 画布的容器墙上弹性弹跳。

如何实现...

为了让我们感觉仍然处于熟悉的领域,随着我们创建的世界变得越来越复杂,代码有意识地保持与前面四个例子尽可能相似。如果我们不这样做,我们就会迷失方向,感到困惑。成功构建复杂计算机程序的全部秘密在于逐步和系统地逐步构建。这并不是沿着一条已经绘制好的道路的计划之旅,而是一次艰苦的未知的丛林探险。

# bounce_ball.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import time
root = Tk()
root.title("The bouncer")
cw = 200 # canvas width
ch = 120 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 50 # time between new positions of the ball
# (milliseconds).
# The parameters determining the dimensions of the ball and its position.
posn_x = 1 # x position of box containing the ball (bottom).
posn_y = 1 # x position of box containing the ball (left edge).
shift_x = 1 # amount of x-movement each cycle of the 'for' loop.
shift_y = 1 # amount of y-movement each cycle of the 'for' loop.
ball_width = 12 # size of ball - width (x-dimension).
ball_height = 12 # size of ball - height (y-dimension).
color = "firebrick" # color of the ball
# Here is a function that detects collisions with the walls of the # container
# and then reverses the direction of movement if a collision is # detected.
def detect_wall_collision():
global posn_x, posn_y, shift_x, shift_y, cw, cy
if posn_x > cw : # Collision with right-hand container wall.
shift_x = -shift_x # reverse direction.
if posn_x < 0 : # Collision with left-hand wall.
shift_x = -shift_x
if posn_y > ch : # Collision with floor.
ballbouncingshift_y = -shift_y
if posn_y < 0 : # Collision with ceiling.
shift_y = -shift_y
for i in range(1,1000): # end the program after1000 position shifts.
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
detect_wall_collision() # Has the ball collided with # any container wall?
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause # for 200 milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas.
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

这里新增加的功能是函数detect_Wall_Collision()。每次调用它时,都会检查球的位置是否已经移动到画布边界之外。如果是,则球的运动方向会被反转。这种方法比较粗糙,因为它没有考虑到球的大小。因此,球会突然消失。

在重力场中的弹跳

在这个配方中,我们增加了重力场的影响,作为之前在画布墙上弹跳规则的补充。

如何实现...

与之前的所有配方不同的地方在于球的新属性velocity_y。在for i in range(0,300)循环的每一轮中,速度都会像在现实世界的重力场中一样被修改。

# gravityball.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Gravity bounce")
cw = 220 # canvas width
ch = 200 # canvas height
GRAVITY = 4
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 30
# The parameters determining the dimensions of the ball and its # position.
posn_x = 15
posn_y = 180
shift_x = 1
velocity_y = 50
ball_width = 12
ball_height = 12
color = "blue"
# The function that detects collisions with the walls and reverses # direction
def detect_wall_collision():
global posn_x, posn_y, shift_x, velocity_y, cw, cy
if posn_x > cw -ball_width: # Collision with right-hand # container wall.
shift_x = -shift_x # reverse direction.
if posn_x < ball_width: # Collision with left-hand wall.
shift_x = -shift_x
ballbouncing, in gravity fieldif posn_y < ball_height : # Collision with ceiling.
velocity_y = -velocity_y
if posn_y > ch - ball_height : # Floor collision.
velocity_y = -velocity_y
for i in range(1,300):
posn_x += shift_x
velocity_y = velocity_y + GRAVITY # a crude equation # incorporating gravity.
posn_y += velocity_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width, \
posn_y + ball_height, \ fill=color)
detect_wall_collision() # Has the ball collided with any # container wall?
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200 # milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas.
root.mainloop()

它是如何工作的...

每次计算新位置时,我们球的垂直velocity_y属性都会增加一个常数量 GRAVITY。结果是,当球向下落时速度会加快,当球向上移动时速度会减慢。因为 Tkinter 画布的 y 方向是向下增加的(与我们的现实世界相反),这会导致球向上移动时速度减慢,向下移动时速度加快。

还有更多...

这种弹球模拟存在一个缺陷。球在弹跳大约三次后从画布上消失,因为计算球的新位置和检测与墙壁碰撞所使用的整数算术过于粗糙。结果是球发现自己超出了我们设定的条件,当它碰到地板时无法反转方向。添加到其速度中的重力将其踢出 posn_y > ch ball_height 的区间,球永远不会被放回画布内。

画布上的位置仅定义为整数,但在计算球的位置时,我们需要处理比这更高的精度。结果证明这里没有问题。在他们的智慧中,Python 设计者允许我们使用所有非常精确的浮点数变量来工作,并且仍然可以将它们传递给 canvas.create_oval(...) 方法,该方法在画布上绘制球。对于最终的绘图,显然它们被转换成了整数。感谢明智的 Python 朋友们。

参见

下一个配方 floating_point_collisions_1.py 使用浮点数位置计算来修复这个示例中的缺陷。

使用浮点数进行精确碰撞

在这里,通过使用浮点数进行所有球位置计算,消除了由整数算术粗糙性引起的模拟缺陷。

如何操作...

所有位置、速度和重力变量都通过显式的小数点写成浮点数。结果在下面的屏幕截图中显示,显示了带有轨迹追踪的弹跳球。

如何操作...

from Tkinter import *
root = Tk()
root.title("Collisions with Floating point")
cw = 350 # canvas width
ch = 200 # canvas height
GRAVITY = 1.5
chart_1 = Canvas(root, width=cw, height=ch, background="black")
chart_1.grid(row=0, column=0)
cycle_period = 80 # Time between new positions of the ball # (milliseconds).
simulation flawseliminatingtime_scaling = 0.2 # This governs the size of the differential steps
# when calculating changes in position.
# The parameters determining the dimensions of the ball and it's # position.
ball_1 = {'posn_x':25.0, # x position of box containing the # ball (bottom).
'posn_y':180.0, # x position of box containing the # ball (left edge).
'velocity_x':30.0, # amount of x-movement each cycle of # the 'for' loop.
'velocity_y':100.0, # amount of y-movement each cycle of # the 'for' loop.
'ball_width':20.0, # size of ball - width (x-dimension).
'ball_height':20.0, # size of ball - height (y-dimension).
'color':"dark orange", # color of the ball
'coef_restitution':0.90} # proportion of elastic energy # recovered each bounce
ball_2 = {'posn_x':cw - 25.0,
'posn_y':300.0,
'velocity_x':-50.0,
'velocity_y':150.0,
'ball_width':30.0,
simulation flawseliminating'ball_height':30.0,
'color':"yellow3",
'coef_restitution':0.90}
def detectWallCollision(ball):
# Collision detection with the walls of the container
if ball['posn_x'] > cw - ball['ball_width']: # Collision # with right-hand wall.
ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \ restitution'] # reverse direction.
ball['posn_x'] = cw - ball['ball_width']
if ball['posn_x'] < 1: # Collision with left-hand wall.
ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \restitution']
ball['posn_x'] = 2 # anti-stick to the wall
if ball['posn_y'] < ball['ball_height'] : # Collision # with ceiling.
ball['velocity_y'] = -ball['velocity_y'] * ball['coef_ \ restitution']
ball['posn_y'] = ball['ball_height']
if ball['posn_y'] > ch - ball['ball_height']: # Floor # collision.
ball['velocity_y'] = - ball['velocity_y'] * ball['coef_ \restitution']
ball['posn_y'] = ch - ball['ball_height']
def diffEquation(ball):
# An approximate set of differential equations of motion # for the balls
ball['posn_x'] += ball['velocity_x'] * time_scaling
ball['velocity_y'] = ball['velocity_y'] + GRAVITY # a crude # equation incorporating gravity.
ball['posn_y'] += ball['velocity_y'] * time_scaling
chart_1.create_oval( ball['posn_x'], ball['posn_y'], ball['posn_x'] + ball['ball_width'],\
ball ['posn_y'] + ball['ball_height'], \ fill= ball['color'])
detectWallCollision(ball) # Has the ball collided with # any container wall?
for i in range(1,2000): # end the program after 1000 position shifts.
diffEquation(ball_1)
diffEquation(ball_2)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200 # milliseconds.
chart_1.delete(ALL) # This erases everything on the
root.mainloop()

工作原理...

精度算术的使用使我们能够注意到以前由于仅使用整数计算而隐藏的模拟行为。这是图形模拟作为调试工具的独特价值。如果你能用视觉方式而不是数字列表来表示你的想法,你将很容易发现代码中的细微差异。人类大脑最适合在图形图像中工作。这是作为猎人的直接后果。

一个图形调试工具...

软件调试器工具箱中还有一个非常实用的技巧,那就是可视化追踪。追踪是一种显示动态行为历史的视觉轨迹。所有这些都在下一个示例中揭示。

轨迹追踪和球与球碰撞

现在我们介绍我们模拟中更复杂行为之一的中空碰撞。

当你在调试程序时,最难的事情是试图将最近观察到的行为保存在你的短期记忆中,并与当前行为进行有意义的比较。这种记忆是一个不完美的记录器。克服这种困难的方法是创建一种图形形式的记忆,某种类型的图片,可以准确地显示过去发生的事情。就像军事大炮瞄准手使用发光的曳光弹来调整他们的瞄准一样,图形程序员可以使用轨迹痕迹来检查执行的历史。

如何做到...

在我们的新代码中,有一个名为 detect_ball_collision (ball_1, ball_2) 的新函数,其任务是预测两个球无论在哪里即将发生的碰撞。碰撞可能来自任何方向,因此我们需要能够测试所有可能的碰撞场景,并检查每一个的行为,看看它是否按计划工作。除非我们创建测试结果的工具,否则这可能过于困难。在这个菜谱中,测试结果的工具是图形轨迹痕迹。这是一条跟随球路径的线,显示了从模拟开始以来球的确切位置。结果在下面的屏幕截图中显示,显示了球与球碰撞的反弹。

如何做到...

# kinetic_gravity_balls_1.py
animationmid-air collision# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
root = Tk()
root.title("Balls bounce off each other")
cw = 300 # canvas width
ch = 200 # canvas height
GRAVITY = 1.5
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 80 # Time between new positions of the ball # (milliseconds).
time_scaling = 0.2 # The size of the differential steps
# The parameters determining the dimensions of the ball and its # position.
ball_1 = {'posn_x':25.0,
'posn_y':25.0,
'velocity_x':65.0,
'velocity_y':50.0,
'ball_width':20.0,
'ball_height':20.0,
'color':"SlateBlue1",
'coef_restitution':0.90}
ball_2 = {'posn_x':180.0,
'posn_y':ch- 25.0,
'velocity_x':-50.0,
'velocity_y':-70.0,
'ball_width':30.0,
'ball_height':30.0,
'color':"maroon1",
'coef_restitution':0.90}
def detect_wall_collision(ball):
animationmid-air collision# detect ball-to-wall collision
if ball['posn_x'] > cw - ball['ball_width']: # Right-hand wall.
ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \restitution']
ball['posn_x'] = cw - ball['ball_width']
if ball['posn_x'] < 1: # Left-hand wall.
ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \restitution']
ball['posn_x'] = 2
if ball['posn_y'] < ball['ball_height'] : # Ceiling.
ball['velocity_y'] = -ball['velocity_y'] * ball['coef_ \restitution']
ball['posn_y'] = ball['ball_height']
if ball['posn_y'] > ch - ball['ball_height'] : # Floor
ball['velocity_y'] = - ball['velocity_y'] * ball['coef_ \restitution']
ball['posn_y'] = ch - ball['ball_height']
def detect_ball_collision(ball_1, ball_2):
#detect ball-to-ball collision
# firstly: is there a close approach in the horizontal direction
if math.fabs(ball_1['posn_x'] - ball_2['posn_x']) < 25:
# secondly: is there also a close approach in the vertical # direction.
if math.fabs(ball_1['posn_y'] - ball_2['posn_y']) < 25:
ball_1['velocity_x'] = -ball_1['velocity_x'] # reverse # direction.
ball_1['velocity_y'] = -ball_1['velocity_y']
ball_2['velocity_x'] = -ball_2['velocity_x']
ball_2['velocity_y'] = -ball_2['velocity_y']
# to avoid internal rebounding inside balls
ball_1['posn_x'] += ball_1['velocity_x'] * time_scaling
ball_1['posn_y'] += ball_1['velocity_y'] * time_scaling
ball_2['posn_x'] += ball_2['velocity_x'] * time_scaling
ball_2['posn_y'] += ball_2['velocity_y'] * time_scaling
animationmid-air collisiondef diff_equation(ball):
x_old = ball['posn_x']
y_old = ball['posn_y']
ball['posn_x'] += ball['velocity_x'] * time_scaling
ball['velocity_y'] = ball['velocity_y'] + GRAVITY
ball['posn_y'] += ball['velocity_y'] * time_scaling
chart_1.create_oval( ball['posn_x'], ball['posn_y'],\
ball['posn_x'] + ball['ball_width'],\
ball['posn_y'] + ball['ball_height'],\
fill= ball['color'], tags="ball_tag")
chart_1.create_line( x_old, y_old, ball['posn_x'], \ ball ['posn_y'], fill= ball['color'])
detect_wall_collision(ball) # Has the ball # collided with any container wall?
for i in range(1,5000):
diff_equation(ball_1)
diff_equation(ball_2)
detect_ball_collision(ball_1, ball_2)
chart_1.update()
chart_1.after(cycle_period)
chart_1.delete("ball_tag") # Erase the balls but # leave the trajectories
root.mainloop()

它是如何工作的...

空中球与球碰撞分为两个步骤。在第一步中,我们测试两个球是否在由 if math.fabs(ball_1['posn_x'] - ball_2['posn_x']) < 25 定义的垂直条带内彼此靠近。用简单的话说,这是在问“球之间的水平距离是否小于 25 像素?”如果答案是肯定的,那么通过语句 if math.fabs(ball_1['posn_y'] - ball_2['posn_y']) < 25,检查区域被缩小到小于 25 像素的小垂直距离。所以每次循环执行时,我们都会扫描整个画布,看看两个球是否都位于一个区域,它们的左下角彼此之间的距离小于 25 像素。如果它们如此接近,那么我们只需通过在水平和垂直方向上反转它们的移动方向,简单地使它们相互反弹。

还有更多...

简单地反转方向并不是数学上正确的方式来反转碰撞球的方向。当然,台球不会那样表现。支配碰撞球体的物理定律要求动量守恒。这需要更复杂的数学,而这本书没有涵盖。

为什么我们有时会得到 tkinter.TckErrors?

如果我们在 Python 暂停时点击关闭窗口按钮(右上角的 X),当 Python 恢复并调用Tcl(Tkinter)在画布上绘制东西时,我们会得到一个错误消息。可能发生的情况是应用程序已经关闭,但Tcl还有未完成的事务。如果我们允许程序运行完成后再尝试关闭窗口,那么终止将是有序的。

旋转线

现在我们将看到如何处理旋转线。在任何类型的图形计算机工作中,最终都会出现需要旋转对象的情况。通过尽可能简单地开始,并逐步添加行为,我们可以处理一些越来越复杂的情况。这个步骤是制作旋转对象艺术中的第一步简单步骤。

准备工作

要理解旋转的数学,你需要对正弦、余弦和正切等三角函数有合理的熟悉度。对于那些一提到三角函数眼睛就发花的人来说,好消息是你可以使用这些例子而不需要理解三角函数。然而,如果你真的试图弄懂数学,那会更有成就感。这就像看足球和踢足球的区别。只有球员才会变得健康。

如何操作...

你只需要编写并运行这段代码,就像你为所有其他食谱所做的那样观察结果。洞察力来自于反复的尝试和修改代码。逐个改变变量p1_xp2_y的值,并观察结果。

# rotate_line_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
root = Tk()
root.title("Rotating line")
cw = 220 # canvas width
ch = 180 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 50 # pause duration (milliseconds).
p1_x = 90.0 # the pivot point
p1_y = 90.0 # the pivot point,
p2_x = 180.0 # the specific point to be rotated
p2_y = 160.0 # the specific point to be rotated.
a_radian = math.atan((p2_y - p1_y)/(p2_x - p1_x))
a_length = math.sqrt((p2_y - p1_y)*(p2_y - p1_y) +\
(p2_x - p1_x)*(p2_x - p1_x))
for i in range(1,300): # end the program after 300 position shifts
a_radian +=0.05 # incremental rotation of 0.05 radians
p1_x = p2_x - a_length * math.cos(a_radian)
p1_y = p2_y - a_length * math.sin(a_radian)
chart_1.create_line(p1_x, p1_y, p2_x, p2_y)
chart_1.update()
chart_1.after(cycle_period)
chart_1.delete(ALL)
root.mainloop()

它是如何工作的...

从本质上讲,所有旋转都归结为以下:

  • 建立旋转中心或支点

  • 选择你想要旋转的对象上的一个特定点

  • 计算从支点到特定兴趣点的距离

  • 计算连接支点和特定点的线的角度

  • 将连接点的线的角度增加一个已知的量,即旋转角度,然后重新计算该点的新的 x 和 y 坐标。

对于数学学生来说,你所要做的是将你的矩形坐标系的原点移动到支点,将你的特定点的坐标表达为极坐标,增加一个增量到角位置,然后将新的极坐标位置转换成一对新的矩形坐标。上述步骤执行了所有这些操作。

更多内容...

轴点故意放置在画布的底部角落,这样在旋转过程中,要旋转的线末端的点大部分时间都会落在画布之外。旋转过程继续进行,没有错误或不良行为,强调了本章前面提到的一个观点,即 Python 在数学上是稳健的。然而,在使用arctangent函数math.atan()时,我们需要小心,因为它会在角度通过 90 度和 270 度时从正值无穷大翻转到负值无穷大。Atan()可能会给出模糊的结果。再次强调,Python 的设计者通过创建atan2(y,x)函数来很好地处理了这个问题,该函数考虑了 y 和 x 的符号,从而在 180 度和-180 度之间给出无歧义的结果。

多行旋转的轨迹追踪

这个例子绘制了一种视觉上吸引人的新艺术风格箭头,但这只是快乐的一面的问题。这个菜谱的真正目的是看看你如何拥有任意数量的轴点,每个轴点都有不同的运动,而在 Python 中,基本的算术仍然简单且看起来干净。使用动画方法来减慢执行速度使得观看变得有趣。我们还看到,给画布上绘制对象的各个部分赋予的标签名称允许在调用canvas.delete(...)方法时选择性地删除它们。

准备工作

想象一个熟练的鼓队长在游行中行进,同时旋转着指挥棒。握住指挥棒末端的是一只小猴子,也在以不同的速度旋转着指挥棒。在猴子指挥棒的尖端是一只微型卷尾猴,以相反的方向旋转着指挥棒...

现在运行程序。

如何做...

按照我们之前做的那样运行下面的 Python 代码。结果将在下面的屏幕截图显示,显示了多行旋转轨迹。

如何做...

# multiple_line_rotations_1.py
multiple line rotationstrajectory tracing#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
root = Tk()
root.title("multi-line rotations")
cw = 600 # canvas width
ch = 600 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 50 # time between new positions of the ball # (milliseconds).
p0_x = 300.0
p0_y = 300.0
p1_x = 200.0
p1_y = 200.0
p2_x = 150.0 # central pivot
p2_y = 150.0 # central pivot
p3_x = 100.0
p3_y = 100.0
p4_x = 50.0
p4_y = 50.0
alpha_0 = math.atan((p0_y - p1_y)/(p0_x - p1_x))
length_0_1 = math.sqrt((p0_y - p1_y)*(p0_y - p1_y) +\
(p0_x - p1_x)*(p0_x - p1_x))
alpha_1 = math.atan((p1_y - p2_y)/(p1_x - p2_x))
multiple line rotationstrajectory tracinglength_1_2 = math.sqrt((p2_y - p1_y)*(p2_y - p1_y) +\
(p2_x - p1_x)*(p2_x - p1_x))
alpha_2 = math.atan((p2_y - p3_y)/(p2_x - p3_x))
length_2_3 = math.sqrt((p3_y - p2_y)*(p3_y - p2_y) +\
(p3_x - p2_x)*(p3_x - p2_x))
alpha_3 = math.atan((p3_y - p4_y)/(p3_x - p4_x))
length_3_4 = math.sqrt((p4_y - p3_y)*(p4_y - p3_y) +\
(p4_x - p3_x)*(p4_x - p3_x))
for i in range(1,5000):
alpha_0 += 0.1
alpha_1 += 0.3
alpha_2 -= 0.4
p1_x = p0_x - length_0_1 * math.cos(alpha_0)
p1_y = p0_y - length_0_1 * math.sin(alpha_0)
tip_locus_2_x = p2_x
tip_locus_2_y = p2_y
p2_x = p1_x - length_1_2 * math.cos(alpha_1)
p2_y = p1_y - length_1_2 * math.sin(alpha_1)
tip_locus_3_x = p3_x
tip_locus_3_y = p3_y
p3_x = p2_x - length_2_3 * math.cos(alpha_2)
p3_y = p2_y - length_2_3 * math.sin(alpha_2)
tip_locus_4_x = p4_x
tip_locus_4_y = p4_y
p4_x = p3_x - length_3_4 * math.cos(alpha_3)
p4_y = p3_y - length_3_4 * math.sin(alpha_3)
chart_1.create_line(p1_x, p1_y, p0_x, p0_y, tag='line_1')
chart_1.create_line(p2_x, p2_y, p1_x, p1_y, tag='line_2')
chart_1.create_line(p3_x, p3_y, p2_x, p2_y, tag='line_3')
chart_1.create_line(p4_x, p4_y, p3_x, p3_y, fill="purple", \tag='line_4')
# Locus tip_locus_2 at tip of line 1-2
chart_1.create_line(tip_locus_2_x, tip_locus_2_y, p2_x, p2_y, \ fill='maroon')
# Locus tip_locus_2 at tip of line 2-3
chart_1.create_line(tip_locus_3_x, tip_locus_3_y, p3_x, p3_y, \ fill='orchid1')
# Locus tip_locus_2 at tip of line 2-3
chart_1.create_line(tip_locus_4_x, tip_locus_4_y, p4_x, p4_y, \ fill='DeepPink')
chart_1.update()
chart_1.after(cycle_period)
chart_1.delete('line_1', 'line_2', 'line_3')
root.mainloop()

它是如何工作的...

正如我们在前面的菜谱中所做的那样,我们通过连接两个点来定义线,每个点都是用 Tkinter 绘图方法使用的矩形坐标来指定的。有三条这样的线连接轴点到尖端。将每个轴点到尖端线想象成一个鼓队长或是一只猴子可能会有所帮助。我们将每个轴点到尖端线转换为长度和角度的极坐标。然后,每个轴点到尖端线都以其自己的增量角度旋转。如果你改变这些角度 alpha_1 等或各种轴点的位置,你将得到无限多样有趣的图案。

还有更多...

一旦你能够控制和改变颜色,你就能创造出前所未有的非凡美丽图案。颜色控制是下一章的主题。

给你的玫瑰

本章的最后一个例子只是给读者的一个礼物。没有提供插图。我们只有在运行代码后才能看到结果。这是一个惊喜。

from Tkinter import *
root = Tk()
root.title("This is for you dear reader. A token of esteem and affection.")
import math
cw = 800 # canvas width
ch = 800 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="black")
chart_1.grid(row=0, column=0)
p0_x = 400.0
p0_y = 400.0
p1_x = 330.0
p1_y = 330.0
p2_x = 250.0
p2_y = 250.0
p3_x = 260.0
p3_y = 260.0
p4_x = 250.0
p4_y = 250.0
p5_x = 180.0
p5_y = 180.0
alpha_0 = math.atan((p0_y - p1_y)/(p0_x - p1_x))
length_0_1 = math.sqrt((p0_y - p1_y)*(p0_y - p1_y) + (p0_x - p1_ \x)*(p0_x - p1_x))
alpha_1 = math.atan((p1_y - p2_y)/(p1_x - p2_x))
length_1_2 = math.sqrt((p2_y - p1_y)*(p2_y - p1_y) + (p2_x - p1_ \x)*(p2_x - p1_x))
alpha_2 = math.atan((p2_y - p3_y)/(p2_x - p3_x))
length_2_3 = math.sqrt((p3_y - p2_y)*(p3_y - p2_y) + (p3_x - p2_ \ x)*(p3_x - p2_x))
alpha_3 = math.atan((p3_y - p4_y)/(p3_x - p4_x))
length_3_4 = math.sqrt((p4_y - p3_y)*(p4_y - p3_y) + (p4_x - p3_ \ x)*(p4_x - p3_x))
alpha_4 = math.atan((p3_y - p5_y)/(p3_x - p5_x))
length_4_5 = math.sqrt((p5_y - p4_y)*(p5_y - p4_y) + (p5_x - p4_ \x)*(p5_x - p4_x))
for i in range(1,2300): # end the program after 500 position # shifts.
animationdigital flower examplealpha_0 += 0.003
alpha_1 += 0.018
alpha_2 -= 0.054
alpha_3 -= 0.108
alpha_4 += 0.018
p1_x = p0_x - length_0_1 * math.cos(alpha_0)
p1_y = p0_y - length_0_1 * math.sin(alpha_0)
tip_locus_2_x = p2_x
tip_locus_2_y = p2_y
p2_x = p1_x - length_1_2 * math.cos(alpha_1)
p2_y = p1_y - length_1_2 * math.sin(alpha_1)
tip_locus_3_x = p3_x
tip_locus_3_y = p3_y
p3_x = p2_x - length_2_3 * math.cos(alpha_2)
p3_y = p2_y - length_2_3 * math.sin(alpha_2)
tip_locus_4_x = p4_x
tip_locus_4_y = p4_y
p4_x = p3_x - length_3_4 * math.cos(alpha_3)
p4_y = p3_y - length_3_4 * math.sin(alpha_3)
tip_locus_5_x = p5_x
tip_locus_5_y = p5_y
p5_x = p4_x - length_4_5 * math.cos(alpha_4)
p5_y = p4_y - length_4_5 * math.sin(alpha_4)
chart_1.create_line(p1_x, p1_y, p0_x, p0_y, tag='line_1', \ fill='gray')
chart_1.create_line(p2_x, p2_y, p1_x, p1_y, tag='line_2', \ fill='gray')
chart_1.create_line(p3_x, p3_y, p2_x, p2_y, tag='line_3', \ fill='gray')
chart_1.create_line(p4_x, p4_y, p3_x, p3_y, tag='line_4', \ fill='gray')
animationdigital flower examplechart_1.create_line(p5_x, p5_y, p4_x, p4_y, tag='line_5', \ fill='#550000')
chart_1.create_line(tip_locus_2_x, tip_locus_2_y, p2_x, p2_y, \ fill='#ff00aa')
chart_1.create_line(tip_locus_3_x, tip_locus_3_y, p3_x, p3_y, \ fill='#aa00aa')
chart_1.create_line(tip_locus_4_x, tip_locus_4_y, p4_x, p4_y, \ fill='#dd00dd')
chart_1.create_line(tip_locus_5_x, tip_locus_5_y, p5_x, p5_y, \ fill='#880066')
chart_1.create_line(tip_locus_2_x, tip_locus_2_y, p5_x, p5_y, \ fill='#0000ff')
chart_1.create_line(tip_locus_3_x, tip_locus_3_y, p4_x, p4_y, \ fill='#6600ff')
chart_1.update() # This refreshes the drawing on the # canvas.
chart_1.delete('line_1', 'line_2', 'line_3', 'line_4') # Erase # selected tags.
root.mainloop()

它是如何工作的...

这个程序的结构与之前的示例相似,但旋转参数已被调整以唤起玫瑰花的形象。所使用的颜色是为了提醒我们,在图形中控制颜色非常重要。

第五章。颜色的魔法

在本章中,我们将涵盖:

  • 一组有限的命名颜色

  • 九种指定颜色的方法

  • 变化阴影的红球

  • 红色渐变色调楔形

  • 艺术家的色轮(牛顿色轮)

  • 数值颜色混合匹配色板

  • 动画分级色轮

  • Tkinter 自带的颜色混合选择器

简介

Tkinter 允许您使用超过 1600 万种颜色。这是红色、绿色和蓝色 256 个级别的总和。有两种主要的方式来指定颜色:通过名称,或作为字符串打包的十六进制值。一个合格的颜色专家可以通过以不同比例混合红色、绿色和蓝色来创建任何可能的颜色。对于构成令人愉悦和有品位的颜色组合,有接受的标准规则和惯例。有时你想要制作阴影混合的颜色,而在其他时候,你只想使用尽可能少的颜色,并且两者的数量都尽可能少。我们将在本章中处理这些问题。

一组有限的命名颜色

有许多浪漫命名的颜色,如矢车菊蓝、雾色玫瑰或木瓜冰淇淋。大约有 340 种这些命名颜色可以在 Python 中使用。

颜色获得名称是因为人们最容易通过与一个地方和一个情感情绪的关联来记住它们。记住富有启发性的名称更容易,因此也更容易使用。在这个例子中,我们通过使用系统名称和消除非常相似的颜色,将长长的列表缩减到 140 个。

如何做到...

按照与前面章节中所有示例完全相同的方式执行程序。你应该在屏幕上看到的是一个逻辑排列的矩形色卡图表。每个色卡上都会有其可调用的名称。这些是在 Python/Tkinter 程序中可以使用的名称,并且它们将被正确显示。

#systematic_colorNames_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Systematically named colors - limited pallette")
cw = 1000 # canvas width
ch = 800 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="black")
canvas_1.grid(row=0, column=1)
whiteColors = "Gainsboro","peach puff","cornsilk",\
"honeydew","aliceblue","misty rose","snow", "snow3","snow4",\
"SlateGray1", "SlateGray3", "SlateGray4",\
"gray", "darkGray","DimGray","DarkSlateGray"
redColors = "Salmon","salmon1","salmon2","salmon3","salmon4",\
"orange red","OrangeRed2","OrangeRed3","OrangeRed4",\
"red","red3","red4",\
"IndianRed1","IndianRed3","IndianRed4",\
"firebrick","firebrick1","firebrick3","firebrick4",\
"sienna","sienna1","sienna3","sienna4"
pinkColors = "Pink","pink3","pink4",\
"hot pink","HotPink3","HotPink4",\
"deep pink","DeepPink3","DeepPink4",\
"PaleVioletRed1","PaleVioletRed2","PaleVioletRed3","PaleVioletRed4",\
"maroon","maroon1","maroon3","maroon4"
magentaColors = "magenta","magenta3","magenta4","DarkMagenta",\
"orchid1","orchid3","orchid4",\
"MediumOrchid3","MediumOrchid4",\
"DarkOrchid","DarkOrchid1","DarkOrchid4",\
"MediumPurple1","MediumPurple3", "MediumPurple4",\
"purple","purple3","purple4"
blueColors = "blue","blue3","blue4",\
"SlateBlue1", "SlateBlue3","SlateBlue4",\
"DodgerBlue2", "DodgerBlue3","DodgerBlue4",\
"deep sky blue","DeepSkyBlue3", "DeepSkyBlue4",\
"sky blue", "SkyBlue3", "SkyBlue4"
cyanColors = "CadetBlue1", "CadetBlue3", "CadetBlue4",\
"pale turquoise", "PaleTurquoise3","PaleTurquoise4",\
"cyan", "cyan3", "cyan4",\
"aquamarine","aquamarine3", "aquamarine4"
greenColors = "green", "green3", "green4","dark green",\
colorssimilar colors, eliminating"chartreuse", "chartreuse3", "chartreuse4",\
"SeaGreen","SeaGreen1", "SeaGreen3",\
"pale green", "PaleGreen3", "PaleGreen4",\
"spring green", "SpringGreen3", "SpringGreen4",\
"olive drab","OliveDrab1", "OliveDrab4",\
"dark olive green","DarkOliveGreen1", "DarkOliveGreen3", \ "DarkOliveGreen4",\
yellowColors= "yellow", "yellow3","yellow4",\
colorsrectangular color swatches chart"gold","gold3","gold4",\
"goldenrod","goldenrod1","goldenrod3","goldenrod4",\
"orange","orange3","orange4",\
"dark orange","DarkOrange1","DarkOrange4"
x_start = 10
y_start = 25
x_width = 118
x_offset = 2
y_height = 30
y_offset = 3
text_offset = 0
text_width = 95
kbk = [x_start, y_start, x_start + x_width, y_start + y_height]
defshowColors(selectedColor):
# Basic columnar color swatch display. All colours laid down in a # vertical stripe.
print "number of colors --> ", len(selectedColor)
for i in range (0,len(selectedColor)):
kula = selectedColor[i]
canvas_1.create_rectangle(kbk, fill=kula)
canvas_1.create_text(kbk[0]+10, kbk[1] , text=kula, \ width=text_width, fill ="black", anchor=NW)
kbk[1] += y_offset + y_height
y0 = kbk[1]
kbk[3] += y_offset + y_height
y1 = kbk[3]
kbk[1] = y_offset + y_height
kbk[3] = y_offset + 2 * y_height
kbk[0] += x_width + 2*x_offset
kbk[2] += x_width + 2*x_offset
return y0,y1
showColors(redColors)
showColors(pinkColors)
showColors(magentaColors)
showColors(cyanColors)
showColors(blueColors)
showColors(greenColors)
showColors(yellowColors)
showColors(whiteColors)
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

这个程序使用了在第二章中开发的技巧,绘制基本形状。有八个颜色家族的命名颜色列表,每个家族按照逻辑顺序排列。主要技术是使用一个通用函数,该函数将使用预定义的矩形,并通过for循环按顺序遍历颜色名称列表。在循环的每次迭代中,一个矩形被填充为该颜色,并在其上打印颜色名称。

更多...

这些颜色是通过试错选择的,以提供适合大多数目的的合理广泛的调色板。在编号的颜色序列中,如红色,其中 red1、red2、red3 和 red4 代表越来越深的阴影,与周围其他颜色非常相似的颜色已被省略。还发现许多颜色是假的,因为它们被涂在画布上作为灰色。

Tkinter 识别的所有颜色名称的完整集合可以在wiki.tcl.tk/16166找到

要获得原色的细腻阴影

为了实现颜色组合的微妙阴影和渐变,你需要以受控的量混合计算机屏幕上使用的原色。我们将在下一个配方中开始这个过程。

一个更紧凑的颜色列表

在以下颜色列表中,还有一些有用的命名颜色更短的子集:

  • white_Colors = "白色", "柠檬绸", "蜜桃冰沙","矢车菊蓝","风信子", "薄雾玫瑰"

  • blue_Colors = "蓝色","蓝色 4","板岩蓝色 1","闪蓝色","钢蓝色","天蓝色"

  • grey_Colors ="板岩灰色 3", "板岩灰色 4", "浅灰色", "深灰色", "暗灰色", "浅板岩灰色"

  • cyan_Colors = "军蓝 1", "青色", "青色 4", "浅海绿色", "碧绿", "碧绿 3"

  • red_Colors = "浅粉色","印度红 1","红色","红色 2","红色 3","红色 4"

  • pink_Colors = "浅粉色","深粉色","热粉色","HotPink3","浅粉色","浅粉色 2"

  • magenta_Colors = "淡紫罗兰红 1", "栗色", "栗色 1", "洋红色","洋红色 4", "兰花 1"

  • purple_Colors = "紫色", "紫色 4", "中紫色 1", "李子 2", "中兰花", "深兰花"

  • brown_Colors = "橙色", "深橙色 1", "深橙色 2", "深橙色 3", "深橙色 4", "马鞍棕色"

  • green_Colors = "绿色", "绿色 3", "绿色 4","黄绿色","绿黄色", "春绿色 2"

  • yellow_Colors= "浅黄色", "黄色", "黄色 3","金色", "金罗盘 1", "卡其色"

如果你将这些列表剪切并粘贴到systematic_colorNames_1.py中替换之前的列表,你将拥有一个更小、更容易管理的 55 色调板,你可能发现它更容易使用。

指定颜色的九种方式

通过这个配方,我们可以看到 Tkinter 识别的所有有效颜色指定的示例。基本上,Tkinter 有两种指定颜色的方法,但总共有九种表达这些颜色的方式。感谢 Python 的设计者,系统非常灵活,可以接受所有这些而不抱怨。

如何做到这一点...

以与第二章中所有示例完全相同的方式执行程序,绘制基本形状,你将看到三个填充红色的圆盘和四个填充蓝色的圆盘。每个圆盘的指定方式都不同。

# color_arithmetic_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title('Ways of Specifying Color')
cw = 270 # canvas width
ch = 80 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
# specify bottom-left and top-right as a set of four numbers named # 'xy'
named_color_1 = "light blue" # ok
named_color_2 = "lightblue" # ok
named_color_3 = "LightBlue" # ok
named_color_4 = "Light Blue" # ok
named_color_5 = "Light Blue" # Name error - not ok: Tcl Error, # unknown color name
rgb_color = "rgb(255,0,0)" # Unknown color name.
#rgb_percent_color = rgb(100%, 0%, 0%) # Invalid syntax
rgb_hex_1 = "#ff0000" # ok - 16.7 million colors
rgb_hex_2 = "#f00" # ok
rgb_hex_3 = "#ffff00000000" # ok - a ridiculous number
tk_rgb = "#%02x%02x%02x" % (128, 192, 200)
printtk_rgb
y1, width, height = 20,20,20
canvas_1.create_oval(10,y1,10+width,y1+height, fill= rgb_hex_1)
canvas_1.create_oval(30,y1,30+width,y1+height, fill= rgb_hex_2)
canvas_1.create_oval(50,y1,50+width,y1+height, fill= rgb_hex_3)
canvas_1.create_oval(70,y1,70+width,y1+height, fill= tk_rgb)
y1 = 40
canvas_1.create_oval(10,y1,10+width,y1+height, fill= named_color_1)
canvas_1.create_oval(30,y1,30+width,y1+height, fill= named_color_2)
canvas_1.create_oval(50,y1,50+width,y1+height, fill= named_color_3)
canvas_1.create_oval(70,y1,70+width,y1+height, fill= named_color_4)
root.mainloop()#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

Tkinter 在 Tkinter 模块库的某个地方定义了不同的名称字符串。

将颜色元组转换为 Tkinter 兼容的十六进制指定符

一些其他语言将颜色指定为红色、绿色和蓝色的数值混合,每个波段的范围从 0 到 255,作为一个元组。例如,纯红色将是(255,0,0),纯绿色将是(0,255,0),蓝色将是(0,0,255)。大量红色与中等绿色和一点蓝色混合可以是(230, 122, 20)。这些元组 Tkinter 不识别,但以下 Python 代码行将任何 color_tuple 转换为 Tkinter 可识别并用作颜色的十六进制数:

Tkinter_hex_color = '#%02x%02x%02x' % color_tuple,

其中 color_tuple = (230, 122, 20) 或我们选择的任何数字组合。

一个色调各异的红色沙滩球

我们使用十六进制颜色规范方案,根据预定义的数值常量列表排列一系列颜色深浅。其基本思想是建立一种方法,以便以可重用的方式访问这些常数,用于相当不同的图片设计。

如何做到这一点...

按照通常的方式执行显示的程序,你会看到一系列从深到浅的彩色圆盘叠加在一起。每个圆盘的大小和位置由列表 hFacwFac 决定。hFac 是 "高度因子" 的缩写,wFac 是 "宽度因子"。以下截图显示了分级色彩球。

如何做到这一点...

# red_beach_ball_1.py
# >>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Red beach ball")
cw = 240 # canvas width
ch = 220 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="black")
chart_1.grid(row=0, column=0)
x_orig = 100
y_orig = 200
x_width = 80
y_hite = 180
xy0 = [x_orig, y_orig]
hexadecimal color specification schemecolor shades series, preparingxy1 = [x_orig - x_width, y_orig - y_hite]
xy2 = [x_orig + x_width, y_orig - y_hite ]
wedge =[ xy0, xy1 , xy2 ]
width= 80 # Standard disk diameter
hite = 80 # Median distance from origin (x_orig, y_orig).
hFac = [1.1, 1.15, 1.25, 1.35, 1.5, 1.6, 1.7] # Height # radial factors.
wFac = [ 2.0, 1.9, 1.7, 1.4, 1.1, 0.75, 0.40] # Disk # diameter factors.
# Color list. Elements incresing in darkness.
kulaRed = ["#500000","#6e0000","#a00000","#ff0000",\
"#ff5050", "#ff8c8c", "#ffc8c8", "#ffffff" ]
kula = kulaRed
for i in range(0, 7): # Red disks
x0_disk = xy0[0] - width * wFac[i]/2 # Bottom left
y0_disk = xy0[1] - hite * hFac[i] + width * wFac[i]/2
xya = [x0_disk, y0_disk] # BOTTOM LEFT
x1_disk = xy0[0] + width * wFac[i]/2 # Top right
y1_disk = xy0[1] - hite * hFac[i] - width * wFac[i]/2
xyb = [x1_disk, y1_disk] # TOP RIGHT
chart_1.create_oval(xya ,xyb , fill=kula[i], outline=kula[i])
root.mainloop()

它是如何工作的...

通过一个循环,将不同深浅的红盘图像按特定顺序排列。匹配的红色深浅保存在按顺序排列的十六进制颜色列表中。Hex 是十六进制的简称。

用于指定参考原点以及所有其他位置参数的变量已经设置好,以便以后在其他模式中重用。这里的重要原则是,通过我们编程的精心规划,我们只需要以通用、专为重用设计的方案解决一个问题。当然,在实践中,这种计划设计需要更多时间,包括大量的实验,比简单的单次编写代码的方式要复杂得多。无论如何,整个实验过程都是从编写混乱、粗糙且能勉强工作的代码开始的。这种初步的粗糙工作是创造性过程中的一个非常重要的部分,因为它允许模糊形成的思想成长和演变成为有效的软件程序。

还有更多...

在确定了在选择的几何排列中绘制阴影圆盘的方案后,我们现在可以尝试不同的排列,并最终得到更丰富、更有用的想法。接下来的两个配方将这一想法发展成艺术家色彩轮的一个版本,展示了如何通过控制混合原色来达到任何颜色。

一个按色调分级的红色色楔

我们创建一个楔形段来形成一个逻辑模式,该模式可以融入一个轮状排列,用以展示不同颜色之间的关系。

如何做到这一点...

在前面的配方中使用的代码结构在此处被重用。当你执行以下代码时,你会看到一排整齐的彩色圆盘被叠加在一个深色阴影的三角形楔形上,从深红色到浅红色。以下截图显示了分级色彩楔形。

如何做到这一点...

# red_color_segment_1.py
#>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Red color wedge")
cw = 240 # canvas width
ch = 220 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
theta_deg = 0.0
x_orig = 100
y_orig = 200
x_width = 80
y_hite = 180
xy0 = [x_orig, y_orig]
wedge-shaped segmentcreatingxy1 = [x_orig - x_width, y_orig - y_hite]
xy2 = [x_orig + x_width, y_orig - y_hite ]
wedge =[ xy0, xy1 , xy2 ]
width= 40 #standard disk diameter
hite = 80 # median wedge height.
hFac = [0.25, 0.45, 0.75, 1.2, 1.63, 1.87, 2.05] # Radial # factors
wFac = [ 0.2, 0.36, 0.6, 1.0, 0.5, 0.3, 0.25] # disk # diameter factors
# Color list. Elements increasing in darkness.
kulaRed = ["#000000","#6e0000","#a00000","#ff0000",\
"#ff5050", "#ff8c8c", "#ffc8c8", \ "#440000" ]
kula = kulaRed
wedge =[ xy0, xy1 , xy2 ] # black background
chart_1.create_polygon(wedge,fill=kula[0])
x_width = 40 # dark red wedge
y_hite = 160
xy1 = [x_orig - x_width, y_orig - y_hite]
xy2 = [x_orig + x_width, y_orig - y_hite ]
wedge =[ xy0, xy1 , xy2 ]
chart_1.create_polygon(wedge,fill=kula[1])
for i in range(0, 7): # red disks
x0_disk = xy0[0] - width * wFac[i]/2 # bottom left
y0_disk = xy0[1] - hite * hFac[i] + width * wFac[i]/2
xya = [x0_disk, y0_disk] # BOTTOM LEFT
x1_disk = xy0[0] + width * wFac[i]/2 # top right
y1_disk = xy0[1] - hite * hFac[i] - width * wFac[i]/2
xyb = [x1_disk, y1_disk] #TOP RIGHT
chart_1.create_oval(xya ,xyb , fill=kula[i], outline=kula[i])
root.mainloop()

它是如何工作的...

通过调整列表 hFacwFac 中的数值,我们将彩色圆盘排列在背景楔形区域内,这个楔形区域恰好是形成圆形十二分之一切片的正确形状。

还有更多...

我们对颜色列表的命名和重新命名方式kula似乎有些冗余,因此可能有些令人困惑。然而,这种看似疯狂的方法是,如果我们同时使用许多其他颜色列表,那么重用现有方法就会变得简单得多。

牛顿的色彩混合大轮

我们制作了一个艺术家色彩轮的版本,展示了如何通过巧妙地混合红色、绿色和蓝色三种原色,获得任何已知颜色和色调。

如何做...

我们制作了一套十二种颜色的列表。每个列表代表当你混合它两侧的颜色时产生的颜色,除了红色、绿色和蓝色这三种原色。代码中的另一个关键添加是函数 rotate(xya, xyb, theta_deg_incr),它用于将颜色楔形图案旋转到新的选择位置,围绕中心点。由于使用了三角函数进行旋转,因此需要在代码顶部导入数学模块。每个部分都构成了完整颜色变化圆的一部分。以下截图显示了艾萨克·牛顿的色彩轮版本。

如何做...

# primary_color_wheel_1.py
#>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
root = Tk()
root.title("Color wheel segments")
cw = 400 # canvas width
ch = 400 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="black")
chart_1.grid(row=0, column=0)
theta_deg = 0.0
color mixingx_orig = 200
y_orig = 200
x_width = 40
y_hite = 160
xy0 = [x_orig, y_orig]
xy1 = [x_orig - x_width, y_orig - y_hite]
xy2 = [x_orig + x_width, y_orig - y_hite ]
wedge =[ xy0, xy1 , xy2 ]
width= 40 #standard disk diameter
hite = 80 # median wedge height.
hFac = [0.25, 0.45, 0.75, 1.2, 1.63, 1.87, 2.05] # Radial # factors
wFac = [ 0.2, 0.36, 0.6, 1.0, 0.5, 0.3, 0.25] # disk # diameter factors
x_DiskRot = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] # rotational coordinates
y_DiskRot = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
#RED
kulaRed = ["#000000", "#6e0000", "#a00000", "#ff0000",\
"#ff5050", "#ff8c8c", "#ffc8c8", \ "#440000" ]
# Khaki
kulaRRedGreen = ["#000000", "#606000", "#8f9f00", "#b3b300",\
"#d6d600", "#dbdb30", \ "#dbdb77", "#3e2700" ]
# Yellow
kulaRedGreen = ["#000000", "#6e6e00", "#a0a000", "#ffff00",\
"#ffff50", "#ffff8c", \ "#ffffc8", "#444400" ]
# Orange
kulaRedGGreen = ["#000000", "#493100", "#692f00", "#a25d00",\
"#ff8300", "#ffa55a", \ "#ffb681", "#303030" ]
# Green
kulaGreen = ["#000000", "#006e00", "#00a000", "#00ff00",\
"#50ff50", "#8cff8c", "#c8ffc8", \ "#004400" ]
# Dark green
kulaGGreenBlue = ["#000000", "#003227", "#009358", "#00a141",\
"#00ff76", "#72ff99", \ "#acffbf", "#003a1d" ]
# Cyan
kulaGreenBlue = ["#000000", "#006e6e", "#00a0a0", "#00ffff",\
"#50ffff", "#8cffff", \ "#c8ffff", "#004444" ]
# Steel Blue
kulaGreenBBlue = ["#000000", "#002c46", "#00639c", "#008cc8",\
"#00b6ff", "#7bb6ff", \ "#addfff", "#001a27" ]
# Blue
kulaBlue = ["#000000", "#00006e", "#0000a0", "#0000ff",\
"#5050ff", "#8c8cff", "#c8c8ff", \ "#000044" ]
# Purple
kulaBBlueRed = ["#000000", "#470047", "#6c00a2", "#8f00ff",\
"#b380ff", "#d8b3ff", "#f1deff", \ "#200031" ]
# Crimson
kulaBlueRed = ["#000000", "#6e006e", "#a000a0", "#ff00ff",\
"#ff50ff", "#ff8cff", "#ffc8ff", \ "#440044" ]
# Magenta
kulaBlueRRed = ["#000000", "#380023", "#80005a", "#b8007b",\
"#ff00a1", "#ff64c5", "#ff89ea", \ "#2e0018" ]
# ROTATE
def rotate(xya, xyb, theta_deg_incr): #xya, xyb are 2 component # points
# General purpose point rotation function
theta_rad = math.radians(theta_deg_incr)
a_radian = math.atan2( (xyb[1] - xya[1]) , (xyb[0] - xya[0]) )
a_length = math.sqrt( (xyb[1] - xya[1])**2 + (xyb[0] - xya[0])**2)
theta_rad += a_radian
theta_deg = math.degrees(theta_rad)
new_x = a_length * math.cos(theta_rad)
new_y = a_length * math.sin(theta_rad)
return new_x, new_y, theta_deg # theta_deg = post # rotation angle
# GENL. SEGMENT BACKGROUND FUNCTION
defsegmentBackground(kula, angle, xy1, xy2):
xy_new1 = rotate(xy0, xy1, angle) # rotate xy1
xy1 =[ xy_new1[0] + xy0[0], xy_new1[1] + xy0[1] ]
xy_new2 = rotate(xy0, xy2, angle) # rotate xy2
xy2 =[ xy_new2[0] + xy0[0], xy_new2[1] + xy0[1] ]
wedge =[ xy0, xy1 , xy2 ]
chart_1.create_polygon(wedge,fill=kula[7])
# GENL. COLOR DISKS FUNCTION
defcolorDisks( kula, angle):
global hite, width, hFac, wFac
for i in range(0, 7): # green segment disks
xya = [xy0[0], xy0[1] - hite * hFac[i] ] # position of point for # rotation
xy_new1 = rotate(xy0, xya, angle) # rotate xya
# NEW CIRCLE CENTERS AFTER ROTATION OF CENTERLINE
x0_disk = xy_new1[0] + xy0[0] - width*wFac[i]/2
y0_disk = xy_new1[1] + xy0[1] + width * wFac[i]/2
xya = [x0_disk, y0_disk] # BOTTOM LEFT
x1_disk = xy_new1[0] + xy0[0] + width*wFac[i]/2
y1_disk = xy_new1[1] + xy0[1] - width * wFac[i]/2
xyb = [x1_disk, y1_disk] #TOP RIGHT
chart_1.create_oval(xya ,xyb , fill=kula[i], outline=kula[i])
for i in range(0,12):
if i==0:
angle = 0.0
kula = kulaRed
if i==1:
angle = 30.0
kula = kulaRRedGreen
if i==2:
angle = 60.0
kula = kulaRedGreen
if i==3:
angle = 90.0
kula = kulaRedGGreen
if i==4:
angle = 120.0
kula = kulaGreen
if i==5:
angle = 150.0
kula = kulaGGreenBlue
if i==6:
angle = 180.0
kula = kulaGreenBlue
if i==7:
angle = 210.0
kula = kulaGreenBBlue
if i==8:
angle = 240.0
kula = kulaBlue
if i==9:
angle = 270.0
kula = kulaBBlueRed
if i==10:
angle = 300.0
kula = kulaBlueRed
if i==11:
angle = 330.0
kula = kulaBlueRRed
if i==12:
angle = 360.0
kula = kulaBlueRRed
segmentBackground( kula, angle, xy1, xy2)
colorDisks( kula, angle)
root.mainloop()

它是如何工作的...

对于轮盘上的每个颜色部分,列表中都包含了一系列的阴影十六进制颜色值。要混合出需要所有三种原色部分的颜色,所需的红色、绿色和蓝色的确切比例并不是一件简单的事情。一般来说,为了使颜色变亮,我们需要添加一些不属于目标颜色的额外颜色。例如,如果我们想要浅黄色,我们需要等量的红色和绿色。但要使黄色更浅,我们需要添加一些蓝色。为了使黄色变暗,我们确保没有蓝色,并组合较小但相等的红色和蓝色比例。

还有更多...

混合颜色既是艺术也是科学。精明的颜色混合需要实践和实验。对人类大脑来说,数值上混合颜色并不自然。我们需要一些视觉-数值-计算工具来帮助我们混合颜色。但数学必须是隐形的。它不能阻碍艺术家。我们希望有原色管和调色板来混合它们。我们的调色板必须自动显示代表我们混合颜色的数值,这样我们就可以记录并将它们纳入 Python 代码中。如果我们的调色板可以放在现有图片的顶部或旁边,以便我们可以匹配图片中的现有颜色,那会是一件好事吗?嗯,下一个菜谱试图实现这个愿望。

数值颜色混合匹配调色板

我们制作了一个小部件,可以轻松混合红色、绿色和蓝色三种基色的任何比例。混合后的颜色会显示在一个大色卡上,可以拖动到显示屏幕的任何位置。色卡位于小部件的边缘,中间颜色最少。我们可以将色卡放置在我们想要匹配的图片中的任何颜色旁边,并使用直观的滑动控件调整混合颜色。

如何操作...

为了制作这个混合工具,我们在正式介绍它们之前的两章中使用了 Tkinter 滑动控件。在这个阶段,您只需复制并使用代码,无需了解它们的工作细节,知道它们将在第七章中解释。

以下截图显示了一个颜色混合调色板。

如何操作...

# color_mixer_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Color mixer in Hex and Integer")
canvas_1 = Canvas(root, width=320, height=400, background="white")
canvas_1.grid(row=0, column=1)
slide_value_red = IntVar() # variables used by slider controls
slide_value_green = IntVar()
slide_value_blue = IntVar()
fnt = 'Bookantiqua 14 bold'
combined_hex = '000000'
red_hex = '00'
green_hex = '00'
blue_hex = '00'
red_int = 0
green_int = 0
blue_int = 0
red_text = 0
green_text = 0
blue_text = 0
# red display
canvas_1.create_rectangle( 20, 30, 80, 110)
canvas_1.create_text(20,10, text="Red", width=60, font=fnt,\
anchor=NW, fill='red' )
# green display
canvas_1.create_rectangle( 100, 30, 160, 110)
canvas_1.create_text(100,10, text="Green", width=60, font=fnt,\
anchor=NW, fill='green' )
# blue display
canvas_1.create_rectangle( 180, 30, 240, 110)
canvas_1.create_text(180,10, text="Blue", width=60, font=fnt,\
anchor=NW, fill='blue' )
# Labels
canvas_1.create_text(250,30, text="integer 256", width=60, anchor=NW )
canvas_1.create_text(250,60, text="% of 256", width=60, anchor=NW )
canvas_1.create_text(250,86, text="hex", width=60, anchor=NW )
# combined display
fnt = 'Bookantiqua 12 bold'
canvas_1.create_rectangle( 20, 170, 220, 220 )
canvas_1.create_text(20,130, text="Combined colors", width=200, font=fnt,\
anchor=NW, fill='black' )
canvas_1.create_text(20,150, text="Hexadecimal red-green-blue", width=300,
font=fnt,anchor=NW, fill='black' )
# callback functions to service slider changes
#=============================================
defcodeShorten(slide_value, x0, y0, width, height, kula):
# This allows the callback functions to be reduced in length.
global combined_hex, red_int, green_int, blue_int
fnt = 'Bookantiqua 12 bold'
slide_txt = str(slide_value)
slide_int = int(slide_value)
slide_hex = hex(slide_int)
slide_percent = slide_int * 100 / 256
canvas_1.create_rectangle(x0, y0, x0 + width, y0 + height, \ fill='white')
canvas_1.create_text(x0+6, y0+6, text=slide_txt, width=width, \ font=fnt,\
anchor=NW, fill=kula )
canvas_1.create_text(x0+6, y0+28, text=slide_percent, \ width=width,\
font=fnt, anchor=NW, fill=kula)
canvas_1.create_text(x0+6, y0+50, text=slide_hex, width=width,\
font=fnt, anchor=NW, fill=kula)
return slide_int
defcallback_red(*args): # red slider event handler
global red_int
kula = "red"
jimmy = str(slide_value_red.get())
red_int = codeShorten(jimmy, 20, 30, 60, 80, kula)
update_display(red_int, green_int, blue_int)
defcallback_green(*args): # green slider event handler
global green_int
kula = "darkgreen"
jimmy = str(slide_value_green.get())
green_int = codeShorten(jimmy, 100, 30, 60, 80, kula)
update_display(red_int, green_int, blue_int)
defcallback_blue(*args): # blue slider event handler
global blue_int
kula = "blue"
jimmy = str(slide_value_blue.get())
blue_int = codeShorten(jimmy, 180, 30, 60, 80, kula)
update_display(red_int, green_int, blue_int)
defupdate_display(red_int, green_int, blue_int):
# Refresh the swatch and nymerical display.
combined_int = (red_int, green_int, blue_int)
combined_hex = '#%02x%02x%02x' % combined_int
canvas_1.create_rectangle( 20, 170, 220 , 220, fill='white')
canvas_1.create_text(26, 170, text=combined_hex, width=200,\
anchor=NW, font='Bookantiqua 16 bold')
canvas_1.create_rectangle( 0, 400, 300, 230, fill=combined_hex)
slide_value_red.trace_variable("w", callback_red)
slide_value_green.trace_variable("w", callback_green)
slide_value_blue.trace_variable("w", callback_blue)
slider_red = Scale(root, # red slider specification # parameters.
length = 400,
fg = 'red',
activebackground = "tomato",
background = "grey",
troughcolor = "red",
label = "RED",
from_ = 0,
to = 255,
resolution = 1,
variable = slide_value_red,
orient = 'vertical')
slider_red.grid(row=0, column=2)
slider_green =Scale(root, # green slider specification # parameters.
length = 400,
fg = 'dark green',
activebackground = "green yellow",
background = "grey",
troughcolor = "green",
label = "GREEN",
from_ = 0,
to = 255,
resolution = 1,
variable = slide_value_green,
orient = 'vertical')
slider_green.grid(row=0, column=3)
slider_blue = Scale(root, # blue slider specification # parameters.
length = 400,
fg = 'blue',
activebackground = "turquoise",
background = "grey",
troughcolor = "blue",
label = "BLUE",
from_ = 0,
to = 255,
resolution = 1,
variable = slide_value_blue,
orient = 'vertical')
slider_blue.grid(row=0, column=4)
root.mainloop()

它是如何工作的...

红色、绿色和蓝色颜色值从零(完全没有颜色)到 255(完全饱和的基色)通过一个滑动控件的位置来设置,这个控件的使用非常直观。每次移动滑动条时,三个滑动条的所有值都会组合并图形化地显示在色卡上,同时以数值形式显示。没有比用 0 到 255 的整数值、十六进制值和纯色或混合色表示的基色成分之间的关系更好的解释方式了。

更多内容...

此小部件将色卡放置在底部的左上角边缘,以便您可以将其拖动到屏幕下方的图片区域的附近,以便能够直观地匹配颜色并读取其十六进制值。还有一个单独的窗口,里面充满了颜色,可以在屏幕上自由移动。如果您想要匹配图像中某个部分的颜色,可以将此色卡放置在图像中感兴趣的区域旁边,并移动滑动条,直到达到满意的匹配,然后记录下十六进制值。

有其他工具可以用来选择颜色

本章的最后一个示例演示了在 Python 模块中构建的颜色混合器。

有没有办法制作更整洁的滑动控制器?

使用滑动控件作为输入数字的图形方法,有时会不方便。为什么我们不能让我们的数字控制器成为画布内的一种绘制对象呢?我们能否使滑动控制器更小、更整洁、不那么显眼?答案是肯定的,我们在第七章中探讨了这一想法,

动态分级色轮

我们绘制了一个艺术家颜色混合轮的平滑分级版本,并对其进行了动画处理,以便观众可以观察当绘制混合颜色光谱时rgb十六进制颜色值的变化。

如何操作...

按照您之前的方式复制、保存并运行此示例,并观察光谱如何以数字和色彩的方式展开。以下截图显示了一个分级色轮。

如何做...

#animated_color_wheel_1.py
# >>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Animated Color Wheel")
cw = 300 # canvas width
ch = 300 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, background="black")
canvas_1.grid(row=0, column=1)
cycle_period = 200
redFl = 255.0
greenFl = 0
blueFl = 0
kula = "#000000"
arcStart = 89
arcEnd = 90
xCentr = 150
yCentr = 160
radius = 130
circ = xCentr - radius, yCentr + radius, xCentr + radius, yCentr - \ radius
# angular position markers, degrees
A_ANG = 0
B_ANG = 60
C_ANG = 120
D_ANG = 180
E_ANG = 240
F_ANG = 300
#G_ANG = 1
G_ANG = 359
intervals = 60 # degrees
# Percent color at each position marker
# index 0 1 2 3 4 5 6 7
redShift = 100, 100, 0, 0, 0, 100, 100 # percent of red
greenShift = 0, 100, 100, 100, 0, 0, 0 # percent of green
blueShift = 0, 0, 0, 100, 100, 100, 0 # percent of blue
# Rate of change of color per degree, rgb integer counts per degree.
red_rate = [0,1,2,3,4,5,6,7]
green_rate = [0,1,2,3,4,5,6,7]
blue_rate = [0,1,2,3,4,5,6,7]
# Calibrate counts-per-degree in each interval, place in xrate list
for i in range(0,6):
red_rate[i] = 256.0 * (redShift[i+1] - redShift[i])/(100 * \ intervals)
green_rate[i] = 256.0 * (greenShift[i+1] - greenShift[i])/(100 * \ intervals)
blue_rate[i] = 256.0 * (blueShift[i+1] - blueShift[i])/(100 * \ intervals)
def rgb2hex(redFl, greenFl, blueFl):
# Convert integer to hex color.
red = int(redFl)
green = int(greenFl)
blue = int(blueFl)
rgb = red, green, blue
return '#%02x%02x%02x' % rgb
for i in range (0, 359):
canvas_1.create_arc(circ, start=arcStart, extent=arcStart - arcEnd,\
fill= kula, outline= kula)
arcStart = arcEnd
arcEnd -=1
# Color component transitions in 60 degree sectors
if i>A_ANG and i<B_ANG:
redFl += red_rate[0]
greenFl += green_rate[0]
blueFl += blue_rate[0]
kula = rgb2hex(redFl, greenFl, blueFl)
if i>B_ANG and i<C_ANG:
redFl += red_rate[1]
greenFl += green_rate[1]
blueFl += blue_rate[1]
kula = rgb2hex(redFl, greenFl, blueFl)
if i>C_ANG and i<D_ANG:
redFl += red_rate[2]
greenFl += green_rate[2]
blueFl += blue_rate[2]
kula = rgb2hex(redFl, greenFl, blueFl)
if i>D_ANG and i<E_ANG:
redFl += red_rate[3]
greenFl += green_rate[3]
blueFl += blue_rate[3]
kula = rgb2hex(redFl, greenFl, blueFl)
if i>E_ANG and i<F_ANG:
redFl += red_rate[4]
greenFl += green_rate[4]
blueFl += blue_rate[4]
kula = rgb2hex(redFl, greenFl, blueFl)
if i>F_ANG and i<G_ANG:
redFl += red_rate[5]
greenFl += green_rate[5]
blueFl += blue_rate[5]
kula = rgb2hex(redFl, greenFl, blueFl)
#kula = rgb2hex(redFl, greenFl, blueFl)
canvas_1.create_text(100, 20, text=kula, fill='white', \ width=200,\
font='SansSerif 12 ', tag= 'degreesAround', anchor= SW)
canvas_1.update() # This refreshes the # drawing on the canvas.
canvas_1.after(cycle_period) # This makes execution pause for # 200 milliseconds.
canvas_1.delete('degreesAround') # This erases the # changing text
root.mainloop()

它是如何工作的...

这里使用的编码思想相对简单。本质上,我们让执行代码通过绘制从 0 到 358 度的彩色弧的过程来完成。在扇形的每一小块中,根据线性增加或减少的斜坡值redFLgreenflyblueFL(以每度计数)的计算,添加红色、绿色和蓝色成分。斜坡意味着从 0 逐渐增加到 100%的值。斜坡值由过渡点(A_ANG、B_ANG等)控制,这些过渡点在彩色圆盘边缘以 60 度间隔均匀分布。

rgb2hex(red, green, blue)函数将红色、绿色和蓝色的浮点值转换为 Tkinter 解释为颜色的十六进制数形式。为了方便观众理解,这个数字显示在画布的顶部。

Tkinter 自带的颜色选择器-混合器

Tkinter 有一个自己非常简单易用的颜色选择工具。

四行代码就能得到一个既优雅又实用的工具。

如何做...

按照您之前处理程序的方式复制、保存并运行此示例。以下截图显示了 Tkinter 的颜色选择器(MS Windows XP)。

如何做...

以下截图显示了 Tkinter 的颜色选择器(Linux Ubuntu 9.10)。

如何做...

# color_picker_1 .py
#>>>>>>>>>>>>>>>
from Tkinter import *
from tkColorChooser import askcolor
askcolor()
mainloop()

它是如何工作的...

这个工具非常简单易用,您可能会问为什么我们还要在数值颜色混合匹配调色板示例中展示更繁琐的版本。有两个原因。首先,我们可以看到如何在 Python 代码中操作颜色。其次,您可以移动到图片上方的独立样本窗口可能很有用。

更多...

颜色混合、命名法和有品位的颜色组合的主题非常广泛且有趣。网络提供了一些非常优雅地解释这种艺术和科学的优秀网站。

这里是一些解释这些想法非常好的网页精选。

第六章. 处理图片

在本章中,我们将涵盖:

  • 原生 Python 中的图片格式

  • 打开图像并发现其属性

  • Python 图片库格式转换:.jpg, .png, .tiff, .gif,以及 .bmp

  • 平面内的图像旋转

  • 调整图片大小

  • 按正确宽高比调整大小

  • 旋转图片

  • 分离颜色带

  • 红色、绿色和蓝色颜色重新混合

  • 通过混合组合图片

  • 通过调整百分比混合图片

  • 使用图像蒙版制作合成图

  • 水平和垂直偏移(滚动)图片

  • 几何变换:水平和垂直翻转和旋转

  • 过滤器:锐化、模糊、边缘增强、浮雕、平滑、轮廓和细节

  • 通过调整大小实现的明显旋转

现在我们将处理栅格图像。这些包括照片、位图图像和数字绘画等所有图像类型,它们都不是我们至今一直在使用的矢量图形绘制。栅格图像由像素组成,这是图片元素的简称。矢量图像是定义为数学形状和颜色表达式,可以在您的直接控制下通过代数和算术进行修改。这些矢量图形只是计算机图形世界的一部分。

另一部分涉及照片和绘制的位图图像的表示和处理,通常被称为栅格图像。Python 识别的唯一栅格图像类型是 GIF图形交换格式)图像,它具有有限的颜色能力;GIF 可以处理 256 种不同的颜色,而 .png.jpg 则有 1670 万种。优点是 GIF 图像在 Python 中的控制允许您使它们动画化,但基本的 Tkinter 提供的库中没有可以操作和改变栅格图像的函数。

然而,有一个非常有用的 Python 模块集合,即 Python Imaging LibraryPIL),它是专门为栅格图像操作设计的。它具有大多数优秀的照片编辑工具所拥有的基本功能。PIL 模块可以轻松地将一种格式转换为另一种格式,包括 GIF, PNG, TIFF, JPEG, BMP,并且 PIL 还可以与许多其他格式一起工作,但前面提到的可能是最常见的。Python 图片库是您一般图形工具包和技能库的重要组成部分。

为了减少混淆,我们将使用文件扩展名缩写,如 .gif, .png, .jpg 等作为 GIF, PNGJPEG 等文件格式的名称。

打开图像文件并发现其属性

首先,我们需要测试 PIL 是否已加载到包含我们其他 Python 模块的库中。最简单的方法是尝试使用 Image 模块的 image_open() 函数打开一个文件。

准备工作

如果 Python Imaging Library (PIL) 还未安装在我们的文件系统中,并且对 Python 可用,我们需要找到并安装它。Tkinter 不需要用于光栅图像处理。你会注意到没有 from Tkinter import * 和没有 root = tK()root.mainloop() 语句。

你可以从 www.pythonware.com/products/pil/ 下载 PIL。

这个网站包含源代码、MS Windows 安装可执行文件和 HTML 或 PDF 格式的手册。

注意

关于 PIL 的最佳解释文档之一是位于新墨西哥技术计算机中心的一个 PDF 文件 infohost.nmt.edu/tcc/help/pubs/pil.pdf。它清晰简洁。

在本章接下来的所有示例中,所有保存到我们硬盘上的图像都被放置在 constr 文件夹内的 picsx 文件夹中。这是为了将结果与包含所有将要用到的输入图像的 pics1 文件夹分开。这使我们免于决定保留什么和丢弃什么的困境。你应该保留 pics1 中的所有内容,并且可以丢弃 picsx 中的任何内容,因为重新运行创建这些文件的程序应该很简单。

如何做到...

将以下代码复制到编辑器中,并保存为 image_getattributes_1.py,然后像所有之前的程序一样执行。在这个程序中,我们将使用 PIL 来发现 JPG 格式图像的属性。请记住,尽管 Python 本身只能识别 GIF 图像,但 PIL 模块可以处理许多图像格式。在我们能让这个小程序运行之前,我们无法进一步使用 PIL。

# image_getattributes_1.py
# >>>>>>>>>>>>>>>>>>
import Image
imageFile = "/constr/pics1/canary_a.jpg"
im_1 = Image.open(imageFile)
im_width = im_1.size[0]
im_height = im_1.size[1]
im_mode = im_1.mode
im_format = im_1.format
print "Size: ",im_width, im_height
print "Mode: ",im_mode
print "Format: ",im_format
im_1.show()

它是如何工作的...

PIL 库的一部分 Image 模块有一个 Image_open() 方法,它可以打开被识别为图像文件的文件。它不会显示图像。这可能会让人困惑。打开一个文件意味着我们的应用程序已经找到了文件的位置,并处理了加载文件所需的所有权限和管理。当你打开一个文件时,会读取文件头以确定文件格式并提取解码文件所需的东西,如模式、大小和其他属性,但文件的其他部分将在稍后处理。模式是一个术语,用来指代包含图像的数据字节应该如何被解释,比如一个特定的字节是否指的是红色通道或透明度通道等等。只有当 Image 模块接收到查看文件、更改其大小、查看某个颜色通道、旋转它或 PIL 模块可以对图像文件执行的数十种操作之一的命令时,它才会从硬盘驱动器实际加载到内存中。

如果我们想查看图像,那么我们使用 im_1\. show() 方法。只需在末尾添加一行 im.show() 即可。

为什么我们需要获取图像属性?当我们准备更改和操作图像时,我们需要更改属性,因此我们通常需要能够找出它们的原始属性。

还有更多...

PIL(Python Imaging Library)的 Image 模块可以读取和写入(打开和保存)常见的图像格式。以下格式既可以读取也可以写入:BMP, GIF, IM, JPG, JPEG, JPE, PCX, PNG, PBM, PPN, TIF, TIFF, XBM, XPM

以下文件格式只能读取:PCD, DCX, PSD。如果我们需要存储 PCD, DCXPSD 格式的图像文件,那么我们首先需要将它们转换为像 PNG, TIFF, JPEGBMP 这样的有效文件格式。Python 本身(没有 PIL 模块)只处理 GIF 文件,因此这些将是自包含应用程序的首选文件格式。JPG 文件非常普遍,因此我们需要证明我们编写的代码可以使用 JPG, GIF, PNGBMP 格式。

我们需要了解的关于图像格式的知识

了解以下关于文件图像格式的内容是有用的:

  • GIF 图像文件是最小且使用和传输速度最快的,它们可能是图像质量和文件大小之间的最佳平衡。缺点是它们颜色范围有限,不适合高质量图片。

  • JPEG 图像是网络上最常见的。质量可以从高到低变化,这取决于你指定的压缩程度。大图像可以被大量压缩,但你会失去图像质量。

  • TIFF 图像体积大,质量/分辨率高。详细的工程图纸通常以 TIFF 文件存档。

  • PNG 图像是 GIF 文件的现代高质量替代品。但 Tkinter 无法识别它们。

  • BMP 图像是不压缩的,有点过时,但仍然有很多。不推荐使用。

    当在 PIL 中处理图像时,PNG 图像是一种方便使用的格式。然而,如果你正在为在多种平台上显示的 Python 程序中的图像做准备,那么在保存之前你需要将它们转换为 GIF 格式。

图像与数字游戏

图像格式就像学习古代语言一样,学得越多,事情就越复杂。但这里有一些基本的数字规则,可以给你一些洞察。

  • GIF 格式最多支持 256 种颜色,但可以使用更少的颜色。

  • PNG 格式最多支持约 14000 种不同的颜色。

  • JPEG 可以处理 1600 万种颜色,与上一章中使用的 #rrggbb 数字相同数量的颜色。

    大多数数码相机的图像都压缩成 JPG 格式,这会减少颜色的范围。因此,在大多数情况下,我们可以将它们转换为 PNG 图像而不会出现明显的质量损失。

以不同的文件格式打开、查看和保存图像

很常见的情况是我们想处理某个图像,但它处于错误的格式。大多数网络图像都是 JPEG.jpg)文件。原生 Python 只能识别 GIF.gif)格式。

准备工作

找到一个 .jpg 图像文件,并将其保存或复制到您为这项成像工作创建的目录中。为了进行这些练习,我们将假设有一个名为 constr 的目录(代表“建筑工地”)。在代码中,您将看到以 /constr/pics1/images-name.ext 形式引用的图像。这意味着 Python 程序期望在名为 constr 的系统目录中找到您请求它打开的文件。您可以将此更改为您认为最适合您在类似艺术家工作室等地方弄乱的地方。检索您的图像文件的路径甚至可以是网址。

因此,在这个例子中,有一个名为 duzi_leo_1.jpg 的图像,一个存储在名为 pics1 的文件夹(目录)中的 JPEG 图像,而 pics1 文件夹又位于 constr 目录中。

如何做...

按照通常的方式执行以下所示的程序。

# images_jpg2png_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/duzi_leo_1.jpg")
im_1.show()
im_1.save('/constr/picsx/duzi_leo_2.png', 'PNG')

它是如何工作的...

按照典型的 Python 风格,Python 的设计者已经尽可能地为程序员简化了事情。在这里发生的事情是,我们创建了一个名为 im_1 的图像对象实例,该对象以 JPEG 格式(扩展名为 .jpg)存在,并命令将其保存为 PNG 格式(扩展名为 .png)。复杂的转换在幕后进行。我们展示图像以确信它已被找到。

最后,我们将其转换为 PNG 格式并保存为 duzi_leo_2.png

还有更多...

我们希望知道我们可以将任何图像格式转换为任何其他格式。不幸的是,图像格式有点像巴别塔现象。由于历史原因、技术演变、专利限制和专有商业霸权,许多图像格式并非旨在公开读取。例如,直到 2004 年,GIF 是专有的。PNG 是作为替代品开发的。下一个示例将展示用于发现您平台上的哪些转换将有效的代码。

JPEG、PNG、TIFF、GIF、BMP 图像格式转换

我们从一个 PNG 格式的图像开始,然后将其保存为以下每种格式:JPG、PNG、GIF、TIFFBMP,并将它们保存在本地硬盘上。然后我们取保存的图像格式,逐一将其转换为其他格式。这样我们就测试了所有可能的转换组合。

准备工作

我们需要将一个 JPG 图像放入 /constr/pics1 文件夹中。提供了一个名为 test_pattern_a.png 的特定 PNG 图像,旨在强调不同格式中的缺陷。

如何做...

按照之前所示执行程序。在命令终端阅读它们的描述性 '元数据'

如何做...

# images_one2another_1.py
#>>>>>>>>>>>>>>>>>>>>>
import Image
# Convert a jpg image to OTHER formats
im_1 = Image.open("/constr/pics1/test_pattern_1.jpg")
im_1.save('/constr/picsx/test_pattern_2.png', 'PNG')
im_1.save('/constr/picsx/test_pattern_3.gif', 'GIF')
im_1.save('/constr/picsx/test_pattern_4.tif', 'TIFF')
im_1.save('/constr/picsx/test_pattern_5.bmp', 'BMP')
# Convert a png image to OTHER formats
im_2 = Image.open("/constr/picsx/test_pattern_2.png")
im_2.save('/constr/picsx/test_pattern_6.jpg', 'JPEG')
im_2.save('/constr/picsx/test_pattern_7.gif', 'GIF')
im_2.save('/constr/picsx/test_pattern_8.tif', 'TIFF')
im_2.save('/constr/picsx/test_pattern_9.bmp', 'BMP')
# Convert a gif image to OTHER formats
# It seems that gif->jpg does not work
im_3 = Image.open("/constr/pics1/test_pattern_3.gif")
#im_3.save('/constr/pics1/test_pattern_10.jpg', 'JPEG')
# "IOError "cannot write mode P as JPEG"
im_3.save('/constr/picsx/test_pattern_11.png', 'PNG')
im_3.save('/constr/picsx/test_pattern_12.tif', 'TIFF')
im_3.save('/constr/picsx/test_pattern_13.bmp', 'BMP')
# Convert a tif image to OTHER formats
im_4 = Image.open("/constr/picsx/test_pattern_4.tif")
im_4.save('/constr/picsx/test_pattern_14.png', 'PNG')
im_4.save('/constr/picsx/test_pattern_15.gif', 'GIF')
im_4.save('/constr/picsx/test_pattern_16.tif', 'TIFF')
im_4.save('/constr/picsx/test_pattern_17.bmp', 'BMP')
# Convert a bmp image to OTHER formats
im_5 = Image.open("/constr/picsx/test_pattern_5.bmp")
im_5.save('/constr/picsx/test_pattern_18.png', 'PNG')
im_5.save('/constr/picsx/test_pattern_19.gif', 'GIF')
im_5.save('/constr/picsx/test_pattern_20.tif', 'TIFF')
im_5.save('/constr/picsx/test_pattern_21.jpg', 'JPEG')

它是如何工作的...

这种转换仅在 PIL 安装的情况下有效。一个例外是,从 GIFJPG 的转换将不会工作。在执行程序之前,预先打开 /constr/pics1 文件夹的内容,并观察图像在执行过程中依次出现,这很有趣。

还有更多...

注意,除了GIF图像外,很难注意到任何图像质量丢失。问题最明显的是,当GIF转换算法必须如图所示在两种相似颜色之间做出选择时。

大小重要吗?

原始的test_pattern_1.jpg是 77 千字节。所有由此派生的图像大小是四到十倍,即使是低质量的GIF图像。原因是只有JPGGIF图像是损失性的,这意味着在转换过程中会丢弃一些图像信息,并且无法恢复。

图像在图像平面上的旋转

我们有一个侧躺的图像,我们需要通过顺时针旋转 90 度来修复它。我们想要存储修复后的图像副本。

准备工作

我们需要将一个PNG图像放入文件夹/constr/pics1中。在以下代码中,我们使用了图像dusi_leo.png。此图像具有突出的红色和黄色成分。

如何做到这一点...

执行之前显示的程序。

# image_rotate_1.py
#>>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/dusi_leo.png")
im_2= im_1.rotate(-90)
im_2.show()
im_2.save("/constr/picsx/dusi_leo_rightway.png")

它是如何工作的...

显示的图像将正确对齐。请注意,我们可以将图像旋转到最小为一度,但不能小于这个数值。还有其他变换可以将图像旋转到 90 度的整数倍。这些在标题为“多重变换”的部分进行了演示。

还有更多...

我们如何创建平滑旋转图像的效果?仅使用 PIL 是无法实现的。PIL 旨在执行图像操作和变换的计算密集型操作。不可能在时间控制的序列中显示一个图像接着另一个图像。为此,你需要 Tkinter,而 Tkinter 只与GIF图像一起工作。

我们会首先创建一系列图像,每个图像比前一个图像稍微旋转一点,并存储每个图像。稍后,我们将运行一个 Python Tkinter 程序,以时间控制的序列显示这些图像系列。这将使旋转动画化。与旋转图像必须放置在具有与原始图像相同大小和方向的框架中的事实相关的问题将在下一章中解决。可以取得一些令人惊讶的有效结果。

图像大小调整

我们将一个大型图像文件(1.8 兆字节)的大小减小到 24.7 千字节。但是图像被扭曲了,因为没有考虑到高度与宽度的比例。

准备工作

就像我们对之前的食谱所做的那样,我们需要将dusi_leo.png图像放入文件夹/constr/pics1中。

如何做到这一点...

要更改图像的大小,我们需要指定最终尺寸,并指定一个过滤器类型,该类型提供填充由调整大小过程产生的任何像素间隙的规则。执行之前显示的程序。以下图像显示了结果。

如何做到这一点...

# image_resize_1.py
#>>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/dusi_leo_1.jpg")
# adjust width and height to desired size
width = 300
height = 300
# NEAREST Filter is compulsory to resize the image
im_2 = im_1.resize((width, height), Image.NEAREST)
im_2.save("/constr/picsx/dusi_leo_2.jpg")

它是如何工作的...

在这里,我们将图片的大小调整为适合 300x300 像素的矩形。如果图像增大,需要添加额外的像素。如果图像减小,则必须丢弃像素。添加哪些特定的像素,它们的颜色将如何确定,必须由调整大小方法中的算法自动决定。PIL 提供了多种方法来完成这项工作。这些像素添加算法作为 过滤器 提供使用。

这正是过滤器设计的目的,在前面的例子中,我们选择使用最近的过滤器。这被记录为使用最近的邻域像素的值。文档有些含糊不清,因为它没有解释将选择哪个最近的像素。在矩形网格中,北、南、东、西的像素距离相等。其他可能的过滤器包括 BILINEAR(在 2x2 环境中的线性插值)、BICUBIC(在 4x4 环境中的三次样条插值)或 ANTIALIAS(高质量的下采样过滤器)。

减小图像也带来了困境。图片元素(像素)需要被丢弃。如果图像中有一个从黑色到白色的锐利边缘,会发生什么?我们是丢弃最后一个黑色像素并用白色像素替换它,还是用介于两者之间的像素替换它?

还有更多...

哪些过滤器最好的问题会因图像类型而异。在这个不断变化的图像处理分支中,需要经验和实验。

如何保持图像的正确宽高比?

如此例所示,除非我们采取措施选择正确的目标图像尺寸比例,否则瘦阿姨 Milly 的照片将变成宽 Winifred,如果照片被展示出来,家庭成员之间可能永远无法和睦相处。因此,我们在下一个菜谱中展示了如何保持比例和礼仪。

正确的比例图像调整大小

我们制作一个缩小尺寸的图像,同时注意保持原始图像的正确宽高比和长宽比。秘诀是使用 Image.size() 函数事先获取确切的图像大小,然后确保我们保持相同的长宽比。

准备工作

正如我们之前的做法,我们需要将 dusi_leo.png 图像放入文件夹 /constr/pics1

如何操作...

执行之前显示的程序。

# image_preserved_aspect_resize_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/dusi_leo_1.jpg")
im_width = im_1.size[0]
im_height = im_1.size[1]
print im_width, im_height
new_size = 0.2 # New image to be reduced to one fifth of original.
# adjust width and height to desired size
width = int(im_width * new_size)
height = int(im_height * new_size)
# Filter is compulsory to resize the image
im_2 = im_1.resize((width, height), Image.NEAREST)
im_2.save("/constr/picsx/dusi_leo_3.jpg")

它是如何工作的...

Image.size() 函数返回两个整数,- 打开图像的宽度,size[0],和高度,size[1]。我们使用缩放乘数 new_size 以相同的比例缩放宽度和高度。

在图像中分离一个颜色带

我们仅隔离图像的绿色部分或颜色带。

准备工作

正如我们之前的做法,我们需要将 dusi_leo.png 图像放入文件夹 /constr/pics1

如何操作...

执行之前显示的程序。

#image_get_green_1.py
#>>>>>>>>>>>>>>>>>>
import ImageEnhance
import Image
red_frac = 1.0
green_frac = 1.0
blue_frac = 1.0
im_1 = Image.open("/a_constr/pics1/dusi_leo_1.jpg")
# split the image into individual bands
source = im_1.split()
R, G, B = 0, 1, 2
# Assign color intensity bands, zero for red and blue.
red_band = source[R].point(lambda i: i * 0.0)
green_band = source[G]
blue_band = source[B].point(lambda i: i * 0.0)
new_source = [red_band, green_band, blue_band]
# Merge (add) the three color bands
im_2 = Image.merge(im_1.mode, new_source)
im_2.show()

它是如何工作的...

Image.split()函数将原始JPG图像中的红色、绿色和蓝色三个颜色波段分离出来。红色波段是source[0],绿色波段是source[1],蓝色波段是[2]JPG图像没有透明度(alpha)波段。PNG图像可以有 alpha 波段。如果这样的PNG图像被split(),其透明度波段将是source[3]。图像中特定像素的颜色量以字节数据记录。您可以通过在分割波段中为每个像素的比例进行类似的调整来改变这个量,例如在red_band = source[R].point(lambda i: i * proportion)这一行中,其中比例是一个介于0.01.0之间的数字。

在这个菜谱中,我们通过将比例量设置为0.0来消除所有红色和蓝色。

还有更多...

在下一个菜谱中,我们以非零比例混合三种颜色。

图像中的红、绿、蓝颜色调整

在这个例子中,我们进一步制作了一个重新混合原始图像颜色的图像,以不同的比例。与之前的例子使用相同的代码布局。

准备中

如前所述,将dusi_leo.png图像放入文件夹/constr/pics1中。

如何做...

执行以下代码。

#image_color_manip_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import ImageEnhance, Image
im_1 = Image.open("/constr/pics1/dusi_leo_smlr_1.jpg")
# Split the image into individual bands
source = im_1.split()
R, G, B = 0, 1, 2
# Select regions where red is less than 100
red_band = source[R]
green_band = source[G]
blue_band = source[B]
# Process the red band: intensify red x 2
out_red = source[R].point(lambda i: i * 2.0)
# Process the green band: weaken by 20%
out_green = source[G].point(lambda i: i * 0.8)
# process the blue band: Eliminate all blue
out_blue = source[B].point(lambda i: i * 0.0)
# Make a new source of color band values
new_source = [out_red, out_green, out_blue]
# Add the three altered bands back together
im_2 = Image.merge(im_1.mode, new_source)
im_2.show()

它是如何工作的...

如前所述,Image.split()函数将原始JPG图像的红色、绿色和蓝色三个颜色波段分离出来。在这种情况下,红色、绿色和蓝色的比例分别是蓝色 200%,20%,和 0%。

还有更多...

在现有图片中调整颜色比例是一种复杂而微妙的艺术,正如我们在前面的章节中所做的那样,在下一个例子中,我们提供了一个使用滑动控制来允许用户通过试错法在波段分离的图像上达到期望颜色混合的菜谱。

滑块控制的颜色调整

我们构建了一个工具,用于在波段分离的图像上获得期望的颜色混合。之前我们使用过的滑动控制是一个方便的设备,用于有意识地调整每个主色波段中颜色的相对比例。

准备中

使用文件夹/constr/pics1中的dusi_leo.png图像。

如何做...

执行以下程序。

#image_color_adjuster_1.py
#>>>>>>>>>>>>>>>>>>>
import ImageEnhance
import Image
import Tkinter
root =Tkinter.Tk()
root.title("Photo Image Color Adjuster")
red_frac = 1.0
green_frac = 1.0
blue_frac = 1.0
slide_value_red = Tkinter.IntVar()
slide_value_green = Tkinter.IntVar()
slide_value_blue = Tkinter.IntVar()
im_1 = Image.open("/constr/pics1/dusi_leo_smlr_1.jpg")
im_1.show()
source = im_1.split() # split the image into individual bands
R, G, B = 0, 1, 2
# Assign color intensity bands
red_band = source[R]
green_band = source[G]
blue_band = source[B]
#===============================================
# Slider and Button event service functions (callbacks)
def callback_button_1():
toolconstructing, for desirable color mix# Adjust red intensity by slider value.
out_red = source[R].point(lambda i: i * red_frac)
out_green = source[G].point(lambda i: i * green_frac) # Adjust # green
out_blue = source[B].point(lambda i: i * blue_frac) # Adjust # blue
new_source = [out_red, out_green, out_blue]
im_2 = Image.merge(im_1.mode, new_source) # Re-combine bands
im_2.show()
button_1= Tkinter.Button(root,bg= "sky blue", text= "Display adjusted image \
(delete previous one)", command=callback_ \button_1)
button_1.grid(row=1, column=2, columnspan=3)
def callback_red(*args):
global red_frac
red_frac = slide_value_red.get()/100.0
def callback_green(*args):
global green_frac
green_frac = slide_value_green.get()/100.0
def callback_blue(*args):
global blue_frac
blue_frac = slide_value_blue.get()/100.0
slide_value_red.trace_variable("w", callback_red)
slide_value_green.trace_variable("w", callback_green)
slide_value_blue.trace_variable("w", callback_blue)
slider_red = Tkinter.Scale(root,
length = 400,
fg = 'red',
activebackground = "tomato",
background = "grey",
troughcolor = "red",
label = "RED",
from_ = 0,
to = 200,
resolution = 1,
variable = slide_value_red,
orient = 'vertical')
slider_red.grid(row=0, column=2)
slider_green = Tkinter.Scale(root,
length = 400,
fg = 'dark green',
activebackground = "green yellow",
background = "grey",
troughcolor = "green",
label = "GREEN",
from_ = 0,
to = 200,
toolconstructing, for desirable color mixresolution = 1,
variable = slide_value_green,
orient = 'vertical')
slider_green.grid(row=0, column=3)
slider_blue = Tkinter.Scale(root,
length = 400,
fg = 'blue',
activebackground = "turquoise",
background = "grey",
troughcolor = "blue",
label = "BLUE",
from_ = 0,
to = 200,
resolution = 1,
variable = slide_value_blue,
orient = 'vertical')
slider_blue.grid(row=0, column=4)
root.mainloop()
#===============================================

它是如何工作的...

使用鼠标控制的滑块位置,我们调整每个红色、绿色和蓝色通道中的颜色强度。调整的幅度从零到 200,但在回调函数中缩放到百分比值。

source = im_1.split()中,split()方法将图像分割成红色、绿色和蓝色波段。point(lambda i: i * intensity)方法将每个波段中每个像素的颜色值乘以一个intensity值,而merge(im_1.mode, new_source)方法将结果波段重新组合成一个新的图像。

在这个例子中,我们使用了 PIL 和 Tkinter 一起。

如果你使用from Tkinter import *,你似乎会得到命名空间混淆:

解释器说:

" im_1 = Image.open("/a_constr/pics1/redcar.jpg")

AttributeError: class Image has no attribute 'open' "

但如果你只是说import Tkinter,这似乎是可行的。

但当然现在你必须将 Tkinter 的所有方法前缀为 Tkinter。

通过混合组合图像

混合两个图像的效果就像从两个不同的投影仪将两个透明的幻灯片图像投射到投影仪屏幕上,每个投影仪的光量由比例设置控制。命令的形式是Image.blend(image_1, image_2, proportion-of-image_1)

准备工作

使用来自文件夹/constr/pics1的两个图像100_canary.png100_cockcrow.png。标题中的100_是一个提醒,表示这些图像的大小是 100 x 100 像素,我们将在控制台上看到每个图像的格式、大小和类型。

如何操作...

在放置好两个大小和模式相同的图像后,执行以下代码。

# image_blend_1.py
# >>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/100_canary.png") # mode is RGBA
im_2 = Image.open("/constr/pics1/100_cockcrow.png") # mode is RGB
# Check on mode, size and format first for compatibility.
print "im_1 format:", im_1.format, ";size:", im_1.size, "; mode:",im_1.mode
print "im_2 format:", im_2.format, ";size:", im_2.size, "; mode:",im_2.mode
im_2 = im_2.convert("RGBA") # Make both modes the same
im_4 = Image.blend(im_1, im_2, 0.5)
im_4.show()

更多...

从格式信息中,我们可以看到第一幅图像的模式是RGBA,而第二幅是RGB。因此,首先需要将第二幅图像转换为RGBA

在这个特定的例子中,比例控制被设置为0.5。这意味着两幅图像以相等的量混合在一起。如果比例设置是0.2,那么im_120%将与im_280%结合。

更多信息部分 1

另一种组合图像的方法是使用第三幅图像作为遮罩来控制遮罩确定的结果图像中每个图像的形状和比例的位置。

通过改变百分比混合图像

我们以不同的透明度混合两个图像。

如何操作...

在放置好两个大小和模式相同的图像后,执行以下代码。

# image_blend_2.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/lion_ramp_2.png")
im_2 = Image.open("/constr/pics1/fiery_2.png")
# Various degrees of alpha
im_3 = Image.blend(im_1, im_2, 0.05) # 95% im_1, 5% im_2
im_4 = Image.blend(im_1, im_2, 0.2)
im_5 = Image.blend(im_1, im_2, 0.5)
im_6 = Image.blend(im_1, im_2, 0.6)
im_7 = Image.blend(im_1, im_2, 0.8)
im_8 = Image.blend(im_1, im_2, 0.95) # 5% im_1, 95% im_2
im_3.save("/constr/picsx/fiery_lion_1.png")
im_4.save("/constr/picsx/fiery_lion_2.png")
im_5.save("/constr/picsx/fiery_lion_3.png")
im_6.save("/constr/picsx/fiery_lion_4.png")
im_7.save("/constr/picsx/fiery_lion_5.png")
im_8.save("/constr/picsx/fiery_lion_6.png")

它是如何工作的...

通过改变图像混合时使用的 alpha 值,我们可以控制每个图像在结果中占主导的程度。

更多...

这种在每个电影帧上执行的过程是常用于从一个场景淡出到另一个场景的效果。

使用遮罩图像制作合成图像

在这里,我们使用函数Image.composite(image_1, image_2, mask_image)来控制两个图像的组合。

准备工作

使用来自文件夹/constr/pics1的图像100_canary.png, 100_cockcrow.png100_sun_1.png。标题中的100_是一个提醒,表示这些图像的大小是 100 x 100 像素,我们将在控制台上看到每个图像的格式、大小和类型。

如何操作...

在放置好三个大小和模式相同的图像后,执行以下代码。

# image_composite_1.py
# >>>>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/constr/pics1/100_canary.png") # mode is RGBA
im_2 = Image.open("/constr/pics1/100_cockcrow.png") # mode is RGB
im_3 = Image.open("/constr/pics1/100_sun_1.png")
# Check on mode, size and format first for compatibility.
print "im_1 format:", im_1.format, ";size:", im_1.size, "; mode:", \im_1.mode
print "im_2 format:", im_2.format, ";size:", im_2.size, "; mode:", \im_2.mode
print "im_3 format:", im_3.format, ";size:", im_3.size, "; mode:", \im_3.mode
im_2 = im_2.convert("RGBA")
im_3 = im_3.convert("L")
im_4 = Image.composite(im_1, im_2, im_3)
im_4.show()

它是如何工作的...

从格式信息中,我们可以看到第一幅图像的模式是RGBA,而第二幅是RGB。因此,首先需要将第二幅图像转换为RGBA

遮罩图像必须是1, LRGBA格式,并且大小相同。在这个菜谱中,我们将其转换为模式L,这是一个 256 级的灰度图像。遮罩中每个像素的值用于乘以源图像。如果某个特定位置的像素值为56,那么image_1将被乘以256 * 56 = 200,而image_2将被乘以56

还有更多...

还有其他效果,如Image.eval(function, Image),其中每个像素都乘以该函数,并且我们可以将函数转换为一些复杂的代数表达式。如果图像有多个波段,则函数应用于每个波段。

另一种效果是Image.merge(mode, bandList),它从多个相同大小的单波段图像创建多波段图像。我们指定新图像的所需模式。bandList 指定器是要组合的单波段图像组件的序列。

参见

通过组合之前显示的图像操作,可以实现无数的效果。我们将深入到图像和信号处理的世界,这可能会变得极其复杂和精细。某些效果已经相当标准化,可以在图像处理应用程序(如GIMPPhotoshop)提供的过滤选项列表中看到。

水平和垂直偏移(滚动)图像

我们在这里看到如何滚动图像。也就是说,将其向右或向左移动而不丢失任何东西——图像实际上就像边缘连接一样滚动。同样的过程在垂直方向上也会起作用。

准备工作

使用文件夹/constr/pics1中的image_canary_a.jpg

如何操作...

执行以下程序,注意我们需要导入 PIL 中的一个模块,名为ImageChopsChops代表通道操作

# image_offset_1.py
# >>>>>>>>>>>>>>>
import Image
import ImageChops
im_1 = Image.open("/constr/pics1/canary_a.jpg")
# adjust width and height to desired size
dx = 200
dy = 300
im_2 = ImageChops.offset(im_1, dx, dy)
im_2.save("/constr/picsx/canary_2.jpg")

它是如何工作的...

约翰·希普曼(John Shipman)于 2009 年中旬撰写的优秀指南《Python Imaging Library》没有提及 ImageChops。

水平、垂直翻转和旋转

在这里,我们查看一组快速且易于操作的变换,这些操作在照片查看器中很典型。

准备工作

使用文件夹/constr/pics1中的image_dusi_leo_1.jpg

如何操作...

执行以下程序,并在/constr/picsx中查看结果。

# image_flips_1.py
#>>>>>>>>>>>>>>
import Image
im_1 = Image.open("/a_constr/pics1/dusi_leo_1.jpg")
im_out_1 = im_1.transpose(Image.FLIP_LEFT_RIGHT)
im_out_2 = im_1.transpose(Image.FLIP_TOP_BOTTOM)
im_out_3 = im_1.transpose(Image.ROTATE_90)
im_out_4 = im_1.transpose(Image.ROTATE_180)
im_out_5 = im_1.transpose(Image.ROTATE_270)
im_out_1.save("/a_constr/picsx/dusi_leo_horizontal.jpg")
im_out_1.save("/a_constr/picsx/dusi_leo_vertical.jpg")
im_out_1.save("/a_constr/picsx/dusi_leo_90.jpg")
im_out_1.save("/a_constr/picsx/dusi_leo_180.jpg")
im_out_1.save("/a_constr/picsx/duzi_leo_270.jpg")

它是如何工作的...

前面的命令是自我解释的。

过滤效果:模糊、锐化、对比度等

PIL 有一个ImageFilter模块,它包含了一系列有用的过滤器,可以增强图像的某些特性,例如锐化模糊的特征。以下菜谱中演示了其中的十个过滤器。

准备工作

使用文件夹 /constr/pics1 中的图像 russian_doll.png。为结果过滤图像创建一个文件夹 /constr/picsx。使用单独的文件夹来存储结果有助于防止文件夹 pics1 因冗余图像和工作中的图像而变得拥挤。每次执行后,我们可以删除 picsx 的内容,而不用担心会丢失菜谱的源图像。

如何操作...

在执行以下代码之前,请在您的屏幕上打开文件夹 /constr/picsx,并在执行完成后观察图像的出现。源图像被选择为模糊的俄罗斯娃娃和清晰的背景,因为这允许我们轻松地区分不同过滤器的影响。

# image_pileof_filters_1.py
# >>>>>>>>>>>>>>>>>>>
import ImageFilter
im_1 = Image.open("/constr/pics1/russian_doll.png")
im_2 = im_1.filter(ImageFilter.BLUR)
im_3 = im_1.filter(ImageFilter.CONTOUR)
im_4 = im_1.filter(ImageFilter.DETAIL)
im_5 = im_1.filter(ImageFilter.EDGE_ENHANCE)
im_6 = im_1.filter(ImageFilter.EDGE_ENHANCE_MORE)
im_7 = im_1.filter(ImageFilter.EMBOSS)
im_8 = im_1.filter(ImageFilter.FIND_EDGES)
im_9 = im_1.filter(ImageFilter.SMOOTH)
im_10 = im_1.filter(ImageFilter.SMOOTH_MORE)
im_11 = im_1.filter(ImageFilter.SHARPEN)
im_2.save("/constr/picsx/russian_doll_BLUR.png")
im_3.save("/constr/picsx/ russian_doll_CONTOUR.png")
im_4.save("/constr/picsx/ russian_doll_DETAIL.png")
im_5.save("/constr/picsx/ russian_doll_EDGE_ENHANCE.png")
im_6.save("/constr/picsx/ russian_doll_EDGE_ENHANCE_MORE.png")
im_7.save("/constr/picsx/ russian_doll_EMBOSS.png")
im_8.save("/constr/picsx/ russian_doll_FIND_EDGES.png")
im_9.save("/constr/picsx/ russian_doll_SMOOTH.png")
im_10.save("/constr/picsx/ russian_doll_SMOOTH_MORE.png")
im_11.save("/constr/picsx/ russian_doll_SHARPEN.png")

它是如何工作的...

这个菜谱表明,最佳的过滤结果高度依赖于我们希望增强或抑制的图像特征,以及正在处理的单个图像的一些微妙特征。在下面这个例子中,EDGE_ENHANCE 过滤器特别有效于对抗娃娃的模糊焦点。与 SHARPEN 过滤器相比,它提高了色彩对比度。调整大小并粘贴以进行合成旋转

我们想要创建一个动画,使图像看起来在图片中间围绕一个垂直轴旋转。在这个菜谱中,我们看到动画的基本图像序列是如何准备的。

我们想要制作一系列图像,这些图像逐渐变窄,就像它们是一张逐渐旋转的板上的海报。然后,我们想要将这些窄图像粘贴在标准尺寸黑色背景的中间。如果这个序列以时间控制的帧序列显示,我们会看到图像似乎围绕一个中心垂直轴旋转。

准备工作

我们在目录 /constr/pics1 中使用图像 100_canary.png,并将结果放置在 /constr/picsx 中,以避免我们的源文件夹 /constr/pics1 过于杂乱。

如何操作...

再次在执行以下代码之前,在您的屏幕上打开文件夹 /constr/picsx,并在执行完成后观察图像的出现。这不是必需的,但观看结果在眼前形成是非常有趣的。

# image_rotate_resize_1.py
# >>>>>>>>>>>>>>>>>>>>
import Image
import math
THETADEG = 5.0 # degrees
THETARAD = math.radians(THETADEG)
im_1 = Image.open("/constr/pics1/blank.png")
im_seed = Image.open("/constr/pics1/100_canary.png") # THE SEED IMAGE
im_seq_name = "canary"
#GET IMAGE WIDTH AND HEIGHT - not done here
# For the time being assume the image is 100 x 100
width = 100
height = 100
num_images = int(math.pi/(2*THETARAD))
Q = []
for j in range(0,2*num_images + 1):
Q.append(j)
for i in range(0, num_images):
new_size = width * math.cos(i*THETARAD) # Width for reduced # image
im_temp = im_seed.resize((new_size, height), Image.NEAREST)
im_width = im_temp.size[0] # Get the width of the reduced image
x_start = 50 -im_width/2 # Centralize new image in a 100x100 # square.
im_1.paste(im_temp,( x_start,10)) # Paste: This creates the # annoying ghosting.
stri = str(i)
# Save the reduced image
Q[i] = "/constr/picsx/" + im_seq_name + stri + ".gif"
im_1.save(Q[i])
# Flip horizontally and save the reduced image.
im_transpose = im_temp.transpose(Image.FLIP_LEFT_RIGHT)
im_1.paste(im_transpose,( x_start,10))
strj = str(2 * num_images - i)
Q[ 2 * num_images - i ] = "/constr/picsx/" + im_seq_name + strj \ + ".gif"
im_1.save(Q[ 2 * num_images - i ])

它是如何工作的...

为了模仿旋转的效果,我们将每个图像的宽度减少到 cosine(new_angle),其中 new_angle 对于每个图像增加 5 度的旋转。然后我们把这个缩小的图像粘贴到一个空白黑色正方形上。最后,我们以系统的方式给序列中的每张图片命名,例如 canary0.gif, canary1.gif,依此类推,直到最后一张图片命名为 canary36.gif

更多内容...

这个例子展示了 Python 图像库非常适合的任务类型——当你需要反复对一个图像或一组图像执行受控的转换时。这些图像可能是电影胶片的帧。如淡入淡出、缩放、颜色变换、锐化、模糊等效果是显而易见的,可以用到的,但你的程序员的想象力还能想出许多其他的效果。

第七章. 合并光栅和矢量图片

在本章中,我们将涵盖:

  • GIF 沙滩球的简单动画

  • 向量行走的生物

  • 在卡拉罗行走穿着鞋的鸟

  • 使用 Gimp 制作部分透明的图像

  • 在宫殿里行走的外交官

  • 森林中的蜘蛛

  • 移动的图像带

  • 连续的图像带

  • 无尽的背景和飘过的云景

如第二章、绘制基本形状和第三章、处理文本中看到的矢量图形,可以通过简单的代数缩小和放大到任何大小和任何方向。它们可以通过基本的三角函数进行旋转动画。光栅图形是有限的。在代码执行时,它们不能动态地调整大小或旋转。它们更繁琐。然而,当我们结合矢量图形和光栅图形时,我们可以得到惊人的效果。Python 不能自己旋转GIF图像。有方法可以合理地模拟旋转,但在尝试了一些这些食谱之后,你会欣赏到这些限制。PIL 可以旋转它们,但不能在 Tkinter 画布上动态旋转。我们在这里探索一些可能性和解决方案。

因为我们在不改变和操作图像的实际属性,所以我们不需要在本章中使用 Python Imaging Library (PIL)。我们需要专门使用GIF格式的图像,因为 Tkinter 就是处理这种格式的。

我们还将看到如何使用"The GIMP"作为工具来准备适合动画的图像。

GIF 沙滩球的简单动画

我们想要动画化一个光栅图像,该图像是从照片中提取的。

为了保持简单和清晰,我们只是将沙滩球的摄影图像(GIF格式)在黑色背景上移动。

准备中

我们需要一个合适的GIF图像,这是我们想要动画化的对象。一个例子,命名为beachball.gif,已经提供。

如何做...

从某处复制一个.gif文件并将其粘贴到你想要保存工作进度图片的目录中。

确保我们计算机文件系统中的路径指向要使用的图像。在下面的例子中,指令ball = PhotoImage(file = "constr/pics2/beachball.gif")表示要使用的图像将在名为pics2的目录(文件夹)中找到,它是另一个名为constr的文件夹的子文件夹。

然后执行以下代码。

# photoimage_animation_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Animating a Photo Beachball")
cycle_period = 100
cw = 320 # canvas width
ch = 120 # canvas height
canvas_1 = Canvas(root, width=cw, height=ch, bg="black")
canvas_1.grid(row=0, column=1)
posn_x = 10
posn_y = 10
shift_x = 2
shift_y = 1
ball = PhotoImage(file = "/constr/pics2/beachball.gif")
for i in range(1,100): # end the program after 100 position # shifts.
posn_x += shift_x
posn_y += shift_y
canvas_1.create_image(posn_x,posn_y,anchor=NW, image=ball)
canvas_1.update() # This refreshes the drawing on the # canvas.
canvas_1.after(cycle_period) # This makes execution pause for # 100 milliseconds.
canvas_1.delete(ALL) # This erases everything on the # canvas.
root.mainloop()

它是如何工作的...

沙滩球的图像在画布上移动的方式与第四章、动画原理中使用的方式完全相同。现在的不同之处在于,照片类型的图像总是占据屏幕的矩形区域。这个框的大小,称为边界框,是图像的大小。我们使用了黑色背景,所以沙滩球图像上的黑色角落看不见。

向量行走的生物

我们使用 第二章 的矢量图形和 第四章 的 动画原理,处理文本,制作一对行走腿。我们想使用这些腿与一些光栅图像的片段一起,看看我们能在制作吸引人的动画方面走多远。我们导入 Tkinter、math 和 time 模块。数学是必要的,以提供维持腿部各部分之间几何关系的三角学。

矢量行走生物

准备工作

我们将使用 Tkinter 和时间模块,就像在 第四章 中所做的那样,再次用于动画线条和圆的运动。你将在代码中看到一些三角学。如果你不喜欢数学,你可以直接复制粘贴代码,无需理解数学是如何工作的。然而,如果你是数学的朋友,观看正弦、余弦和正切一起工作,让一个孩子微笑是很有趣的。

如何做到这一点...

按照前一个图像所示执行程序。

# walking_creature_1.py
# >>>>>>>>>>>>>>>>
from Tkinter import *
import math
import time
root = Tk()
root.title("The thing that Strides")
cw = 400 # canvas width
ch = 100 # canvas height
#GRAVITY = 4
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 100 # time between new positions of the ball # (milliseconds).
base_x = 20
base_y = 100
hip_h = 40
thy = 20
#===============================================
# Hip positions: Nhip = 2 x Nstep, the number of steps per foot per # stride.
hip_x = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 60, 60] #15
hip_y = [0, 8, 12, 16, 12, 8, 0, 0, 0, 8, 12, 16, 12, 8, 0] #15
step_x = [0, 10, 20, 30, 40, 50, 60, 60] # 8 = Nhip
step_y = [0, 35, 45, 50, 43, 32, 10, 0]
# The merging of the separate x and y lists into a single sequence.
#==================================
# Given a line joining two points xy0 and xy1, the base of an # isosceles triangle,
# as well as the length of one side, "thy" . This returns the # coordinates of the apex joining the equal-length sides.
def kneePosition(x0, y0, x1, y1, thy):
theta_1 = math.atan2((y1 - y0), (x1 - x0))
L1 = math.sqrt( (y1 - y0)**2 + (x1 - x0)**2)
if L1/2 < thy:
# The sign of alpha determines which way the knees bend.
alpha = -math.acos(L1/(2*thy)) # Avian
#alpha = math.acos(L1/(2*thy)) # Mammalian
else:
alpha = 0.0
theta_2 = alpha + theta_1
x_knee = x0 + thy * math.cos(theta_2)
y_knee = y0 + thy * math.sin(theta_2)
return x_knee, y_knee
def animdelay():
vector imagespair of walking legs, creatingchart_1.update() # This refreshes the drawing on the # canvas.
chart_1.after(cycle_period) # This makes execution pause for # 100 milliseconds.
chart_1.delete(ALL) # This erases *almost* everything on # the canvas.
# Does not delete the text from # inside a function.
bx_stay = base_x
by_stay = base_y
for j in range(0,11): # Number of steps to be taken - arbitrary.
astep_x = 60*j
bstep_x = astep_x + 30
cstep_x = 60*j + 15
aa = len(step_x) -1
for k in range(0,len(hip_x)-1):
# Motion of the hips in a stride of each foot.
cx0 = base_x + cstep_x + hip_x[k]
cy0 = base_y - hip_h - hip_y[k]
cx1 = base_x + cstep_x + hip_x[k+1]
cy1 = base_y - hip_h - hip_y[k+1]
chart_1.create_line(cx0, cy0 ,cx1 ,cy1)
chart_1.create_oval(cx1-10 ,cy1-10 ,cx1+10 ,cy1+10, \ fill="orange")
if k >= 0 and k <= len(step_x)-2:
# Trajectory of the right foot.
ax0 = base_x + astep_x + step_x[k]
ax1 = base_x + astep_x + step_x[k+1]
ay0 = base_y - step_y[k]
ay1 = base_y - step_y[k+1]
ax_stay = ax1
ay_stay = ay1
if k >= len(step_x)-1 and k <= 2*len(step_x)-2:
# Trajectory of the left foot.
bx0 = base_x + bstep_x + step_x[k-aa]
bx1 = base_x + bstep_x + step_x[k-aa+1]
by0 = base_y - step_y[k-aa]
by1 = base_y - step_y[k-aa+1]
bx_stay = bx1
by_stay = by1
aknee_xy = kneePosition(ax_stay, ay_stay, cx1, cy1, thy)
chart_1.create_line(ax_stay, ay_stay ,aknee_xy[0], \ aknee_xy[1], width = 3, fill="orange")
chart_1.create_line(cx1, cy1 ,aknee_xy[0], aknee_xy[1], \ width = 3, fill="orange")
chart_1.create_oval(ax_stay-5 ,ay1-5 ,ax1+5 ,ay1+5, \ fill="green")
chart_1.create_oval(bx_stay-5 ,by_stay-5 ,bx_stay+5 , \ by_stay+5, fill="blue")
bknee_xy = kneePosition(bx_stay, by_stay, cx1, cy1, thy)
chart_1.create_line(bx_stay, by_stay ,bknee_xy[0], \ bknee_xy[1], width = 3, fill="pink")
chart_1.create_line(cx1, cy1 ,bknee_xy[0], bknee_xy[1], \ width = 3, fill="pink")
animdelay()
root.mainloop()

它是如何工作的...

不深入细节,程序中的策略包括在走一步时定义一只脚的运动。这种运动由两个列表 step_x(水平)step_y(垂直) 给出的八个相对位置定义。臀部运动由一对单独的 x 和 y 位置 hip_xhip_y 给出。

使用三角学来计算膝盖的位置,假设大腿和小腿的长度相同。计算基于高中教授的正弦法则。是的,我们在学校确实学到了有用的东西!

时间动画调节 指令组装成一个函数 animdelay()

还有更多...

在 Python 的 math 模块中,有两个反正切函数可用于根据两个相邻边的长度计算角度。atan2(y,x) 是最好的,因为它处理了正切在圆周上运行时所做的疯狂事情——当它通过 90 度及其倍数时,正切从负无穷大跳到正无穷大。

一个数学膝盖很乐意向前或向后弯曲以满足其方程。我们使角度的符号为负,以使向后弯曲的鸟膝盖为正,以使向前弯曲的哺乳动物膝盖为正。

更多信息部分 1

这个动态的行走臀部和大腿动作被用于以下食谱中,制作沙漠中的鸟、宫殿地面的外交官和森林中的蜘蛛行走。

在 Karroo 走路的穿鞋鸟

我们现在协调四个 GIF 图像和行走腿的动作,制作一只会走的 Apteryx(一种像几维鸟一样的不会飞的鸟)。

在 Karroo 走路的穿鞋鸟

准备工作

我们需要以下 GIF 图像:

  • 一幅合适的风景背景图

  • 一只没有腿的鸟身体

  • 一双鲜艳颜色的鞋子,让人微笑

  • 之前食谱中的行走鸟类腿

使用的图像是karroo.gif, apteryx1.gifshoe1.gif。请注意,鸟和鞋的图像具有透明背景,这意味着在鸟或鞋周围看不到矩形背景。在接下来的食谱中,我们将看到实现所需透明度的最简单方法。

如何操作...

以通常的方式执行程序。

# walking_birdy_1.py
# >>>>>>>>>>>>>>>>
from Tkinter import *
import math
import time
root = Tk()
root.title("A Walking birdy gif and shoes images")
cw = 800 # canvas width
ch = 200 # canvas height
#GRAVITY = 4
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 80 # time between new positions of the bird # (milliseconds).
im_backdrop = "/constr/pics1/karoo.gif"
im_bird = "/constr/pics1/apteryx1.gif"
im_shoe = "/constr/pics1/shoe1.gif"
birdy =PhotoImage(file= im_bird)
shoey =PhotoImage(file= im_shoe)
backdrop = PhotoImage(file= im_backdrop)
chart_1.create_image(0 ,0 ,anchor=NW, image=backdrop)
base_x = 20
base_y = 190
hip_h = 70
thy = 60
#==========================================
# Hip positions: Nhip = 2 x Nstep, the number of steps per foot per # stride.
hip_x = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 60, 60] #15
hip_y = [0, 8, 12, 16, 12, 8, 0, 0, 0, 8, 12, 16, 12, 8, 0] #15
step_x = [0, 10, 20, 30, 40, 50, 60, 60] # 8 = Nhip
step_y = [0, 35, 45, 50, 43, 32, 10, 0]
#=============================================
# Given a line joining two points xy0 and xy1, the base of an # isosceles triangle,
# as well as the length of one side, "thy" this returns the # coordinates of
# the apex joining the equal-length sides.
def kneePosition(x0, y0, x1, y1, thy):
theta_1 = math.atan2(-(y1 - y0), (x1 - x0))
L1 = math.sqrt( (y1 - y0)**2 + (x1 - x0)**2)
alpha = math.atan2(hip_h,L1)
theta_2 = -(theta_1 - alpha)
x_knee = x0 + thy * math.cos(theta_2)
y_knee = y0 + thy * math.sin(theta_2)
return x_knee, y_knee
def animdelay():
Apteryx imageanimatingchart_1.update() # Refresh the drawing on the canvas.
chart_1.after(cycle_period) # Pause execution pause for 80 # milliseconds.
chart_1.delete("walking") # Erases everything on the canvas.
bx_stay = base_x
by_stay = base_y
for j in range(0,13): # Number of steps to be taken - arbitrary.
astep_x = 60*j
bstep_x = astep_x + 30
cstep_x = 60*j + 15
aa = len(step_x) -1
for k in range(0,len(hip_x)-1):
# Motion of the hips in a stride of each foot.
cx0 = base_x + cstep_x + hip_x[k]
cy0 = base_y - hip_h - hip_y[k]
cx1 = base_x + cstep_x + hip_x[k+1]
cy1 = base_y - hip_h - hip_y[k+1]
#chart_1.create_image(cx1-55 ,cy1+20 ,anchor=SW, \ image=birdy, tag="walking")
if k >= 0 and k <= len(step_x)-2:
# Trajectory of the right foot.
ax0 = base_x + astep_x + step_x[k]
ax1 = base_x + astep_x + step_x[k+1]
ay0 = base_y - 10 - step_y[k]
ay1 = base_y - 10 -step_y[k+1]
ax_stay = ax1
ay_stay = ay1
if k >= len(step_x)-1 and k <= 2*len(step_x)-2:
# Trajectory of the left foot.
bx0 = base_x + bstep_x + step_x[k-aa]
bx1 = base_x + bstep_x + step_x[k-aa+1]
by0 = base_y - 10 - step_y[k-aa]
by1 = base_y - 10 - step_y[k-aa+1]
bx_stay = bx1
by_stay = by1
chart_1.create_image(ax_stay-5 ,ay_stay + 10 ,anchor=SW, \ image=shoey, tag="walking")
chart_1.create_image(bx_stay-5 ,by_stay + 10 ,anchor=SW, \ image=shoey, tag="walking")
aknee_xy = kneePosition(ax_stay, ay_stay, cx1, cy1, thy)
chart_1.create_line(ax_stay, ay_stay-15 ,aknee_xy[0], \ aknee_xy[1], width = 5, fill="orange", tag="walking")
chart_1.create_line(cx1, cy1 ,aknee_xy[0], aknee_xy[1], \ width = 5, fill="orange", tag="walking")
Apteryx imageanimatingbknee_xy = kneePosition(bx_stay, by_stay, cx1, cy1, thy)
chart_1.create_line(bx_stay, by_stay-15 ,bknee_xy[0], \ bknee_xy[1], width = 5, fill="pink", tag="walking")
chart_1.create_line(cx1, cy1 ,bknee_xy[0], bknee_xy[1], \ width = 5, fill="pink", tag="walking")
chart_1.create_image(cx1-55 ,cy1+20 ,anchor=SW, image=birdy, \ tag="walking")
animdelay()
root.mainloop()
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

之前食谱中对三角学所做的评论也适用于这里。我们现在看到的是,一旦准备好了合适的GIF图像,矢量对象和位图图像可以很容易地结合起来。

还有更多...

对于想要在电脑上制作课程的教师及其学生,这些技术提供了各种可能性,如历史之旅和重演、地理之旅,以及科学实验。让学生们做项目讲述故事。动画年鉴?

使用 GIMP 制作具有透明背景的 GIF 图像

我们使用免费开源的 GIMP 图像编辑器将具有不透明背景的图像转换为具有透明背景的图像。

准备工作

我们可以在www.gimp.org/获取GIMPGNU 图像处理程序)。有适用于 Windows 和 Linux 的版本。GIMP 是一个优秀的软件包,值得学习如何使用。如果你不习惯它,可能会感到有些挫败,所以这个特定的食谱专门描述了将具有不透明背景的.png图像转换为具有透明背景的.gif图像的步骤。

在 Windows 系统中,你只需访问网站并点击下载按钮,它就会自动安装并立即可以使用。在 Linux 系统中,它通常已经预装了。对于基于 Debian 的 Linux 系统,使用sudo apt-get install gimp命令应该可以安装它,然后你就可以开始使用了。

如何操作...

这个食谱不涉及运行 Python 代码。相反,它是一系列在 GIMP GUI 上使用鼠标执行的操作。在以下说明中,点击选择 | 反转是“左键点击,选择,然后左键点击反转”的简称。

  1. 打开 GIMP 并打开文件apteryx1.png。这是一只已经画好的卡通鸟。如何操作...

  2. 点击窗口 | 可停靠对话框 | 图层。这将打开一个显示面板,显示我们正在工作的图像的所有图层。观察图层的变化是使用 GIMP 的秘诀。

  3. 点击选择 | 按颜色,然后将光标箭头放在图像的黑色部分上并点击。你将看到围绕鸟轮廓的闪烁虚线。我们所做的是仅选择图片中的黑色部分进行修改。如何操作...

  4. 点击选择 | 反转。这样做是将选择更改为除了黑色部分之外的所有内容。

  5. 点击编辑 | 复制。这会选取所选部分的副本(除了黑色之外的所有内容)并将其放置在不可见的剪贴板上。

  6. 点击编辑 | 粘贴。这将从剪贴板复制内容,并可能将其粘贴到我们现有的图像上。但在你完成下一步之前,粘贴的图像将处于一种无人地带。

  7. 点击图层 | 新建。这会将图像粘贴部分牢固地放置在其自己的单独图层上。图层就像带有复合图像部分的透明玻璃片。当你对它们进行操作并更改一个图层时,其他图层保持不变。如何操作...

  8. 右键单击如图所示的背景图层,然后点击删除图层。这将丢弃由原始图像组成的背景图层。你会看到只剩下一个图层。它包含放置在透明背景上的鸟的图像。如何操作...

  9. 点击文件 | 另存为。在保存窗口中,输入apteryx1.gif作为文件名。如何操作...

  10. 关闭 GIMP。你将在你发送到的任何文件夹中找到带有透明背景的新GIF图像。在 Linux 系统中,透明区域显示为灰色棋盘图案。

它是如何工作的...

本章中使用的所有具有透明区域的图像都是用这种方式准备的。还有其他方法可以实现这一点,但这可能是最易得的。本章中的动画由一个较小的、部分透明的图像在较大的不透明图像上移动组成。

外交官在宫殿中行走

我们现在使用之前相同的腿,并适当着色,来动画化一个庄重的人。对于人类风格的行走,我们需要选择在代码中预先选择的正确的哺乳动物膝盖弯曲角度选项。

外交官在宫殿中行走

准备工作

我们需要以下GIF图像:

  • 一个合适的风景背景图片

  • 一个没有腿的人类身体

  • 一双庄重的鞋子以彰显尊严

  • 行走的哺乳动物腿

使用的图像是palace.gif, ambassador.gifambassador_shoe1.gif。和之前一样,人和鞋的图像都有透明的背景。

如何操作...

执行之前显示的程序。

# walking_toff_1.py
# >>>>>>>>>>>>>>>>>
from Tkinter import *
import math
import time
root = Tk()
root.title("A Walking Toff in Natural Habitat - gif images")
cw = 800 # canvas width
ch = 200 # canvas height
#GRAVITY = 4
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 120 # time between new positions of the man # (milliseconds).
im_backdrop = "/constr/pics1/toff_bg.gif"
im_toff = "/constr/pics1/ambassador.gif"
im_shoe = "/constr/pics1/toff_shoe.gif"
toff =PhotoImage(file= im_toff)
shoey =PhotoImage(file= im_shoe)
backdrop = PhotoImage(file= im_backdrop)
chart_1.create_image(0 ,0 ,anchor=NW, image=backdrop)
base_x = 20
base_y = 190
hip_h = 60
thy = 25
#============================================
# Hip positions: Nhip = 2 x Nstep, the number of steps per foot per # stride.
hip_x = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 60, 60] #15
hip_y = [0, 4, 6, 8, 6, 4, 0, 0, 0, 4, 6, 8, 6, 4, 0] #15
step_x = [0, 10, 20, 30, 40, 50, 60, 60] # 8 = Nhip
step_y = [0, 15, 25, 30, 25, 22, 10, 0]
#============================================
# Given a line joining two points xy0 and xy1, the base of an # isosceles triangle,
# as well as the length of one side, "thy" this returns the # coordinates of
# the apex joining the equal-length sides.
def kneePosition(x0, y0, x1, y1, thy):
theta_1 = math.atan2((y1 - y0), (x1 - x0))
L1 = math.sqrt( (y1 - y0)**2 + (x1 - x0)**2)
if L1/2 < thy:
alpha = math.acos(L1/(2*thy))
else:
alpha = 0.0
theta_2 = alpha + theta_1
x_knee = x0 + thy * math.cos(theta_2)
y_knee = y0 + thy * math.sin(theta_2)
return x_knee, y_knee
def animdelay():
diplomat walking at palace recipechart_1.update() # Refresh the drawing on the canvas.
chart_1.after(cycle_period) # Pause execution for 120 # milliseconds.
chart_1.delete("walking") # Erases everything on the canvas.
bx_stay = base_x
by_stay = base_y
for j in range(0,13): # Number of steps to be taken - # arbitrary.
astep_x = 60*j
bstep_x = astep_x + 30
cstep_x = 60*j + 15
aa = len(step_x) -1
for k in range(0,len(hip_x)-1):
# Motion of the hips in a stride of each foot.
cx0 = base_x + cstep_x + hip_x[k]
cy0 = base_y - hip_h - hip_y[k]
cx1 = base_x + cstep_x + hip_x[k+1]
cy1 = base_y - hip_h - hip_y[k+1]
if k >= 0 and k <= len(step_x)-2:
# Trajectory of the right foot.
ax0 = base_x + astep_x + step_x[k]
ax1 = base_x + astep_x + step_x[k+1]
ay0 = base_y - 10 - step_y[k]
ay1 = base_y - 10 -step_y[k+1]
ax_stay = ax1
ay_stay = ay1
if k >= len(step_x)-1 and k <= 2*len(step_x)-2:
# Trajectory of the left foot.
bx0 = base_x + bstep_x + step_x[k-aa]
bx1 = base_x + bstep_x + step_x[k-aa+1]
by0 = base_y - 10 - step_y[k-aa]
by1 = base_y - 10 - step_y[k-aa+1]
bx_stay = bx1
by_stay = by1
# The shoes
chart_1.create_image(ax_stay-5 ,ay_stay + 10 ,anchor=SW, \ image=shoey, tag="walking")
chart_1.create_image(bx_stay-5 ,by_stay + 10 ,anchor=SW, \ image=shoey, tag="walking")
diplomat walking at palace recipe# Work out knee positions
aknee_xy = kneePosition(ax_stay, ay_stay, cx1, cy1, thy)
bknee_xy = kneePosition(bx_stay, by_stay, cx1, cy1, thy)
# Right calf.
chart_1.create_line(ax_stay, ay_stay-5 ,aknee_xy[0], \ aknee_xy[1], width = 5, fill="black", tag="walking")
# Right thigh.
chart_1.create_line(cx1, cy1 ,aknee_xy[0], aknee_xy[1], \ width = 5, fill="black", tag="walking")
# Left calf.
#bknee_xy = kneePosition(bx_stay, by_stay, cx1, cy1, thy)
chart_1.create_line(bx_stay, by_stay-5 ,bknee_xy[0], \ bknee_xy[1], width = 5, fill="black", tag="walking")
# Left thigh.
chart_1.create_line(cx1, cy1 ,bknee_xy[0], bknee_xy[1], \ width = 5, fill="black", tag="walking")
# Torso
chart_1.create_image(cx1-20 ,cy1+30 ,anchor=SW, \ image=toff, tag="walking")
animdelay() # Animation
root.mainloop()

它是如何工作的...

通过使用GIF图像中的透明通道进行图像组合所提供的大量可能性,使我们能够创建工作室质量的卡通动画。关于之前菜谱中提到的三角学的评论也适用于此处。

森林中的蜘蛛

我们现在结合哺乳动物和鸟类的腿的动作来创建一个看起来邪恶的蜘蛛。我们也是第一次引入移动背景。这里没有使用透明图像,因为整个蜘蛛是由动画矢量线和椭圆组成的。

森林中的蜘蛛

准备工作

这里,我们需要一张长而窄的条带图片,其宽度远大于 Tkinter 画布提供的宽度。这并不是问题,并帮助我们创造蜘蛛穿越无尽森林的错觉。

如何做...

按照之前的方式执行显示的程序。

# walker_spider_1.py
# >>>>>>>>>>>>>>>>
from Tkinter import *
import math
import time
root = Tk()
root.title("Mr Incy Wincy")
cw = 500 # canvas width
ch = 100 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 100 # time between new positions of thespider # (milliseconds).
base_x = 20
base_y = 100
avian = 1
ax = [ base_x, base_x+20, base_x+60 ]
ay = [ base_y, base_y, base_y ]
bx = [ base_x+90, base_x+130, base_x+170]
by = [ base_y, base_y, base_y ]
cx1 = base_x + 80
cy1 = base_y - 20
thy = 50
#=============================================
posn_x = 0
posn_y = 00
spider_backg = PhotoImage(file = "/constr/pics1/jungle_strip_1.gif")
mammal and bird leg motionscombining#===========================================
foot_lift = [10,10,5,-5,-10,-10] # 3 legs per side, each foot in # sequence = 18 moves
foot_stay = [ 0, 0,0, 0, 0, 0]
#========================================
# Given a line joining two points xy0 and xy1, the base of an # isosceles triangle,
# as well as the length of one side, "thy" this returns the # coordinates of
# the apex joining the equal-length sides - the position of the knee.
def kneePosition(x0, y0, x1, y1, thy, avian):
theta_1 = math.atan2((y1 - y0), (x1 - x0))
L1 = math.sqrt( (y1 - y0)**2 + (x1 - x0)**2)
if L1/2 < thy:
# The sign of alpha determines which way the knees bend.
if avian == 1:
alpha = -math.acos(L1/(2*thy)) # Avian
else:
alpha = math.acos(L1/(2*thy)) # Mammalian
else:
alpha = 0.0
theta_2 = alpha + theta_1
x_knee = x0 + thy * math.cos(theta_2)
y_knee = y0 + thy * math.sin(theta_2)
return x_knee, y_knee
def animdelay():
chart_1.update() # This refreshes the drawing on the # canvas.
chart_1.after(cycle_period) # This makes execution pause for 100 # milliseconds.
chart_1.delete(ALL) # This erases *almost* everything on # the canvas.
for j in range(0,11): # Number of steps to be taken - arbitrary.
mammal and bird leg motionscombiningposn_x -= 1
chart_1.create_image(posn_x,posn_y,anchor=NW, image=spider_backg)
for k in range(0,len(foot_lift)*3):
posn_x -= 1
chart_1.create_image(posn_x,posn_y,anchor=NW, \ image=spider_backg)
#cx1 += 3.5
cx1 += 2.6
# Phase 1
if k >= 0 and k <= 5:
ay[0] = base_y - 10 - foot_lift[k]
ax[0] += 8
by[0] = base_y - 10 - foot_lift[k]
bx[0] += 8
# Phase 2
if k > 5 and k <= 11:
ay[1] = base_y - 10 - foot_lift[k-6]
ax[1] += 8
by[1] = base_y - 10 - foot_lift[k-6]
bx[1] += 8
# Phase 3
if k > 11 and k <= 17:
ay[2] = base_y - 10 - foot_lift[k-12]
ax[2] += 8
by[2] = base_y - 10 - foot_lift[k-12]
bx[2] += 8
for i in range(0,3):
aknee_xy = kneePosition(ax[i], ay[i], cx1, cy1, thy, 1) # Mammal knee
bknee_xy = kneePosition(bx[i], by[i], cx1, cy1, thy, 0) # Bird knee
chart_1.create_line(ax[i], ay[i] ,aknee_xy[0], \ aknee_xy[1], width = 3)
chart_1.create_line(cx1, cy1 ,aknee_xy[0], \ aknee_xy[1], width = 3)
chart_1.create_line(bx[i], by[i] ,bknee_xy[0], \ bknee_xy[1], width = 3)
chart_1.create_line(cx1, cy1 ,bknee_xy[0], \ bknee_xy[1], width = 3)
chart_1.create_oval(cx1-15 ,cy1-10 ,cx1+15 , \ cy1+10, fill="black")
animdelay()
root.mainloop()

它是如何工作的...

制作蜘蛛行走看起来合理的本质艺术在于调整步长、身体离地高度和腿(腿节)长度,使它们彼此一致。稍微调整错误,腿就会打滑或看起来是由非常有弹性的材料制成的。

还有蜘蛛腿运动同步的问题。在这个食谱中,我们选择让肢体以配对序列移动。

还有更多...

真蜘蛛有八条腿,而不是像这个例子中的六条。你可以尝试添加额外的腿对作为一个挑战。真正的蜘蛛每条腿还有额外的节段。让腿的三角学工作对于数学天才来说是一个极好的挑战。

移动的图片带

我们制作一个像幻灯片一样的移动图片带。这与典型的幻灯片不同,因为它显示的是一张连续移动的条带,图片被放置在末端到末端。

移动的图片带

准备工作

我们需要一套四张图片,所有图片大小相同。如果它们的大小不同,程序仍然可以工作,但看起来设计得不好。为这段代码提供的图片是:brass_vase.gif, red_vase.gif, blue_vase.gifglass_vase.gif,高度为 200 像素,宽度为 100 像素。

如何做...

按照之前的方式执行显示的程序。

# passing_show_1.py
# >>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
import time
root = Tk()
root.title("Vase Show")
cw = 400 # canvas width
ch = 200 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 100 # time between new positions of the ball (milliseconds).
#=======================================================================
posn_x1 = 0
posn_x2 = 100
posn_x3 = 200
posn_x4 = 300
posn_y = 00
im_brass = PhotoImage(file = "/constr/pics1/brass_vase.gif")
im_red = PhotoImage(file = "/constr/pics1/red_vase.gif")
im_blue = PhotoImage(file = "/constr/pics1/blue_vase.gif")
im_glass = PhotoImage(file = "/constr/pics1/glass_vase.gif")
#=======================================================================
def animdelay():
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 100 milliseconds.
chart_1.delete(ALL) # This erases *almost* everything on the canvas.
for j in range(0,400): # Number of steps to be taken - arbitrary.
posn_x1 -= 1
posn_x2 -= 1
posn_x3 -= 1
posn_x4 -= 1
chart_1.create_image(posn_x1,posn_y,anchor=NW, image=im_brass)
chart_1.create_image(posn_x2,posn_y,anchor=NW, image=im_red)
chart_1.create_image(posn_x3,posn_y,anchor=NW, image=im_blue)
chart_1.create_image(posn_x4,posn_y,anchor=NW, image=im_glass)
animdelay()
root.mainloop()
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

每张图片都有自己的 x 位置坐标 posn_x1,posn_x2 等。一个 'for' 循环在每次循环执行时将这些位置调整一个像素,导致图片逐渐向左移动。

连续的图片带

这个食谱扩展了之前示例中使用的位置调整机制,以维持连续的图片带。

准备工作

我们使用与之前食谱中相同的四张图片。

如何做...

按照之前完全相同的方式执行程序。

# endless_passing_show_1.py
# >>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
import time
root = Tk()
root.title("Vase Show")
cw = 100 # canvas width
ch = 200 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="black")
chart_1.grid(row=0, column=0)
cycle_period = 100 # time between new positions of the images milliseconds).
#==============================================
posn_x1 = 0
posn_x2 = 100
posn_x3 = 200
posn_x4 = 300
posn_y = 00
im_brass = PhotoImage(file = "/constr/pics1/brass_vase.gif")
im_red = PhotoImage(file = "/constr/pics1/red_vase.gif")
im_blue = PhotoImage(file = "/constr/pics1/blue_vase.gif")
im_glass = PhotoImage(file = "/constr/pics1/glass_vase.gif")
#=============================================
def animdelay():
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 100 milliseconds.
chart_1.delete(ALL) # This erases *almost* everything on the canvas.
for j in range(0,600): # Number of steps to be taken - arbitrary.
posn_x1 -= 1
posn_x2 -= 1
posn_x3 -= 1
posn_x4 -= 1
chart_1.create_image(posn_x1,posn_y,anchor=NW, image=im_brass)
chart_1.create_image(posn_x2,posn_y,anchor=NW, image=im_red)
chart_1.create_image(posn_x3,posn_y,anchor=NW, image=im_blue)
chart_1.create_image(posn_x4,posn_y,anchor=NW, image=im_glass)
# The numerical parameters below could be turned into
# a 'for' loop and allow the loop to be compact and interminable.
if j == 100:
posn_x1 = 300
if j == 200:
posn_x2 = 300
if j == 400:
posn_x3 = 300
if j == 400:
posn_x4 = 300
animdelay()
root.mainloop()

它是如何工作的...

这个程序的小技巧是重置 x 位置坐标,posn_1 等,这些坐标控制图片在画布上退出后画布上的位置。位置坐标被重置到画布右侧 200 像素的位置。

无尽背景

下一个要实现的是创建一个看起来几乎是无限宽的全景。我们扩展了之前示例中使用的技术,制作了一个看起来似乎是无限长的背景图片。

无尽背景

准备工作

我们提供了一个经过处理的单一图像,其右侧边缘与左侧边缘完全吻合,如果它们并排放置,将创建一个无限连续的图像。在此编辑中使用了 GIMP 图像处理程序。用非常简化的解释来说,我们执行以下操作:

  1. 我们复制图像的一部分,这部分在垂直方向上细节不多,这是我们进行裁剪的地方。

  2. 然后将这部分粘贴到一端,以便两个图像有实质性的重叠。

  3. 然后,包含复制和粘贴部分的顶层应用了模糊边缘的橡皮擦工具,这样我们就看不到从一幅图像到另一幅图像的过渡。

如何做到这一点...

执行以下代码。

# passing_cloudscape_1.py
# >>>>>>>>>>>>>>>>
from Tkinter import *
import time
root = Tk()
root.title("Freedom Flight Cloudscape")
cw = 400 # canvas width
ch = 239 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="black")
chart_1.grid(row=0, column=0)
cycle_period = 50 # time between new positions of the background # (milliseconds).
#=============================================
posn_x1 = 0
posn_x2 = 574
posn_plane_x = 60
posn_plane_y = 60
posn_y = 00
# Panorama image size = 574 x 239
im_one = PhotoImage(file = "/constr/pics1/continuous_clouds \ _panorama.gif")
im_two = PhotoImage(file = "/constr/pics1/continuous_clouds \ _panorama.gif")
im_plane = PhotoImage(file = "/constr/pics1/yellow_airplane_2.gif")
#===========================================
def animdelay():
chart_1.update() # This refreshes the drawing on the # canvas.
chart_1.after(cycle_period) # This makes execution pause for 50 # milliseconds.
chart_1.delete(ALL) # This erases *almost* everything on # the canvas.
num_cycles = 10 # Number of total cycles of the # loop.
k = 0
for j in range(0,num_cycles*1148): # Number of steps to be taken # arbitrary.
posn_x1 -= 1
posn_x2 -= 1
k += 1
chart_1.create_image(posn_x1,posn_y,anchor=NW, image=im_one)
chart_1.create_image(posn_x2,posn_y,anchor=NW, image=im_two)
chart_1.create_image(posn_plane_x,posn_plane_y,anchor=NW, \ image=im_plane)
if k == 574:
posn_x1 = 574
if k == 1148:
posn_x2 = 574
k = 0
posn_x1 = 0
animdelay()
root.mainloop()

它是如何工作的...

我们使用与之前食谱中相同的 x 坐标位置调整技术。这次我们选择调整位置为 574 的倍数,这是云景图像的像素宽度。我们还使用了透明背景的飞机图像。飞机保持静止。

第八章。数据输入与数据输出

在本章中,我们将涵盖:

  • 在硬盘上创建新文件

  • 将数据写入新创建的文件

  • 将数据写入多个文件

  • 向现有文件添加数据

  • 将 Tkinter 绘图形状保存到磁盘

  • 从磁盘检索 Python 数据

  • 简单的鼠标输入

  • 存储和检索鼠标绘制的形状

  • 鼠标线条编辑器

  • 所有可能的鼠标操作

简介

现在我们来讨论在硬盘等存储介质上存储和检索图形数据的细节。除了位图图像外,我们还需要能够创建、存储和检索越来越复杂的矢量图形。我们还希望有将位图图像的部分转换为矢量图像的技术。

到目前为止,我们所有的程序都将其数据包含在源代码中。这限制了我们可以方便地在几分钟内键入的数据列表和数组复杂性。我们不希望有这种限制。我们希望能够处理和操作可能达到数百兆字节大小的原始数据块。手动键入这样的文件是难以想象的低效。有更好的做事方式。这就是命名文件、数据流和硬盘的作用。

在硬盘上创建新文件

我们编写并执行了最简单的程序,该程序将在磁盘上创建一个数据文件。

到目前为止,我们不需要在硬盘或 USB 闪存盘上存储任何数据。现在,我们将通过一系列简单的练习来存储和检索存储介质上的文件数据。然后,我们使用这些方法以实际的方式保存和编辑 Tkinter 线条。Tkinter 线条可以是一组单独的线段和形状的集合。如果我们正在开发复杂且丰富的绘图,我们能够存储和检索工作进度是至关重要的。

如何做到...

按照常规方式编写、保存并执行显示的程序。当你运行程序时,你将观察到的唯一成功执行迹象是在你点击Enter后短暂的暂停。执行将没有任何消息而终止。然而,现在在目标目录constr中存在一个名为brand_new_file.dat的新文件。我们应该打开constr并验证这确实如此。

# file_make_1 .py
# >>>>>>>>>>>>>>
filename = "constr/brand_new_file.dat"
FILE = open(filename,"w")

它是如何工作的...

这个看起来简约的程序实现了以下目标:

  • 它验证了 Python 的文件 I/O 函数存在且正在工作。不需要导入任何模块

  • 它展示了 Python 访问存储设备上的数据文件的方式并没有什么异常

  • 它证明了操作系统遵循 Python 的文件创建指令

如何读取新创建的文件

一旦创建了一个文件,就可以读取它。因此,一个读取磁盘上现有文件的程序会是这样的:

# file_read_1 .py
# >>>>>>>>>>>
filename = "constr/brand_new_file.dat"
FILE = open(filename,"r")

如你所见,唯一的区别是r而不是w

注意,Python 以多种格式读取和写入文件。在rbwb中的b表示以字节或二进制格式读取和写入。这些是每个字节中的1s0s。在我们的例子中,没有brw告诉 Python 解释器必须将字节解释为 ASCII 字符。我们唯一需要记住的是保持格式分离。

将数据写入新创建的文件

我们现在创建一个文件,然后向其中写入一小部分数据。这些非常简单的菜谱的价值在于,当我们尝试一些复杂且不符合预期的任务时,简单的一步测试程序允许我们将问题分解成简单的任务,我们可以逐步增加复杂性,验证每个新更改的有效性。这是许多最佳程序员使用的经过验证和信任的哲学。

# file_write_1.py
#>>>>>>>>>>>>>
# Let's create a file and write it to disk.
filename = "/constr/test_write_1.dat"
filly = open(filename,"w") # Create a file object, in write # mode
for i in range(0,2):
filly.write("everything inside quotes is a string, even 3.1457")
filly.writelines("\n")
filly.write("How will stored data be delimited so we can read \ chunks of it into elements of list, tuple or dictionart?")
filly.writelines("\n")
#filly.close()

它是如何工作的...

在这一点上需要注意的重要事情是,换行符\n是 Python 区分变量的自然方式。空格字符也将用作数字或字符值的分隔符或定界符。

将数据写入多个文件

我们在这里看到,像我们预期的那样,使用 Python 打开和写入一系列单独的文件非常简单直接。一旦我们看到了正确的语法示例,它就会正常工作。

# file_write_2.py
#>>>>>>>>>>>>>
# Let's create a file and write it to disk.
filename_1 = "/constr/test_write_1.dat"
filly = open(filename_1,"w") # Create a file object, in # write mode
filly.write("This is number one and the fun has just begun")
filename_2 = "/constr/test_write_2.dat"
filly = open(filename_2,"w") # Create a file object, in # write mode
filly.write("This is number two and he has lost his shoe")
filename_3 = "/constr/test_write_3.dat"
filly = open(filename_3,"w") # Create a file object, in # write mode
filly.write("This is number three and a bump is on his knee")
#filly.close()

它是如何工作的...

这个示例的价值在于它提供了正确的调试语法示例。因此,它可以以最小的麻烦进行重用和修改。

向现有文件添加数据

我们测试了三种将数据写入现有文件的方法,以发现一些基本的数据存储规则。

# file_append_1.py
#>>>>>>>>>>>>>>>>>
# Open an existing file and add (append) data to it.
filename_1 = "/constr/test_write_1.dat"
filly = open(filename_1,"a") # Open a file in append mode
filly.write("\n")
filly.write("This is number four and he has reached the door")
for i in range(0,5):
filename_2 = "/constr/test_write_2.dat"
filly = open(filename_2,"a") # Create a file in append mode
filly.write("This is number five and the cat is still alive")
filename_3 = "/constr/test_write_2.dat"
filly = open(filename_3,"w") # Open an existing file in # write mode
# The command below WILL fail "w" is really "overwrite"
filly.write("This is number six and they cannot find the fix")

它是如何工作的...

构成第一种方法的是两件事:首先,我们以追加模式打开文件("a"),这意味着我们将数据添加到文件中已有的内容。不会销毁或覆盖任何内容。其次,我们通过以下行将新数据与旧数据分开:

filly.write("\n")

第二种方法可行,但这是一个非常不好的实践,因为没有方法来分离不同的条目。

第三种方法会清除文件中之前存储的内容。

所以记住 write 和 append 之间的区别

如果我们清楚地记住上述三种方法,我们将能够成功存储和检索我们的数据,而不会出现错误和挫败感。

将 Tkinter 绘制的形状保存到磁盘

当我们使用 Tkinter 创建一个复杂的形状时,我们通常希望保留这个形状以供以后使用。实际上,我们希望建立一个整个形状库。如果其他人做类似的工作,我们可能希望共享和交换形状。这种社区努力是大多数强大且成功的开源程序成功的关键。

准备工作

如果我们回到名为“绘制复杂形状——螺旋藤”的例子,在第二章,绘制基本形状中,我们会看到形状是由两个坐标列表vine_xvine_y定义的。我们首先将这些形状保存到磁盘文件中,然后看看成功检索和绘制它们需要什么。

在你的硬盘上创建一个名为/constr/vector_shapes的文件夹,以便接收你的存储数据。

如何做...

按照常规方式执行显示的程序。

# save_curly_vine_1.py
#>>>>>>>>>>>>>>>>>
vine_x = [23, 20, 11, 9, 29, 52, 56, 39, 24, 32, 53, 69, 63, \ 47, 35, 35, 51,\
82, 116, 130, 95, 67, 95, 114, 95, 78, 95, 103, 95, 85, 95, 94.5]
vine_y = [36, 44, 39, 22, 16, 32, 56, 72, 91, 117,125, 138, 150, \ 151, 140, 123, 107,\
92, 70, 41, 5, 41, 66, 41, 24, 41, 53, 41, 33, 41, 41, 39]
vine_1 = open('/constr/vector_shapes/curley_vine_1.txt', 'w')
vine_1.write(str(vine_x ))
vine_1.write("\n")
vine_1.write(str(vine_y ))

它是如何工作的...

首先要注意的是,存储的数据没有“类型”,它仅仅是文本字符。因此,任何要追加到打开文件的数據都必须使用字符串转换函数str(some_integer_or_float_object)转换为字符串格式。

第二点要注意的是,将整个列表作为一个列表对象存储,例如str(vine_x),是做事的最佳方式,因为以这种方式存储后,可以直接作为一个整行读入到类似的列表对象中,参见下一道菜谱了解如何做到这一点。在典型的 Python 风格中,简单而明显的方法似乎总是最好的。

存储命令

当我们检索混合整数和浮点数据的列表时面临的问题是,它被存储为一个长的字符字符串。那么我们如何让 Python 将包含方括号、逗号、空格和新行字符的长字符串列表转换为正常的 Python 数值列表呢?我们希望我们的绘图不受损坏。有一个很棒的功能eval()可以轻松完成这项工作。

另有一种名为 pickle 的方法也能做到同样的事情。

从磁盘存储中检索 Python 数据

我们从存储的文件curley_vine_1.txt中检索两个列表vine_xvine_y。我们希望它们与存储之前的形式完全相同。

准备工作

这个菜谱的准备是通过运行之前的程序save_curly_vine_1.py来完成的。如果这个程序运行成功,那么在/constr/vector_shapes目录下将会有一个名为curly_vine_1.txt的文件。如果你打开这个文本文件,你会看到两行,第一行是我们原始的vine_x的字符串表示,同样地,这个文件的第二行将代表vine_y

# retrieve_curly_vine_1.py
#>>>>>>>>>>>>>>>>>>>>>
#vine_x = []
vine_1 = open('/constr/vector_shapes/curley_vine_1.txt', 'r')
vine_x = eval(vine_1.readline())
vine_y = eval(vine_1.readline())
# Tests to confirm that everything worked.
print "vine_x = ",vine_x
print vine_x[31]
print "vine_y = ",vine_y
print vine_y[6]

它是如何工作的...

这之所以如此简单而优雅,是因为eval()函数的存在。文档中提到:“expression参数被解析并作为 Python 表达式评估”以及“返回值是评估表达式的结果”。这意味着括号内的文本被当作普通的 Python 表达式处理并执行。在我们的特定例子中,花括号内的字符串被解释为一个数字列表,而不是字符,这正是我们想要的。

简单鼠标输入

我们现在开发代码,通过捕捉电子图纸上鼠标点击而不是使用铅笔、橡皮和由死树制成的纸张来帮助绘制复杂的形状。我们将这个复杂任务分解为下一个三个菜谱中涵盖的简单步骤。

# mouseclick_1.py
#>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
frame = Frame(root, width=100, height=100)
def callback(event):
print "clicked at", event.x, event.y
frame.bind("<Button-1>", callback)
frame.grid()
root.mainloop()
root.destroy()

它是如何工作的...

点击鼠标按钮被称为事件。如果我们想让我们的程序在程序内部执行某些操作,那么我们需要编写一个callback函数,每当事件发生时都会调用这个函数。callback的旧术语是“中断服务例程”。

这行frame.bind("<Button-1>", callback)实际上表示:

“在事件(即鼠标左键点击(<Button-1>))和被调用的函数callback之间建立一个连接(绑定)”。你可以给这个函数取任何你喜欢的名字,但“callback”这个词可以使代码更容易理解。

需要注意的最后一个点是,变量event.xevent.y是保留用于记录鼠标的 x-y 坐标。在这个特定的callback函数中,我们打印出鼠标点击时的位置,在一个称为“frame”的框架中。

还有更多...

在接下来的两个菜谱中,我们基于使用鼠标触发的callback函数,目的是制作一个形状追踪工具。

存储和检索鼠标绘制的形状

我们制作一个程序,通过使用鼠标和三个按钮,我们可以将形状存储到磁盘上,清除画布,然后召回并在屏幕上显示形状。

准备工作

确保你已经创建了一个名为constr的文件夹,因为这是我们的程序期望能够保存绘制的形状的地方。它也是当被命令检索并显示时将从中检索的地方。

# mouse_shape_recorder_1.py
#>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Mouse Drawn Shape Saver")
cw = 600 # canvas width
ch = 400 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="#ffffff")
chart_1.grid(row=1, column=1)
pt = [0]
x0 = [0]
y0 = [0]
count_point = 0
x_end = 10
y_end = 10
#============================================
# Create a new circle where the click happens and draw a new line
# segment to the last point (where the mouse was left clicked).
def callback_1(event): # Left button pressed.
global count_point, x_end, y_end
global x0, y0
global x0_n, y0_n, pt
x_start = x_end
y_start = y_end
x_end = event.x
y_end = event.y
chart_1.create_line(x_start, y_start , x_end,y_end , fill = \ "#0088ff")
chart_1.create_oval(x_end-5,y_end-5, x_end+5, y_end+5, outline = \ "#0088ff")
count_point += 1
pt = pt + [count_point]
x0 = x0 + [x_end] # extend list of all points
y0 = y0 + [y_end]
chart_1.bind("<Button-1>", callback_1) # <button-1> left mouse button
#==============================================
# 1\. Button control to store segmented line
def callback_6():
global x0, y0
xy_points = open('/constr/shape_xy_1.txt', 'w')
xy_points.write(str(x0))
xy_points.write('\n')
xy_points.write(str(y0))
xy_points.close()
Button(root, text="Store", command=callback_6).grid(row=0, column=2)
#=============================================
# 2\. Button control to retrieve line from file.
def callback_7():
global x0, y0 # Stored list of mouse-click positions.
xy_points = open('/constr/shape_xy_1.txt', 'r')
x0 = eval(xy_points.readline())
y0 = eval(xy_points.readline())
xy_points.close()
print "x0 = ",x0
print "y0 = ",y0
for i in range(1, count_point): # Re-plot the stored and # retreived line
chart_1.create_line(x0[i], y0[i] , x0[i+1], y0[i+1] , \ fill = "#0088ff")
chart_1.create_oval(x_end - 5,y_end - 5, x_end + 5, \ y_end + 5 , outline = "#0088ff")
Button(root, text="retrieve", command=callback_7).grid(row=1, \ column=2)
#=============================================
# 3\. Button control to clear canvas
def callback_8():
chart_1.delete(ALL)
Button(root, text="CLEAR", command=callback_8).grid(row=2, column=2)
root.mainloop()

它是如何工作的...

除了用于将左鼠标点击的位置添加到列表x0y0(x 和 y 坐标)的callback函数外,我们还有另外三个callback函数。这三个额外的callback函数是用来触发执行以下功能的:

  • 将列表x0y0保存到名为shape_xy_1.txt的磁盘文件中。

  • 清除画布上的所有绘制线条和圆圈

  • 检索shape_xy_1.txt的内容,并将其重新绘制到画布上

还有更多...

绘制是一个不完美的过程,艺术家和制图员会使用橡皮和铅笔。当我们用连接到计算机的鼠标绘制时,我们也需要对所绘制的任何线条进行调整和修正。我们需要编辑能力。

绘制是一个不完美的过程。我们希望能够调整一些点的位置,以改善绘制。我们将在下一个菜谱中这样做。

鼠标线条编辑器

在绘制完成后,我们编辑(更改)使用鼠标绘制的形状。

准备工作

为了限制代码的复杂性和长度,我们排除了上一个菜谱中提供的存储和回忆绘制形状的功能。因此,在这个菜谱中不会使用任何存储文件夹。

# mouse_shape_editor_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
root = Tk()
root.title("Left drag to draw, right to re-position.")
cw = 600 # canvas width
ch = 650 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="#ffffff")
chart_1.grid(row=1, column=1)
linedrag = {'x_start':0, 'y_start':0, 'x_end':0, 'y_end':0}
map_distance = 0
dist_meter = 0
x_initial = 0
y_initial = 0
#==============================================
# Adjust the distance between points if desired
way_points = 50 # Distance between editable way-points
#==============================================
magic_circle_flag = 0 # 0-> normal dragging, 1 -> double-click: # Pull point.
point_num = 0
x0 = []
y0 = []
#================================================
def separation(x_now, y_now, x_dot, y_dot): # DISTANCE MEASUREMENT
# Distance to points - used to find out if the mouse # clicked inside a circle
sum_squares = (x_now - x_dot)**2 + (y_now -y_dot)**2
distance= int(math.sqrt(sum_squares)) # Get Pythagorean # distance
return( distance)
#================================================
# CALLBACK EVENT PROCESSING FUNCTIONS
def callback_1(event): # LEFT DOWN
global x_initial, y_initial
x_initial = event.x
y_initial = event.y
def callback_2(event): # LEFT DRAG
global x_initial, y_initial
global map_distance, dist_meter
global x0, y0
linedrag['x_start'] = linedrag['x_end'] # update positions
linedrag['y_start'] = linedrag['y_end']
linedrag['x_end'] = event.x
linedrag['y_end'] = event.y
increment = separation(linedrag['x_start'],linedrag['y_start'], \ linedrag['x_end'], linedrag['y_end'] )
map_distance += increment # Total distance - # potentiasl use as a map odometer.
dist_meter += increment # Distance from last circle
if dist_meter>way_points: # Action at way-points
x0.append(linedrag['x_end']) # append to line
y0.append(linedrag['y_end'])
xb = linedrag['x_end'] - 5 ; yb = linedrag['y_end'] - 5 # Centre circle on line
x1 = linedrag['x_end'] + 5 ; y1 = linedrag['y_end'] + 5
chart_1.create_oval(xb,yb, x1,y1, outline = "green")
dist_meter = 0 # re-zero the odometer.
linexy = [ x_initial, y_initial, linedrag['x_end'] , \ linedrag['y_end'] ]
chart_1.create_line(linexy, fill='green')
x_initial = linedrag['x_end'] # start of next segment
y_initial = linedrag['y_end']
def callback_5(event): # RIGHT CLICK
global point_num, magic_circle_flag, x0, y0
# Measure distances to each point in turn, determine if any are # inside magic circle.
# That is, identify which point has been clicked on.
for i in range(0, len(x0)):
d = separation(event.x, event.y, x0[i], y0[i])
if d <= 5:
point_num = i # this is the index that controls editing
magic_circle_flag = 1
chart_1.create_oval(x0[i] - 10,y0[i] - 10, x0[i] + 10, \ y0[i] + 10 , width = 4, outline = "#ff8800")
x0[i] = event.x
y0[i] = event.y
def callback_6(event): # RIGHT RELEASE
global point_num, magic_circle_flag, x0, y0
if magic_circle_flag == 1: # The point is going to be # repositioned.
x0[point_num] =event.x
y0[point_num] =event.y
chart_1.delete(ALL)
chart_1.update() # Refreshes the drawing on the # canvas.
q=[]
for i in range(0,len(x0)):
q.append(x0[i])
q.append(y0[i])
chart_1.create_oval(x0[i] - 5,y0[i] - 5, x0[i] + 5, \ y0[i] + 5 , outline = "#00ff00")
chart_1.create_line(q , fill = "#ff00ff") # Now show the # new positions
magic_circle_flag = 0
#==============================
chart_1.bind("<Button-1>", callback_1) # <Button-1> ->LEFT mouse # button
chart_1.bind("<B1-Motion>", callback_2)
chart_1.bind("<Button-3>", callback_5) # <Button-3> ->RIGHT mouse # button
chart_1.bind("<ButtonRelease-3>", callback_6)
root.mainloop()

它是如何工作的...

之前的程序现在包括:

  • 处理左右鼠标点击和拖动的 callback 函数。

距离测量函数 separation(x_now, y_now, x_dot, y_dot)。当右键点击时,测量到每个线段的距离。如果这些距离中的任何一个在现有接点内部,则绘制一个橙色圆圈,并将控制权传递给 callback_6,该函数更新新点的坐标并刷新修改后的绘图。是否移动点的决定由 magic_circle_flag 的值决定。这个标志的状态由 separation() 计算的距离确定。当右键按下时,如果距离测量发现它在接点内部,则将其设置为 1,在移动一个点之后将其设置为 0

还有更多...

现在我们有了通过鼠标操作来控制和调整线条和曲线绘制的方法,其他可能性也被打开了。

为什么不添加更多功能?

好好扩展这个程序的功能,包括:

  • 删除点的功能

  • 处理未连接段的能力

  • 选择或点击创建点的功能

  • 拖动仙女灯(等长段)

随着我们工作的扩展,这个列表会变得越来越长。最终,我们将创建一个有用的矢量图形编辑器,并且会有压力去匹配现有专有和开源编辑器的功能。为什么重造轮子?如果这是一个实际的选择,那么与现有成熟的矢量编辑器产生的矢量图像一起工作的努力可能会更有成效。

使用其他工具获取和重新处理图像

在下一章中,我们将探讨如何使用开源矢量图形编辑器 Inkscape 中的矢量图像。Inkscape 能够导出多种格式的图像,包括一种标准化的网络格式,称为缩放矢量图形或简称SVG

如何利用鼠标

本章大量使用了鼠标作为用户交互工具,在 Tkinter 画布上绘制形状。为了充分利用鼠标的技能,下一道菜谱将是对鼠标交互完整工具包的考察。

我们可以测量蜿蜒线上的距离

在代码中,有一个名为 map_distance 的变量尚未使用。它可以用来追踪地图上蜿蜒路径的距离。想法是,如果我们想在类似谷歌地图这样的地图上测量未标记路径和道路的距离,我们就能将这个方法适应到这项任务中。

所有可能的鼠标动作

现在我们编写一个程序来测试 Python 能够响应的每个可能的鼠标事件。

如何操作...

以正常方式执行显示的程序。

# all_mouse_actions_1.py
#>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Mouse follower")
# The Canvas here is bound to the mouse events
cw = 200 # canvas width
ch = 100 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="#ffffff")
chart_1.grid(row=1, column=1)
#========= Left Mouse Button Events ===============
# callback events
def callback_1(event):
print "left mouse clicked"
def callback_2(event):
print "left dragged"
def callback_3(event):
print "left doubleclick"
def callback_4(event):
print "left released"
#======== Center Mouse Button Events ======================
def callback_5(event):
print "center mouse clicked"
def callback_6(event):
print "center dragged"
def callback_7(event):
print "center doubleclick"
def callback_8(event):
print "center released"
#======== Right Mouse Button Events ======================
def callback_9(event):
print "right mouse clicked"
def callback_10(event):
print "right dragged"
def callback_11(event):
print "right doubleclick"
def callback_12(event):
print "right released"
# <button-1> is the left mouse button
chart_1.bind("<Button-1>", callback_1)
chart_1.bind("<B1-Motion>", callback_2)
chart_1.bind("<Double-1>", callback_3)
chart_1.bind("<ButtonRelease-1>", callback_4)
# <button-2> is the center mouse button
chart_1.bind("<Button-2>", callback_5)
chart_1.bind("<B2-Motion>", callback_6)
chart_1.bind("<Double-2>", callback_7)
chart_1.bind("<ButtonRelease-2>", callback_8)
# <button-3> is the right mouse button
chart_1.bind("<Button-3>", callback_9)
chart_1.bind("<B3-Motion>", callback_10)
chart_1.bind("<Double-3>", callback_11)
chart_1.bind("<ButtonRelease-3>", callback_12)
root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

它是如何工作的...

上述代码相当直观。创建了一个小画布,它能够响应所有鼠标操作。确认响应是否正确工作是通过在系统控制台上输入确认消息来实现的。我们可以通过在 callback 函数中插入适当的 Python 命令来调整 callback 函数,以执行我们选择的任何任务。

还有更多...

鼠标事件和 Tkinter 小部件通常协同工作。大多数 Tkinter 图形用户界面小部件都是设计用来通过鼠标事件进行控制的,例如左键或右键点击,或者按住按钮进行拖动。Tkinter 提供了丰富的选择,这些小部件将在 第十章,GUI 构建第一部分第十一章,GUI 构建第二部分 中进行探讨。

第九章。交换 Inkscape SVG 绘图与 Tkinter 形状

在本章中,我们将涵盖:

  • Inkscape 作为获取 Tkinter 线形状(路径)的工具

  • 寻找和安装 Inkscape

  • 哪里可以找到 SVG 裁剪艺术

  • 从矢量图像中获取 Tkinter 路径

  • 将 SVG 图像中的路径数据转换为其他格式

  • 使用 Inkscape 作为 Tkinter 路径的图形工具

简介

在本章中,我们探讨将图形形状数据引入 Tkinter 程序的替代方法和手段。最普遍的矢量图形格式是为网页设计的,这被称为 SVG,即 缩放矢量图形 的缩写。它是世界 Wide Web 联盟定义的官方标准规范,自 1999 年以来一直存在。

我们对 SVG 的兴趣源于它在 Python 中使用 Tkinter 模块创建绘制形状的实用价值。

像 Inkscape 这样的专业矢量绘图软件包和一些专有绘图软件包,通过一些 Python 代码的帮助,使我们能够获取可以直接用于 Tkinter 的 create_line(x0,y0 …) 函数中的坐标列表。

网上可用的无版权 SVG 图片库正在不断增长。借助像 Inkscape 这样的工具,我们可以拆解现有图像,并使用其部分内容用于我们的图形工作和 Python 程序。其中一个这样的网站是 www.openclipart.org/,它允许并鼓励任何人复制存储在该网站上的数千个 SVG 格式的图像。

SVG 绘图以多种方式编码线条。一种方式是将线条表示为画布上的 x-y 坐标点序列。每个点被定义为相对于画布零位置的数字对,该位置是西北角(右上角)。第二种方式是将每个点表示为相对于前一点的相对位移。

SVG 绘图的架构

我们将研究 Inkscape 如何编码绘图,以便我们可以在 Python 中对其进行解释以供使用。我们将要做的是:

  1. 在 Inkscape 中绘制一些简单的对象,并将它们保存为“Plain SVG”格式的文件。

  2. 然后我们在文本编辑器中打开文件,检查内容,以便我们能够识别出我们感兴趣的行。

  3. 最后,我们将编写代码将感兴趣的 SVG 线转换为 Tkinter 列表,我们可以在我们的 Python 程序中直接使用。

准备工作

我们现在需要做的第一件事是获取并安装 Inkscape 到我们的计算机上。我们可以在 www.inkscape.org/download/ 找到它,那里有适用于 Linux 和 Microsoft Windows 的版本。

Inkscape 的在线文档和教程非常出色。然而,我们希望使用最少的 Inkscape,所以这个配方就是提供一些基本指导,以完成最小任务。

如何操作...

在 Inkscape 中我们需要使用的唯一工具是下面的截图所示的线条绘制笔。我们使用这个工具绘制了一个“Z”形状,并将文件保存为 z_inkscape.svg

产生的代码,在文本编辑器中显示,如下截图所示:

如何做...

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg

version="1.1"
width="744.09448"
height="1052.3622"
id="svg3741">
<defs
id="defs3743" />
<g
id="layer1">
<path
d="m 122.85714,89.50504 280,0 -280,45.71429 271.42857,0 "
id="path3751"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

它是如何工作的...

之前的大部分代码对我们来说没有兴趣。它是网络浏览器解释以显示网页的 XML 代码。然而,其中嵌入的 SVG 路径是我们想要以某种方式转移到 Python 中的,以便 Tkinter 可以将其显示为绘制的形状。

我们感兴趣的部分是从 <path 开始的段落,因为这是用笔工具绘制的 "Z" 形状的 SVG 格式描述。这是代码的部分:

<path
d="m 122.85714,89.50504 280,0 -280,45.71429 271.42857,0 " id="path3751"
style="fill:none;stroke:#000000;stroke-width:1px;stroke- linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />

这是整个 'Zorro' 标志的 SVG 描述以及随后的行,已经稍微简化,去掉了小数点,以提高可读性:

d="m 122, 89 280,0 -280,45 271,0"

这条线相当于可以写成的 Tkinter 指令组:

x0 = 122
y0 = 89
canvas.create_line(x0,y0, x0+280,y0+0, x0-280,y0+45, x0+271,y0,+0 )

'm' 符号是 SVG 指令 "移动到",其中移动的像素数是加到线上前一个点的坐标的增量,除了第一个点 122,89,它告诉笔从哪里开始。

还有更多...

我们不想成为 SVG 专家。我们只想知道足够多的知识,以便能够识别出我们可以用于 Python 的图形数据。本着这个精神,这里给出了一些最常见的 SVG 指令的总结。

  • m x,y 是 "移动到" 指令,它将笔移动到点 x,y 而不绘制线。

  • m x0,y0 x1,y1 x2,y2 将从 x0,y0 绘制一条线到 x1,y1,然后从 x1,y1 绘制另一段线到 x2, y2。注意,SVG 解释器只将第一个点 x0,y0 解释为 "移动到" 指令,但将后续的点对解释为 "线到" 指令。"线到" 指令是将笔尖放到表面上并绘制的指令。

  • m x0,y0 x1,y1 x2,y2 将从 x0,y0 绘制一条线到 x0+x1,y0+y1,然后从 x0+x1,y0+y1 绘制另一段线到 x0+x2,y0+y2

    需要注意的是,小写字母的使用很重要,并且告诉 SVG 解释器将坐标计算为增量值,这些值必须加到前一个位置上。与 m 指令一样,笔移动到第一个点 x0,y0 而不绘制任何东西,但所有后续的点都作为连接相邻点的线段绘制。

  • l x,y 指令让笔从当前笔所在的位置绘制一条线到点 x,y

  • l x,y 指令让笔从当前笔的位置(例如 x0,y0)绘制一条线到点 x0 + x, y0 + y

  • 在路径坐标列表末尾的 z 将通过绘制一条线从当前点回到起点来闭合路径。

独立路径的 SVG 代码

每个独立的路径都有自己的 <path innards-of the path /> 代码。

因此,三个独立路径的 SVG 代码可能如下所示:

<path
d="M 125,100 340,149 340,100"
id="path3000"
style="style-descriptors" />
<path
d="m 128,258 0,137 148,0 0,-145 -148,8 z"
id="path3001"
style="style-descriptors" />
<path
d="m 114,629 0,-134 0,122 102,0 0,-134 105,0 0,120 82,0 0,-114"
id="path3002"
style="style-descriptors " />

我们感兴趣的是从 d= 开始的三条线,因为这些提供了 x,y 对的字符串,它们给出了绘制形状上点的位置。由于 Tkinter 只会使用整数部分,因此高算术精度是多余的。然而,如果我们需要通过将每个数字乘以一个放大因子来放大图片,那么高算术精度可以避免形状的一小部分扭曲。

在 Inkscape 中追踪图像的形状

我们想使用 Inkscape 来捕捉一系列复杂的形状,这些形状用铅笔和纸绘制会既繁琐又困难。一个实际的应用例子可能是你想要画一头大象的画,你需要一些可靠的指南,基于杂志图片或照片,来确定四肢和身体的轮廓。一种方法是用铅笔和直尺在图片上画一个网格,然后在空白画布上重复缩放版本的网格,最后用铅笔画出轮廓。另一种方法是把大象的 JPG, GIF, PNG, BMPTIFF 图像拖入 Inkscape,并使用笔工具在其上追踪一系列线条。这些轮廓可以打印出来,然后追踪到你的画布上。这些相同的形状可以用 Python 中的 Tkinter 来使用。

将位图图像转换为 SVG 路径还有其他方法,但它们需要对图像进行相当多的预处理,例如颜色分离和将连续的灰度转换为纯黑白。下面所示的方法允许我们决定我们的线条必须遵循的确切路径,即使原始图像提供了许多微妙和模糊的选择。

准备工作

将我们要处理的图像放在一个方便的文件夹中。在这个菜谱中,我们使用 /constr/pics1

如何操作...

  1. 打开 Inkscape 并选择 文件 | 打开如何操作...

  2. 选择你想要处理的图像。如何操作...

  3. 添加一个新图层。这允许我们在一个图层上绘制线条,而不会干扰包含照片图像的背景图层。如何操作...

  4. 放大图像以便更容易看到放置笔工具的位置。这也有助于提高我们将要追踪的路径的准确性。

    我们这样做是通过点击左侧边栏工具栏上的放大镜图标,然后点击带有加号符号的放大镜来放大。这位于顶部边栏出现的工具栏中。

    如何操作...

  5. 在左侧边栏工具栏上点击笔工具,然后跟随我们想要捕捉、保存并最终转换为 Tkinter 形式的图片上的路径。如何操作...

    • 注意,Inkscape 允许我们在不中断追踪线条动作的情况下移动图片和放大或缩小。然后我们可以开始点击图像中选定路径上的点,并将鼠标指针移到滚动条或缩放图标上,移动或点击它们。Tkinter 在指针位于绘图区域外时暂时挂起笔工具的动作。

      另一个方便的功能是,如果我们不小心在错误的位置点击鼠标,我们可以通过在键盘上按一次删除键("del")来擦除这个错误。这将撤销正在追踪的线条上的最后一个点击位置。

      如果我们希望重新定位完成线条上的任何点,可以使用位于左侧边栏工具栏第二行的点编辑工具来完成。

    如何操作...

  6. 在每个单独路径的最终点,必须双击笔工具。这结束了该特定路径的绘制,并将笔移开。对于下一条线,我们需要再次在工具栏上点击笔图标。

  7. 以下截图显示了感兴趣线条的完整追踪集:如何操作...

  8. 现在我们将我们的工作保存为 SVG 格式文件。

    要提取 SVG 路径以转换为 Tkinter 线条,我们只需打开一个文本编辑器,然后在编辑器中打开我们刚刚保存的 SVG 格式文件。这个文件是一个包含一些 SVG 代码的 XML 文本文件,正如本章第一道菜谱中解释的那样。我们感兴趣的部分是以下开始的线条:

    d="m 1 ...
    
    

    下一道菜谱提供了将 SVG 路径转换为 Tkinter 线条并显示以供确认的 Python 代码。

我们需要多频繁地点击鼠标?

一旦我们开始追踪线条的活动,我们会发现我们必须在多频繁地左击鼠标以创建新点方面进行判断。你将发现,使用许多点可以获得最佳精度,而使用最少的点可以获得最低的保真度。我们会惊讶地发现,只需要很少的点就可以以可接受的保真度表示我们的形状。

这是因为 Tkinter 平滑线函数中smooth='true'属性的魔力:如下一道菜谱所示,canvas_1.create_line(Q, fill='green', smooth='true')`。

从位图图像获取 SVG 路径的另一种方法

从位图图像获取 SVG 矢量代码的另一种方法是使用 Inkscape 的追踪路径和路径简化工具。

将 SVG 路径转换为 Tkinter 线条

我们将长而复杂的 Inkscape 追踪路径(SVG 编码)转换为 Tkinter 线条,可以使用如canvas.create_line(x0,y0, x1,y1, x2,y2, ...)这样的方法来显示。

以下程序以略微编辑过的 SVG 路径形式,将其转换为Tkintercreate_line()函数中可用的形式。

要做到这一点,我们需要交换分隔坐标对的单个空格字符,并用逗号替换它们。

同时,我们希望将 SVG 路径使用的增量坐标值转换为绝对值,通过将增量值加到相应的上一个值上。

准备工作

下面是一个 5 点线条的典型 SVG 路径:

d="m 128,258 0,137 148,0 0,-145 -148,8 z"

在文本编辑器中,将文本转换为列表形式非常简单,例如 a = "[128,258 0,137 148,0 0,-145 -148,8] "

这些数字列表可能长达数百行,因此我们希望自动化繁琐且易出错的替换每个空格为逗号,并随后进行将增量值替换为绝对值的算术运算。这正是代码所做的事情。

该程序使用 Inkscape 之前追踪的线条之一,并插入逗号,进行算术运算以获取用于 canvas.create_line(x0,y0, x1,y1, x2,y2, ...) 的坐标列表。

如何做...

按照常规方式执行以下代码。

# spaces_for_commas_svg2tkinter_1.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
fromTkinter import *
root = Tk()
root.title("Conversion of SVG paths to Tkintercreate_line()")
cw = 1000 # canvas width.
ch = 800 # canvas height.
canvas_1 = Canvas(root, width=cw, height=ch, background="white")
canvas_1.grid(row=0, column=1)
tkint_line = []
svg_line_coords = '1551.2964,83.663208 -92.9426,0 -64.2149,28.727712 -13.5189,32.10744\
37.177,43.9365 65.9048,27.03785 82.8034,5.06959 82.8034,-11.82906\
45.6264,-30.41757 -3.3798,-38.86691 -72.6642,-42.246629 -59.1453,-11.829058'
# replace each space with a comma. b is a string
Tkinter LineSVG path, converting tob = svg_line_coords.replace(' ', ',')
# separates string b, at each comma, into a list.
c = b.split(',')
# Convert string elements into a floating point number list.
p= len(c)
for i in range(0,p):
tkint_line.append(float(c[i]))
# Add incremental coordinates to the previous value
for i in range(0, p-2):
# Add the increment to the value two positions back
# because two positions separate each x and each y.
tkint_line[i +2] = tkint_line[i +2] + tkint_line[i]
# Scale it to a convenient size
for i in range(0,len(tkint_line)):
tkint_line[i] =int((tkint_line[i]+1)/ 2)
canvas_1.create_line(tkint_line, fill='green', smooth='true')
root.mainloop()

它是如何工作的...

为了使代码简单且简短,我们将 SVG 路径的略微编辑形式放入 Python 代码中,如以下行所示:

`a ='1551.2964,83.663208 ...'。

代码执行四个基本操作:

  • 它在 SVG 路径字符串中找到空格的地方放置逗号。

  • 它将单个字符串在每个逗号处分割成单独的字符串元素列表。

  • 它将每个元素转换为浮点数。

  • 它通过将每个元素加到前一个元素上两个位置进行算术运算。x 坐标与 y 坐标交替,因此要将 x 值加到前一个 x 值上,我们需要跳过中间的 y 值。

修改后的 SVG 路径被转换为可以直接在行 canvas_1.create_line(Q, fill='green', smooth='true') 中使用的 Python 列表,以在画布上绘制它。

还有更多...

当其他七个来自 table_glass_vase_inkscape.svg 的 Inkscaped 线条以相同方式转换时,我们得到以下截图所示的结果:

还有更多...

我们应该在图像转换代码上走多远?

我们已经尽力使代码简单且简短。我们本可以投入更多精力来自动化我们在文本编辑器中进行的轻微编辑,以删除 m 并在引号内适当位置放置方括号。

从位图图像获取 SVG 路径的另一种方法

从位图图像中提取 SVG 路径的另一种方法是使用 Inkscape 中的路径、追踪位图工具,然后是路径和简化工具。这种方法对于像我们这里使用的透明玻璃花瓶这样的复杂图像效果不佳。它最适合简单的黑白图像。Inkscape 工具基于另一个名为 potrace 的工具,它有一个名为 potracegui 的界面。potrace 工具的问题是你首先必须将你的图像转换为位图格式。我们在本章中使用的方法允许我们针对我们想要使用的特定线条做出非常具体的选择,无论图像多么复杂。

第十章。GUI 构建:第一部分

在本章中,我们将涵盖:

  • 小部件配置

  • 按钮焦点

  • 带有验证的最简单推按钮

  • 数据输入框

  • 引起消息弹出的彩色按钮

  • 按钮之间的复杂交互

  • 按钮和组件布局中的图像

  • 网格几何管理器和按钮数组

  • 从列表中选择的下拉菜单

  • 列表框

  • 窗口中的文本

简介

在本章中,我们提供了用于创建图形用户界面的组件的配方。这些被称为GUI图形用户界面。GUI 组件的常用术语是小部件。Widget 这个词没有特定的含义,除了“一般类型的装置”。如果你使用了第四章中的示例,在颜色混合调色板上的动画原理,那么你就使用了在本章中将要解释的滑块或刻度小部件。我们还将演示创建我们自己的小部件并不太难。

小部件配置:标签

我们在这里看到如何使用其configuration()方法更改大多数小部件的属性(属性)。

如何做到这一点...

以通常的方式执行显示的程序。

# widget_configuration_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk( )
labelfont = ('times', 30, 'bold')
widget = Label(root, text='Purple and red mess with your focus')
widget.config(bg='red', fg='purple')
widget.config(font=labelfont)
widget.config(bd=6)
widget.config(relief=RAISED)
widget.config(height=2, width=40) # lines high, characters wide
widget.pack(expand=YES, fill=BOTH)
root.mainloop( )

它是如何工作的...

所有小部件都有默认值,例如灰色背景和 12 点字体大小。一旦执行了创建小部件的代码,小部件就会带有所有分配的属性出现在屏幕上。在代码的下方,当程序正在执行时,可以使用widget.config(attribute=new value)方法更改小部件的属性。结果如下所示截图:

它是如何工作的...

更多...

选择是好的,因为它允许我们的 GUI 看起来很好。这种选择的缺点是它允许我们做出糟糕的选择。但正如谚语所说:用智慧做出的糟糕选择会导向好的选择。

如果我们运行这个程序,我们会看到所组合的颜色几乎是最糟糕的组合,因为这两种颜色具有不同的波长,并且在到达视网膜的过程中略微遵循不同的路径。

按钮焦点

在这里,我们演示了焦点概念,这比描述更容易展示。当窗口内有一组小部件时,只有一个小部件可以响应像鼠标按钮点击这样的事件。在这个例子中,鼠标光标下的按钮具有焦点,因此它是响应鼠标点击的那个按钮。当光标移到另一个按钮上时,那个按钮就具有焦点。在这个例子中,具有焦点的按钮在 Linux 操作系统中会改变颜色。在 MS Windows 中,按钮不会改变颜色,但鼠标光标会改变。

如何做到这一点...

以通常的方式执行显示的程序。

#button_focus_1.py
#>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
butn_widget_1 = Button(text='First, RAISED', padx=10, pady=10)
butn_widget_1.config(cursor='gumby')
butn_widget_1.config(bd=8, relief=RAISED)
butn_widget_1.config(bg='dark green', fg='white')
butn_widget_1.config(font=('helvetica', 20, 'underline italic'))
butn_widget_1.grid(row=1, column = 1)
butn_widget_2 = Button(text='Second, FLAT', padx=10, pady=10)
butn_widget_2.config(cursor='circle')
butn_widget_2.config(bd=8, relief=FLAT)
butn_widget_2.grid(row=1, column = 2)
butn_widget_3 = Button(text='Third, SUNKEN', padx=10, pady=10)
butn_widget_3.config(cursor='heart')
butn_widget_3.config(bd=8, relief=SUNKEN)
butn_widget_3.config(bg='dark blue', fg='white')
butn_widget_3.config(font=('helvetica', 30, 'underline italic'))
butn_widget_3.grid(row=1, column = 3)
butn_widget_4 = Button(text='Fourth, GROOVE', padx=10, pady=10)
butn_widget_4.config(cursor='spider')
butn_widget_4.config(bd=8, relief=GROOVE)
butn_widget_4.config(bg='red', fg='white')
butn_widget_4.config(font=('helvetica', 20, 'bold'))
butn_widget_4.grid(row=1, column = 4)
butn_widget_5 = Button(text='Fifth RIDGE', padx=10, pady=10)
butn_widget_5.config(cursor='pencil')
butn_widget_5.config(bd=8, relief=RIDGE)
butn_widget_5.config(bg='purple', fg='white')
butn_widget_5.grid(row=1, column = 5)
root.mainloop( )

它是如何工作的...

当我们在 Linux 下运行前面的代码时,我们会看到每个按钮的颜色随着它获得焦点而改变。获得焦点的按钮是这一组中唯一会响应左键点击的按钮。在 Windows 7 下,这种带焦点的颜色变化不起作用。尽管如此,焦点行为和鼠标事件反应的逻辑并未受到影响。

我们还趁机查看可用的不同按钮边框样式。

它是如何工作的...

还有更多...

在这个例子中需要注意的一点是,按钮的大小由字体大小和放置在按钮上的文本量决定。

带验证的最简单的按钮

我们现在聚焦于通过 callback() 函数进行事件处理的最简单示例。

之前提到的验证是指任何提供确认我们代码做了我们想要它做的事情的反应。当你以实验性方式开发代码时,你需要在最早阶段就进行某种验证,以便建立洞察力。

如何做到这一点...

复制、保存并执行。结果如下所示:

如何做到这一点...

# button_1.py
push buttonwith validation#>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
def callback_1(): # The event processor function
print "Someone pushed a button"
# The instantiation (i.e. creation of a specific instance or # realization) of a button.
button_1= Button(root, command=callback_1).grid(row=1, column=0)
root.mainloop()

它是如何工作的...

当你用鼠标指针点击小按钮时,你的终端上会出现一条消息。消息的出现是程序产生的关键验证操作。

这个简单的例子展示了所有响应用户输入的程序的基本设计。然后你只需要做以下事情:

  • 等待某些外部事件,例如鼠标点击或键盘上的按键。

  • 如果外部事件发生,我们必须在我们的程序中有一个 事件处理程序 函数,该函数指定必须执行的操作。这些通常被称为 callback 函数。

在代码内部,创建任何设计用于接受用户输入的小部件的实例时,必须始终有一个选项指定符,如 command=callback_1,它指向名为 callback_1 的事件处理函数,该函数将在事件发生时执行我们希望它执行的所有操作。我们不必使用实际的单词 callback_1 - 我们可以选择任何我们喜欢的单词。在这种情况下,事件是按钮的点击。我们要求它在 callback() 函数内部做的所有事情只是打印一条消息。然而,由我们的 callback() 函数引发的行动列表可以像我们喜欢的那样长。

还有更多...

编程文献经常使用“实例化”这个词,尤其是在面向对象编程的上下文中提及对象时。这个词的意思是将某个对象从之前仅存在的半抽象描述转变为一个实际的代码块,该代码块具有一个真实的命名空间,其变量与程序内部的数据和命令进行交互。Python 与 Tkinter 有一个预定义的对象,称为按钮。在我们的上一个程序中,我们通过以下命令将一个名为 button_1 的按钮实例化出来:

button_1= Button(root, command=callback_1).grid(row=1, column=0)

等号右侧的描述是从 Tkinter 库内部的长列表对象中提取的现有抽象描述。左侧的button_1名称是实例的名称,该实例将具有所有之前只是库中文字的实际属性。这就像有一个带有工程图纸和赛车组装说明的文件(抽象描述),然后让某个工程车间实际制造一个闪亮的钢制和铬制跑车实例。带有金属蓝色漆面的东西,你将坐在里面,随风驾驶,是对象的实例。这个带有图纸和制造说明的文件相当于我们 Python 代码中的对象定义。那个金属蓝色的东西,你将坐在里面,随风驾驶,是对象的实例。

按钮在 Windows 上的行为不同

在此配方中,按钮在 MS Windows 中的行为与 Linux 略有不同。Windows 在包含按钮的框架右上角显示正常的最小化、最大化、关闭符号。我们通过点击右上角的“X”符号来关闭应用程序。在 Linux 中,框架顶部有一个圆形按钮。当我们点击这个按钮时,会弹出一个菜单,其中包含一个关闭命令,可以结束程序。

数据输入框

我们创建一个 GUI,它提供了一个数据输入框和一个按钮来处理输入框中输入的任何文本。

Entry小部件是 Tkinter 的标准小部件,用于输入或显示单行文本。

按钮的callback()函数(事件处理器)将文本框的内容分配给变量的值。所有这些操作都通过显示这个变量的值来验证。

如何操作...

以正常方式执行显示的程序。

# entry_box_1.py
#>>>>>>>>>>>>>>>
from Tkinter import *
from Dialog import Dialog
root = Tk()
root.title("Data Entry Box")
enter_data_1 = Entry(root, bg = "pale green") # Creates a text entry
# field
enter_data_1.grid(row=1, column=1)
enter_data_1.insert(0, "enter text here") # Place text into the box.
def callback_origin():
# Push button event handler.
data_inp_1 = enter_data_1.get() # Fetch text from the box.
# Create a label and write the value of 'data_inp_1' to it.
# ie. Validate by displaying the newly acquired data as a label on
# the frame.
label_2_far_right = Label(root, text=data_inp_1)
label_2_far_right.grid(row=1, column=3)
# This is button that triggers data transfer from entry box to named
# variable 'data_inp_1'.
but1= Button(root, text="press to \
transfer",command=callback_origin).grid(row=5, column=0)
root.mainloop()

如何工作...

如何工作...

单独的文本输入框并没有太大的用处。它就像一个邮筒,文本可以发送到它或从它那里取走。

此程序执行以下操作:

  • 它设置了一个名为root的父框架或窗口,其中包含一个标签按钮和一个显示初始消息“在此输入文本”的文本框。

  • 我们可以点击输入框,并用新文本替换初始文本。

  • 如果我们点击按钮,它将框中的内容作为变量data_inp_1的值。

  • 它将data_inp_1的值显示为文本框右侧的标签。

更多...

按钮执行有用功能的关键在于放置在callback()函数中的代码,该函数在按钮被按下时执行。

编程按钮可能会变得非常复杂,我们很容易被自己的独创性所困惑。规则是保持简单。

你可以在框架内的同一位置定位多个按钮,可见的按钮是 Python 程序最后放置的那个按钮。

之后,我们可以制作一组按钮,当开启时看起来“发光”,当关闭时看起来“暗淡”。做这些事情很有趣,但要注意不要过于聪明。一位非常聪明和明智的程序员说过以下的话:

—Brian W. Kernighan,C 编程语言合著者。

我们是否保持了事情简单?

在本章第六个名为“按钮之间的复杂交互”的菜谱中,我们忽略明智的建议,只是为了探索可能的可能性。我们这样做是为了自己的教育和乐趣,但对于任何专业工作都应该避免。

单行与多行输入

这里使用的部件被称为输入部件,仅用于单行输入。还有一个名为文本的部件,它被设计用于多行输入。本章后面将有一个如何使用此部件的示例。

聪明的几何管理器

注意在程序执行过程中,父窗口的大小如何改变以适应标签文本的大小。这是一个非常智能的程序设计。

带有消息弹出的彩色按钮

按钮可以被赋予不同的视觉属性和复杂的行为。在这里,我们创建了一个蓝色的凸起按钮,当用鼠标点击时,其外观会发生变化。当按钮被按下时,会弹出一个消息框小部件。

如何去做...

以正常方式执行显示的程序。

# button_message_1.py
#>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import tkMessageBox
root = Tk()
root.title("Message Button")
def callback_button():
tkMessageBox.showinfo( "Certificate of Button Pushery", \ "Are glowing pixels a reality?")
message_button = Button(root,
bd=6, # border width
relief = RAISED, # raised appearance # to button border
bg = "blue", # normal background
# color
fg = "green", # normal foreground
# (text) color
font = "Arial 20 bold",
text ="Push me", # text on button
activebackground = "red", # background when
# button is clicked
activeforeground = "yellow", # text color when
# clicked
command = callback_button) # name of event
# handler
message_button.grid(row=0, column=0)
root.mainloop()

它是如何工作的...

如何工作...如何工作...

我们现在看到的是,按钮非常可定制,正如 Tkinter 中的许多小部件一样。这个菜谱说明了你作为 GUI 程序员肯定会遇到的另一个术语,那就是“焦点”这个词。

重点是当有多个小部件在一个图形容器上时,一次只能给予一个关注或监听。每个按钮都被编程为响应鼠标点击,但当鼠标点击时,只有一个按钮应该响应。响应的部件是程序所关注的部件。在我们的例子中,当鼠标指针移动到按钮上时,你会看到焦点被给予按钮(焦点被用来在 Linux 操作系统中改变按钮的颜色)。这就像主席向想要发表讲话的会议小组提供发言机会一样。有抱负的演讲者只能在主席提供发言机会(给予他们焦点)时才能这样做。当这种情况发生时,其他人预计要安静地礼貌地倾听。

按钮之间的复杂交互

在这个菜谱中,我们展示了如何通过获取一组三个相互修改的按钮,使按钮动作变得尽可能复杂。

如何去做...

以正常方式执行显示的程序。

# button_interaction_1.py
#>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("now what?")
def callback_button_1():
message_button_1.flash()
message_button_2["bg"]= "grey"
message_button_2.flash()
message_button_3.flash()
message_button_3["bg"]= "pink"
message_button_1["relief"] = SUNKEN
message_button_1["text"]= "What have you done?"
def callback_button_2():
message_button_2["bg"]= "green"
message_button_3["bg"]= "cyan"
message_button_1["relief"] = RAISED
message_button_1["text"]= "Beware"
def callback_button_3():
message_button_1.destroy()
message_button_2.destroy()
message_button_3.destroy()
root.destroy()
message_button_1 = Button(root,
bd=6,
relief = RAISED, # Raised # appearance.
bg = "blue" # Normal (without
# focus) # background
# color
fg = "green", # Normal (without
# focus) # foreground
# (text) color
font = "Arial 20 bold",
text ="Push me first", # Text on button
activebackground = "red", # Background when
# button has # focus
activeforeground = "yellow", #Text with focus
command = callback_button_1) # event handler
message_button_1.grid(row=0, column=0)
message_button_2 = Button(root,
bd=6,
relief = SUNKEN,
bg = "green",
fg = "blue",
font = "Arial 20 bold",
text ="Now Push me",
activebackground = "purple",
activeforeground = "yellow",
command = callback_button_2)
message_button_2.grid(row=1, column=0)
set of three buttonsmodifying, one anothermessage_button_3 = Button(root,
bd=6,
relief = SUNKEN,
bg = "grey",
fg = "blue",
font = "Arial 20 bold",
text ="kill everything",
activebackground = "purple",
activeforeground = "yellow",
command = callback_button_3)
message_button_3.grid(row=2, column=0)
root.mainloop()

如何工作...

如何工作...如何工作...

所有的动作都发生在事件处理程序(回调函数)中。每个实例化的对象,如这里使用的按钮,都有一个属性集合,如颜色、文本和外观,可以通过以下规范进行修改:message_button_2["bg"]= "grey"

所以,当按钮 1 被点击时,按钮 2 的背景颜色从绿色变为灰色。

虽然使用按钮动作创建非常复杂的交互式行为很有趣,但很快就会变得几乎不可能跟踪你想要的行为。你添加的复杂性越多,就会出现越多的意外行为。因此,最好的建议是尽量保持简单。

按钮上的图片和按钮打包

通过将 GIF 格式的图片放置在按钮上,我们可以创建任何期望的外观。图片可以传达关于按钮功能的有关信息。需要考虑图片的大小,并且要谨慎地使用几何管理器。

如何实现...

按照通常的方式执行显示的程序。

# image_button_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Image Sized Buttons")
go_image = PhotoImage(file = "/constr/pics1/go_on.gif")
fireman_image = PhotoImage(file = "/constr/pics1/fireman_1.gif")
winged_lion_image = PhotoImage(file = "/constr/pics1/winged_lion.gif")
earth_image = PhotoImage(file = "/constr/pics1/earth.gif")
def callback_go():
print "Go has been pushed to no purpose"
def callback_fireman():
print "A little plastic fireman is wanted"
def callback_lion():
print "A winged lion rampant would look cool on a t-shirt"
def callback_earth():
print "Think of the children (and therefore also of their parents)"
btn_go= Button(root, image = go_image, \
command=callback_go ).grid(row=0, column=0)
btn_firmean= Button(root, image = fireman_image, \
command=callback_fireman).grid(row=0, column=1)
btn_lion= Button(root, image = winged_lion_image, \
command=callback_lion ).grid(row=0, column=2)
btn_earth= Button(root, image = earth_image, \
command=callback_earth ).grid(row=0, column=3)
root.mainloop()

如何工作...

如何工作...

这里需要注意的事情是,网格几何管理器会尽可能整齐地将所有小部件打包在一起,无论小部件的大小如何。

还有更多...

Python 模块设计背后的一个美妙思想是,它们的行为应该是友好和宽容的。这意味着如果属性被编码为不合适的值,则解释器将选择默认值,至少是一种可能工作的选择。这对程序员来说是一个巨大的帮助。如果你遇到过 Python 开发者圈子中的任何成员,他们仅仅因为这个原因就值得得到一个亲切的拥抱。

网格几何管理器和按钮数组

通过将 GIF 格式的图片放置在按钮上,我们可以创建任何期望的外观。需要考虑图片的大小,并且要谨慎地使用几何管理器。

如何实现...

按照正常方式执行显示的程序。

# button_array_1.py
#>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Button Array")
usb = PhotoImage(file = "/constr/pics1/Usbkey_D.gif")
galaxy = PhotoImage(file = "/constr/pics1/galaxy_D.gif")
alert = PhotoImage(file = "/constr/pics1/alert_D.gif")
earth = PhotoImage(file = "/constr/pics1/earth_D.gif")
eye = PhotoImage(file = "/constr/pics1/eye_D.gif")
rnd_2 = PhotoImage(file = "/constr/pics1/random_2_D.gif")
rnd_3 = PhotoImage(file = "/constr/pics1/random_3_D.gif")
smile = PhotoImage(file = "/constr/pics1/smile_D.gif")
vine = PhotoImage(file = "/constr/pics1/vine_D.gif")
blueye = PhotoImage(file = "/constr/pics1/blueeye_D.gif")
winglion = PhotoImage(file = "/constr/pics1/winglion_D.gif")
def cb_usb(): print "usb"
def cb_galaxy(): print "galaxy"
def cb_alert(): print "alert"
def cb_earth(): print "earth"
def cb_eye(): print "eye"
def cb_rnd_2(): print "random_2"
def cb_rnd_3(): print "random_3"
def cb_smile(): print "smile"
GIF format imagesplacing, on button arraysdef cb_vine(): print "vine"
def cb_blueeye(): print "blueeye"
def cb_winglion(): print "winglion"
butn_usb = Button(root, image = usb, command=cb_usb \
).grid(row=0, column=0)
butn_galaxy = Button(root, image = galaxy, command=cb_galaxy).grid(row=1, column=0)
butn_alert = Button(root, image = alert, command=cb_alert \
).grid(row=2, column=0)
butn_earth = Button(root, image = earth, command=cb_earth \
).grid(row=3, column=0)
butn_eye = Button(root, image = eye, command=cb_eye \
).grid(row=0, column=1, rowspan=2)
butn_rnd_2 = Button(root, image = rnd_2, command=cb_rnd_2 \
).grid(row=2, column=1)
butn_rnd_3 = Button(root, image = rnd_3, command=cb_rnd_3 \
).grid(row=3, column=1)
butn_smile = Button(root, image = smile, command=cb_smile \
).grid(row=0, column=2, columnspan=2)
butn_vine = Button(root, image = vine, command=cb_vine \
).grid(row=1, column=2, rowspan=2, columnspan=2)
butn_blueye = Button(root, image = blueye, \
command=cb_blueeye).grid(row=3, column=2)
butn_winglion= Button(root, image = winglion, command=cb_winglion \
).grid(row=3, column=3)
root.mainloop()

它是如何工作的...

如何工作...

Tkinter 中有两种几何管理器。在这本书中,我们一直独家使用网格几何管理器,因为它可以降低复杂性,而且使用起来简单,还能直接控制界面布局。另一种布局几何管理器称为 pack,将在下一章中介绍。

规则很简单。我们的父窗口或框架被分成行和列。Row=0 是顶部的第一行,column=0 是左侧的第一列。columnspan=2 意味着使用此属性的控件位于两个相邻列的中心。请注意,带有藤蔓图标的按钮位于四个网格区域的中心,因为它同时具有 columnspan=2rowspan=2

还有更多...

通过更改此示例中的网格属性,您可以帮助自己获得对网格几何管理器的洞察。请花一些时间实验网格管理器,它将在您的编程努力中带来回报。

从列表中选择的下拉菜单

在这里,我们使用下拉菜单小部件作为从提供的几个选项中选择一个项的方法。

如何实现...

以通常的方式执行显示的程序。结果如下截图所示:

如何实现...

# dropdown_1.py
# >>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Drop-down boxes for option selections.")
var = StringVar(root)
var.set("drop down menu button")
def grab_and_assign(event):
chosen_option = var.get()
label_chosen_variable= Label(root, text=chosen_option)
label_chosen_variable.grid(row=1, column=2)
print chosen_option
drop_menu = OptionMenu(root, var, "one", "two", "three", "four", \ "meerkat", "12345", "6789", command=grab_and_assign)
drop_menu.grid(row=0, column=0)
label_left=Label(root, text="chosen variable= ")
label_left.grid(row=1, column=0)
root.mainloop()

它是如何工作的...

下拉菜单有自己的按钮。当点击此按钮时被调用的 callback() 函数在此特定菜谱中命名为 grab_and_assign,此事件服务程序中的一条指令是将所选菜单项的值分配给变量 chosen_option。执行此操作的指令是 chosen_option = var.get()

如我们之前所做的那样,我们通过在父窗口上打印 chosen_option 的新值作为标签来确保一切按预期工作。

列表框变量选择

列表框是一个以列表形式显示选择项的控件。可以通过在列表项上点击鼠标光标来选择列表中的项。

如何实现...

以通常的方式执行显示的程序。

# listbox_simple_1.py
#>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Listbox Data Input")
def get_list(event):
# Mouse button release callback
# Read the listbox selection and put the result in an entry box
# widget
index = listbox1.curselection()[0] # get selected line index
seltext = listbox1.get(index) # get the line's text & # assign
# to a variable
enter_1.delete(0, 50) # delete previous text in
# enter_1 otherwise the # entries
# append to each other.
enter_1.insert(0, seltext) # now display the selected # text
# Create the listbox (note that size is in characters)
listboxitem, selecting fromlistbox1 = Listbox(root, width=50, height=6)
listbox1.grid(row=0, column=0)
# Fill the listbox with data
listbox1.insert(END, "a list entry")
for item in ["one has begun", "two is a shoe", "three like a knee", \
"four to the door"]:
listbox1.insert(END, item)
# use entry widget to display/edit selection
enter_1 = Entry(root, width=50, bg='yellow')
enter_1.insert(0, 'Click on an item in the listbox')
enter_1.grid(row=1, column=0)
# left mouse click on a list item to display selection
listbox1.bind('<ButtonRelease-1>', get_list)
root.mainloop()

它是如何工作的...

如何工作的...

命名为 listbox1 的列表框被创建并放置在 Tkinter 窗口中。它使用 for 循环填充了五个字符串项。

当鼠标光标点击一个项时,get_list 函数将该项作为变量 seltext 的值。此变量的值显示在黄色输入框中。

窗口中的文本

这是一个在窗口中放置文本的简单方法。没有提供与文本交互的选项。

如何实现...

以通常的方式执行显示的程序。

# text_in_window_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Text in a window")
text_on_window = Text(root)
text_on_window.grid(row=0, column=0)
for i in range(20):
text_on_window.insert(END, "Fill an area with some text: line %d\n"\
% i)
root.mainloop()

它是如何工作的...

通过 Text(root) 方法创建 Text 控件,并通过 insert(…) 函数将文本放置其中。END 属性将每行新文本放置在上一行的末尾。

第十一章:GUI 构建:第二部分

在本章中,我们将涵盖:

  • 网格布局几何管理器

  • 打包几何管理器

  • 单选按钮从多个选项中选择一个

  • 复选框(勾选框)从多个选项中选择一些

  • 按键事件处理

  • 滚动条

  • 框架

  • 自定义 DIY 控制器小部件(一个更瘦的滑块)

简介

在本章中,我们提供了更多关于图形用户界面(GUI)的食谱。上一章中的食谱被设计为运行时与你的代码交互的基本方式。在本章中,我们扩展了这些想法并试图将它们结合起来。

我们首先探索两个布局几何管理器的特性。在整个这本书中,直到本章,我们一直使用网格管理器,因为它似乎给我们提供了对 GUI 外观的最大控制。

当我们编写使用小部件的 Tkinter 代码时,我们必须做出的一个选择是如何在我们包含它们的主小部件内部安排这些小部件。有两个布局几何管理器可供选择:打包和网格。打包管理器是最容易使用的,直到你有了自己关于如何在家居中安排家具的想法,家具和房屋是对于小部件和包含小部件的有用隐喻。网格管理器给你提供了对布局的绝对控制。

网格布局几何管理器

我们查看以计划的方式布局 16 个标签按钮的代码。根据每个按钮上的标签,它只应该在北、南、东、西参考系统中的一个位置。

准备工作

网格和打包都有导航参考方案。从我们的 GUI 将如何显示的角度来理解,最简单的方法是使用网格,它使用清晰的行、列方案来指定我们的小部件位置,如下面的截图所示:

准备工作

如何做...

以通常的方式执行程序。结果如下面的截图所示:

如何做...

# grid_button_array_1.py
#>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Pack Geometry Manager")
butn_NW = Button(root, bg='blue',text="NorthWest").grid(row=0, \ column=0)
butn_NW1 = Button(root, bg='blue',text="Northwest").grid(row=0, \ column=1)
butn_NE1 = Button(root, bg='blue',text="Northeast").grid(row=0, \ column=2)
butn_NE = Button(root, bg='blue',text="NorthEast").grid(row=0, \ column=3)
butn_N1W = Button(root, bg='sky blue',text="norWest").grid(row=1, \ column=0)
Grid Layout Geometry Managerexamplebutn_N1W1 = Button(root, bg='sky blue',text="norwest").grid(row=1, \ column=1)
butn_S1E1 = Button(root, bg='pale green',text="soueast").grid(row=1, column=2)
butn_S1E = Button(root, bg='pale green',text="souEast").grid(row=1, column=3)
butn_SW = Button(root, bg='green',text="SouthWest").grid(row=2, \column=0)
butn_SW1 = Button(root, bg='green',text="SothuWest").grid(row=2, \ column=1)
butn_SE1 = Button(root, bg='green',text="SouthEast").grid(row=2, \ column=2)
butn_SE = Button(root, bg='green',text="SouthEast").grid(row=2, \ column=3)
root.mainloop()

它是如何工作的...

网格布局管理器在解释布局指令时是明确的。没有歧义,结果容易理解。幸运的是,对于我们用户来说,Python 语言的一个根深蒂固的哲学是,在可能的情况下,解释器应该对轻微的编程疏忽表现出友好和宽容。例如,假设我们给所有按钮分配了相同的网格布局地址。例如,假设我们将所有按钮分配为grid(row=5, column=5)。结果看起来像窗口中只有一个按钮。实际上,布局管理器会将所有按钮堆叠在一起,第一个在底部,最后一个在顶部。如果我们按相反的顺序逐个销毁它们,我们会看到这个序列展开。

更多内容...

只需记住,我们永远不会在同一个程序中混合使用包和网格布局管理器。如果您这样做,您的程序将冻结,因为每个管理器都试图遵守冲突的指令。

包布局管理器

我们试图实现前一个截图所示的结果,但并不完全成功,因为包试图将小部件排列成一条单行。包提供的有限灵活性在于它允许我们决定条带应该从哪里开始。

准备中

包布局管理器使用导航者指南针方案,如下所示:

准备中

如何操作...

按照常规方式执行显示的程序。结果如下截图所示:

如何操作...

# pack_button_array_1.py
#>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Pack Geometry Manager")
butn_NW = Button(root, bg='blue',text="NorthWest").pack(side=LEFT)
butn_NW1 = Button(root, bg='blue',text="Northwest").pack(side=LEFT)
butn_NE1 = Button(root, bg='blue',text="Northeast").pack(side=LEFT)
butn_NE = Button(root, bg='blue',text="NorthEast").pack(side=LEFT)
butn_N1W = Button(root, bg='sky blue',text="norWest").pack()
butn_N1W1 = Button(root, bg='sky blue',text="norwest").pack()
butn_S1E1 = Button(root, bg='pale green',text="soueast").pack(side=BOTTOM)
Pack Geometry Managerexamplebutn_S1E = Button(root, bg='pale green',text="souEast").pack(side=BOTTOM)
butn_SW = Button(root, bg='green',text="SouthWest").pack(side=RIGHT)
butn_SW1 = Button(root, bg='green',text="SothuWest").pack(side=RIGHT)
butn_SE1 = Button(root, bg='green',text="SouthEast").pack(side=RIGHT)
butn_SE = Button(root, bg='green',text="SouthEast").pack(side=RIGHT)
root.mainloop()

它是如何工作的...

包布局管理器将小部件排列成行或列。如果我们尝试同时做这两件事,结果将难以预测,如前一个截图所示。

它所做的是从一个可能指定的边缘开始,然后按照它们在代码中出现的顺序,一个接一个地排列小部件。如果您没有指定开始边缘,默认为顶部,因此小部件将作为一个单独的列排列。

也有一些参数指定小部件是否应该填充到可用空间。我们可以从这个细节中获取:

effbot.org/tkinterbook/pack.htm

单选按钮从多个选项中选择一个

我们使用单选按钮从一组选择中做出一个选择。集合中的每个按钮都与同一个变量相关联。当鼠标左键点击某个按钮时,与该特定按钮关联的值被分配为变量的值。

单选按钮从多个选项中选择一个

如何操作...

按照常规方式执行显示的程序。

# radiobuttons_1.py
#>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk( )
root.title("Radiobuttons")
var_1 = StringVar( )
rad_1 = Radiobutton(root, text='violent', variable = var_1, \ value="action").grid(row=0, column=0)
rad_2 = Radiobutton(root, text='love', variable = var_1, \ value="romance").grid(row=0, column=1)
rad_2 = Radiobutton(root, text='conflict', variable = var_1, \ value="war").grid(row=0, column=2)
def callback_1():
v_1 = var_1.get()
print v_1
button_1= Button(root, command=callback_1).grid(row=4, column=0)
root.mainloop()

它是如何工作的...

我们指定了一个特殊的 Tkinter 字符串变量,我们将其命名为var_1。我们可以根据哪个单选按钮被点击分配三个可能的字符串值。一个普通按钮用于显示var_1在任何时刻的值。

复选框(勾选框)从多个选项中选择一些

勾选框始终有一个值。它们与单选按钮相反,允许从一组中选择多个选项。每个勾选框都与一个不同的变量名相关联。

复选框(勾选框)从多个选项中选择一些

如何操作...

按照常规方式执行显示的程序。

# checkbox_1.py
#>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import tkMessageBox
root = Tk()
root.title("Checkboxes")
check_var1 = IntVar()
check_var2 = IntVar()
check_var3 = StringVar()
check_var4 = StringVar()
def change_it():
print "Why do you want to change things?"
Ck_1 = Checkbutton(root, text = "Dog", variable = check_var1, \ command=change_it, \
onvalue = 1, offvalue = 0, height=3, \
width = 10).grid(row=0, column=0)
Ck_2 = Checkbutton(root, text = "Cat", variable = check_var2, \
onvalue = 1, offvalue = 0, height=6, \
width = 10).grid(row=1, column=0)
Ck_3 = Checkbutton(root, text = "Rat", variable = check_var3, \
onvalue = "fly me", offvalue = "keep walking", \ height=9, \
width = 10).grid(row=2, column=0)
Ck_4 = Checkbutton(root, text = "Frog", variable = check_var4, \
onvalue = "to the moon", offvalue = "to Putney road", \height=12, \
width = 10).grid(row=3, column=0)
def callback_1():
v_1 = check_var1.get()
v_2 = check_var2.get()
v_3 = check_var3.get()
v_4 = check_var4.get()
print v_1, v_2, v_3, v_4
button_1= Button(root, command=callback_1).grid(row=4, column=0)
root.mainloop()

它是如何工作的...

有四个勾选框(勾选框),因此有四个变量。其中两个是整数,两个是字符串。每当底部按钮被点击时,所有四个值都会显示出来。

键击事件处理

在图形用户界面术语中,事件处理器是指当外部事件(如按键或鼠标点击)发生时执行的功能的术语。本书中使用的等效术语是回调函数。我们通过函数定义中包含的单词event来识别回调函数。

在这里,我们创建了一个对按键做出反应的事件处理器。

如何操作...

按照常规方式执行程序。

# keypress_1.py
#>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Key symbol Getter")
def key_was_pressed(event):
print 'keysym=%s' % (event.keysym)
text_1 = Text(root, width=20, height=5, highlightthickness=15)
text_1.grid(row=0, column=0)
text_1.focus_set()
root.mainloop()

如何工作...

我们创建了一个文本框来显示当键盘“事件”发生时按下的哪个键。我们选择在文本框内显示和验证我们的按键,该文本框不会对功能键按下做出反应。如果我们使用标签小部件代替,我们会看到预期的function键显示。换句话说,函数event.keypress正确地感知了所有按键,即使它们不是由正常字符表示。

滚动条

滚动条提供了一种使用鼠标控制的滑块在更大的图像或文本区域中移动查看窗口的方法。它可以与列表框、画布、输入小部件或文本小部件一起使用。在这个例子中,我们使用垂直滚动条在滚动条的查看窗口后面上下移动 GIF 图像。

如何操作...

需要在画布和滚动条之间建立双向连接:

  • 画布的yscrollcommand选项必须连接到垂直滚动条的.set方法,并且

  • 滚动条的command选项必须连接到画布的.yview方法。

按照常规方式执行显示的程序。

scrollbar_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
frame_1 = Frame(root, bd=2, relief=SUNKEN)
frame_1.grid(row=0, column=0)
pic_1 = PhotoImage(file="/constr/pics1/table_glass_vase.gif")
yscrollbar = Scrollbar(frame_1, orient=VERTICAL, \ bg="skyblue",activebackground="blue")
yscrollbar.grid(row=0, column=1, sticky=N+S)
canvas_1 = Canvas(frame_1, bd=0, scrollregion=(0, 0, 2100, 2000), # The extent of the scrollable area.
yscrollcommand=yscrollbar.set, # Link to the # scrollbar.
)
canvas_1.grid(row=0, column=0)
canvas_1.create_image(0 ,0 ,anchor=NW, image= pic_1)
yscrollbar.config(command=canvas_1.yview) # Link to the #canvas.
mainloop()

它是如何工作的...

画布和滚动条之间的双向连接是通过canvas_1 = Canvas(…配置命令中的yscrollcommand=yscrollbar.set选项和在滚动条配置选项yscrollbar.config(command=canvas_1.yview)中实现的。

在 Python 中,我们不能在定义变量之前引用它,这就是为什么在yscrollbar声明之前不能使用yscrollbar.config语句的原因。

更多...

上述示例为了简单起见,只有垂直滚动条。如果我们想包括水平滚动条,我们会插入以下语句:

xscrollbar = Scrollbar(frame_1, orient=HORIZONTAL, bg="orange",activebackground="red")
xscrollbar.grid(row=1, column=0),
canvas_1 = Canvas(frame_1, bd=0, scrollregion=(0, 0, 2100, 2000), # The extent of the area across which can be scrolled.
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set,

在画布声明之后,添加以下代码行:

xscrollbar.config(command=canvas_1.xview)

定制 DIY 控制器小部件

我们从画布上的基本图形元素构建自己的小部件。Tkinter 提供的现有滑动控制小部件有时看起来有点大而笨重。如果我们需要一个更整洁、紧凑的滑动式用户输入设备,我们可以自己制造。

这里所做的选择是将基本滑块功能作为图形和文本元素组装在 Tkinter 画布上。

定制 DIY 控制器小部件

如何操作...

在以下代码中,我们看到三组相似的代码,由双行和回调函数分隔,该函数根据名为focus_flag的变量的值关注三个段中的其中一个。按照常规方式执行显示的程序。

# mini_slider_widget_1.py
#>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import math
root = Tk()
root.title("A 3 color linear slider control gadget")
cw = 200 # canvas width
ch = 200 # canvas height
chart_1 = Canvas(root, width=cw, height=ch, background="#ffffff")
chart_1.grid(row=1, column=1)
#============================================
# Mini slider canvas widget
focus_flag = 0 # 0-> uncommited, 1 -> slider #1, 2 -> slider #2 etc.
x_1 = 50 # Position of slider #1 base.
y_1 = 150
x_2 = 80 # Position of slider #2 base.
y_2 = 150
x_3 = 110 # Position of slider #3 base.
y_3 = 150
length_1 = 100 # Length of slider #1 (pixels) - constant.
length_2 = 110
length_3 = 120
slide_1 = y_1 # Position of slider handle #1 - variable.
slide_2 = y_2
slide_3 = y_3
#==============================================
def separation(x_now, y_now, x_dot, y_dot): # distance # measurement
# Distance to points - used to find out if the mouse clicked # inside a circle
sum_squares = (x_now - x_dot)**2 + (y_now -y_dot)**2
distance= int(math.sqrt(sum_squares)) # get #pythagorean distance
widgetsconstructingreturn( distance)
#==============================================
def canv_slider(xn, yn, length, kula):
# Draw the background slider gadgets.
y_top = yn -length
chart_1.create_line(xn, yn, xn, y_top, fill="gainsboro", width = 6)
chart_1.create_rectangle(xn - 5, yn -3, xn + 5, yn + 3, fill=kula, tag="knob_active")
chart_1.create_text(xn, yn + 10, text='zero',font=('verdana', 8))
chart_1.create_text(xn, y_top - 10, text='max',font=('verdana', 8))
canv_slider(x_1, y_1, length_1, "red")
canv_slider(x_2, y_2, length_2, "green")
canv_slider(x_3, y_3, length_3, "blue")
#==============================================
def dyn_slider(xn, yn, slide_val, kula, tagn):
# Draw the dynamic slider position.
chart_1.delete(tagn)
chart_1.create_line(xn, yn, xn, slide_val, fill=kula, width=4, tag =tagn)
chart_1.create_rectangle(xn - 5, slide_val -3 , xn + 5,slide_val + 3, fill=kula, tag=tagn)
chart_1.create_text(xn + 15, slide_val, text=str(slide_val), font=('verdana', 6),tag =tagn)
#==============================================
def callback_1(event):
# LEFT CLICK event processor.
global x_1, y_1, x_2, y_2, x_3, y_3, focus_flag
global slide_1, slide_2, slide_3
# Measure distances to identify which point has been clicked on.
d1 = separation(event.x, event.y, x_1, slide_1)
d2 = separation(event.x, event.y, x_2, slide_2)
d3 = separation(event.x, event.y, x_3, slide_3)
if d1 <= 5:
focus_flag = 1
if d2 <= 5:
focus_flag = 2
if d3 <= 5:
focus_flag = 3
def callback_2(event):
widgetsconstructing# LEFT DRAG event processor.
global length_1, length_2, length_3
global x_1, y_1, x_2, y_2, x_3, y_3, focus_flag
global slide_1, slide_2, slide_3
pos_x = event.x
slide_val = event.y
if focus_flag == 1 and slide_val <= y_1 and slide_val >= y_1 - length_1\
and pos_x <= x_1 + 10 and pos_x >= x_1 - 10:
dyn_slider(x_1, y_1, slide_val, "red", "slide_red")
slide_1 = slide_val
if focus_flag == 2 and slide_val <= y_2 and slide_val >= y_2 - length_2\
and pos_x <= x_2 + 10 and pos_x >= x_2 - 10:
dyn_slider(x_2, y_2, slide_val, "green", "slide_green")
slide_2 = slide_val
if focus_flag == 3 and slide_val <= y_3 and slide_val >= y_3 - length_3\
and pos_x <= x_3 + 10 and pos_x >= x_3 - 10:
dyn_slider(x_3, y_3, slide_val, "blue", "slide_blue" )
slide_3 = slide_val
#==============================
chart_1.bind("<Button-1>", callback_1)
chart_1.bind("<B1-Motion>", callback_2)
root.mainloop()

如何工作...

这是一个数值输入小部件数组,它使用彩色条的长度以及数值读数来向用户提供反馈。

函数callback_1对鼠标左键的点击做出反应,而callback_2在按钮按下时对鼠标拖动做出响应。通过测量鼠标左键点击时的鼠标位置来确定由鼠标左键控制的是哪三组控制。这个测量是通过函数separation(x_now, y_now, x_dot, y_dot)完成的。它测量鼠标点击位置与每个滑块控制矩形的距离。如果它很近(在 5 像素以内)接近一个控制矩形,那么focus_flag的值将被设置为与该位置关联的整数。

它的工作原理与官方 Tkinter 的刻度/滑块小部件类似。

当您想在画布上放置一个幻灯控制器时,这很有用。

它们占用的屏幕面积比 Ttkinter 刻度小部件少。

更多...

如果我们只需要一个画布滑块小部件而不是三个,那么注释掉或删除处理两个小部件的任何代码行就是一个简单的问题。

在框架内组织小部件

我们使用 Tkinter 框架将相关的小部件组合在一起。当我们这样做之后,我们只需要考虑我们希望框架如何排列,因为它们的内容已经被处理好了。

在框架内组织小部件

如何操作...

以通常的方式执行程序。

# frame_1.py
#>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.config(bg="black")
root.title("It's a Frame-up")
#================================================
# frame_1 and her motley little family
frame_1 = Frame(root, bg="red", border = 4, relief="raised")
frame_1.grid(row=0, column=0, columnspan=2)
redbutton_1 = Button(frame_1, text="Red",bg ="orange", fg="red")
redbutton_1.grid(row=0, column=1)
greenbutton_1 = Button(frame_1, text="Brown",bg ="pink", fg="brown")
greenbutton_1.grid(row=1, column=2)
bluebutton_1 = Button(frame_1, text="Blue",bg ="yellow", fg="blue")
bluebutton_1.grid(row=0, column=3)
#================================================
# frame _2 and her neat blue home
frame_2 = Frame(root, bg="blue", border = 10, relief="sunken")
frame_2.grid(row=1, column=0)
redbutton_2 = Button(frame_2, text="Green",bg ="brown", fg="green")
redbutton_2.grid(row=0, column=1)
greenbutton_2 = Button(frame_2, text="Brown",bg ="green", fg="brown")
greenbutton_2.grid(row=2, column=2)
bluebutton_2 = Button(frame_2, text="Pink",bg ="gray", fg="black")
bluebutton_2.grid(row=3, column=3)
#================================================
# frame_3 with her friendly green home
frame_3 = Frame(root, bg="green", border = 20, relief="groove")
frame_3.grid(row=1, column=1)
redbutton_3 = Button(frame_3, text="Purple",bg ="white", fg="red")
redbutton_3.grid(row=0, column=3)
greenbutton_3 = Button(frame_3, text="Violet",bg ="cyan", fg="violet")
greenbutton_3.grid(row=2, column=2)
bluebutton_3 = Button(frame_3, text="Cyan",bg ="purple", fg="blue")
bluebutton_3.grid(row=3, column=0)
root.mainloop()

它是如何工作的...

框架的位置是相对于“根”窗口来指定的。

在每个框架内部,属于该框架的小部件都是按照不参考该框架外部任何内容的方式进行排列的。

例如,指定redbutton_1.grid(row=0, column=1)red_button放置在网格几何中的row=0column=1,这是红色框架frame_1的宇宙。红色按钮对框架外的世界一无所知。

更多...

第一次,我们将根 Tkinter 窗口的背景颜色从默认的灰色改为黑色。

附录 appA. 在 Microsoft Windows 中运行 Python 程序的快速提示

在 Microsoft Windows 中运行 Python 程序

在 Linux 操作系统上,Python 通常已经安装。它已经安装了 Tkinter、math 和其他许多库。您不需要修改任何系统搜索路径变量,如Path,来运行 Python。

Microsoft Windows 可能会抛出一些障碍,但克服它们并不太难。如果 Python 版本是 2.7,Python Windows 安装程序将在 Windows 目录C:\Python27中安装它需要的所有东西。Python 版本 2.6 将被存储在C:\Python26

我们在哪里可以找到 Windows 安装程序?

我们可以在www.python.org/download/找到它。当www.python.org/download/页面打开时,选择Python 2.7 Windows 安装程序(Windows 二进制文件不包含源代码)

这将下载一个名为Python-2.7.msi的文件到我们的 Windows 下载文件夹。我们只需双击此文件,Python 2.7 版本就会自动安装到我们的系统中的C:\Python27

我们必须使用 Python 2.7 版本吗?

不,这本书中的代码应该在 Python 版本 2.4、2.5、2.6 和 2.7 上运行。它已经被不同版本的人运行过。如果不修改新 Python 语法的要求,它将无法在 Python 3.0 及以上版本上运行。例如,print 必须改为 print(要打印的内容)。

为什么会出现"python 未识别…"?

这是因为当你在命令窗口中输入python时,Windows 操作系统不知道在哪里找到 Python,如下面的截图所示:

为什么会出现"python 未识别…"?

有三种方法可以解决这个问题:

  1. 输入 python 和我们要运行的程序的全路径名称。在这个例子中,我们使用了名为entry_box_1.py的 python 程序。它被存储在名为constr的文件夹中,正如第一章中第一个示例运行简短的 Python 程序中所述。下面的截图显示了命令行对话框。george是登录到 Windows 的用户名。为什么会出现"python 未识别…"?

  2. Python27文件夹内工作。我们执行的操作是cd.. 和 cd.. 再次。然后进入文件夹Python27。然后我们可以在命令行中输入python \constr\entry_box_1.py,如下面的截图所示:为什么会出现"python 未识别…"?

  3. 修改 Windows 系统变量,告知 Windows 在哪里搜索可执行文件。我们通过在命令行窗口中输入set PATH=%PATH%;C:\Python27来完成此操作。从现在起,我们只需在任何文件夹中输入python \constr\entry_box_.py即可。实现这一点的对话框如下所示:

    截图:

posted @ 2025-09-18 14:36  绝不原创的飞龙  阅读(37)  评论(0)    收藏  举报