本教程适用于 Processing 的 Python 模式。如果您发现任何错误或有建议,请告诉我们。本教程改编自 Daniel Shiffman 所著的《Learning Processing》一书,由 Morgan Kaufmann Publishers 出版,版权所有 © 2008 Elsevier Inc. 保留所有权利。
如果你想在Processing中在屏幕上显示文本,你必须首先熟悉Python内置的字符串类。字符串对你来说可能不是一个全新的概念,很可能你之前已经处理过它们。例如,如果你曾经向消息窗口打印过一些文本或从文件加载过图像,你就写过这样的代码:
print("printing some text to the message window!") # 打印字符串
img = loadImage("filename.jpg") # 使用字符串作为文件名
然而,尽管你可能在这里和那里使用过字符串,现在是时候释放它们的全部潜力了。
虽然从技术上讲是Python类,但由于字符串使用如此频繁,Processing.py在其参考文档中包含了文档。在这里你会找到像通用字符串类这样的参考页面。
本页只涵盖了字符串类的一些可用方法。完整文档可以在Python的字符串页面上找到。
字符串,就其核心而言,实际上只是一种存储字符列表的奇特方式。如果我们没有字符串类,我们可能不得不写一些像这样的代码:
sometext = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']
显然,这在Processing中会是一个巨大的痛苦。做以下事情并创建一个字符串对象要简单得多:
sometext = "How do I make string? Type some characters between quotation marks!"
从上面可以看出,字符串只不过是引号之间的字符列表。然而,这只是字符串的数据。我们必须记住,字符串是一个具有方法的对象(你可以在参考页面上找到)。这就像我们在像素教程中学到的PImage既存储与图像相关的数据,也存储功能:copy()、loadPixels()等。
例如,在字符串末尾使用方括号(即myString[0])返回字符串中给定索引处的单个字符。这实际上只是调用字符串的__getitem__()方法的一种简单方式!注意,字符串就像列表一样,第一个字符是索引#0!
message = "some text here."
c = message[3]
print(c) # 结果是 'e'
另一个有用的函数是len()。通过调用len()函数并将我们的字符串作为参数,我们可以检索字符串的长度。
message = "This String is 34 characters long."
print(len(message)) # 打印 34
我们还可以使用.upper()方法将字符串更改为全大写(.lower也可用)。
uppercase = message.upper()
print(uppercase)
你可能会注意到这里有点奇怪。为什么我们不简单地说"message.upper()",然后打印"message"变量?相反,我们将"message.upper"的结果分配给一个具有不同名称的新变量——"uppercase"。
这是因为字符串是一种特殊的对象。它是不可变的。不可变对象是其数据永远不能改变的对象。一旦我们创建一个字符串,它就会终生保持不变。任何时候我们想要改变字符串,我们都必须创建一个新的。所以在转换为大写的情况下,方法.upper()返回一个全大写的字符串对象副本。
最后,让我们看看==(相等)运算符。现在,字符串可以用"=="运算符进行比较,如下所示:
one = "hello"
two = "hello"
print(one == two) # True!
虽然在其他语言中字符串有自己的相等性检查方法,但在Python中,相等性检查内置在默认的==(相等)运算符中。这使得比较字符串变得超级简单!
字符串对象的另一个特性是连接,将两个字符串连接在一起。字符串用"+"运算符连接。当然,加号通常意味着数字的加法。当与字符串一起使用时,它意味着连接。
helloworld = "Hello" + "World"
变量也可以使用字符串格式化引入到字符串中
x = 10
message = "The value of x is: %i" % (x)
print(message)
codeMsg = "spooky ghost"
secretMsg = "The secret message is: %s" % codeMsg
注意上面我在将不同类型的变量放入字符串时使用了不同的字符(%i表示整数,%s表示字符串)。你可以在这里找到这些字符串格式化操作的完整列表。
显示字符串最简单的方法是在消息窗口中打印它。这可能是你在调试时做过的事情。例如,如果你需要知道鼠标的水平位置,你会写:
print(mouseX)
或者如果你需要确定代码的某个部分被执行了,你可能会打印出一个描述性消息。
print("We got here and we're printing out the mouse location!!!")
虽然这对调试很有价值,但它不会帮助我们为用户显示文本的目标。要在屏幕上放置文本,我们必须遵循一系列简单的步骤。
首先,我们创建一个PFont对象。为了做到这一点,我们将使用createFont()函数。作为参数,我们引用字体名称和大小。这应该只做一次,通常在setup()期间。就像加载图像一样,加载字体是一个内存密集且缓慢的过程,如果放在像draw()这样频繁调用的函数中,会严重影响你的草图的性能。由于Java(Processing.py是用它创建的)的限制,不是所有字体都可以使用,有些可能在一个操作系统上工作,而在其他系统上不工作。当与他人分享草图或在网上发布时,你可能需要在草图的数据目录中包含字体的.ttf或.otf版本,因为其他人可能没有在他们的计算机上安装该字体。只有可以合法分发的字体才应该包含在草图中。除了字体名称,你还可以指定大小以及是否开启抗锯齿。
f = createFont("Arial", 16, True) # f将表示16号Arial,抗锯齿开启
接下来,我们使用textFont()指定字体。textFont()接受一个或两个参数,字体变量和字体大小,后者是可选的。如果你不包含字体大小,字体将以最初加载的大小显示。在可能的情况下,text()函数将使用原生字体而不是用createFont()在幕后创建的位图版本,这样你就有机会动态缩放字体。当使用P2D时,草图的实际原生版本字体将被使用,提高绘制质量和性能。使用P3D渲染器时,将使用位图版本,因此指定与加载的字体大小不同的字体大小可能导致像素化文本。
textFont(f,36)
然后,使用fill()指定颜色。
fill(255)
最后,调用text()函数来显示文本。(这个函数就像形状或图像绘制一样,它接受3个参数——要显示的文本,以及显示该文本的x和y坐标。)
text("Hello Strings!",10,100)
这里是所有步骤一起:
def setup():
global f
size(200,200)
f = createFont("Arial",16)
def draw():
global f
background(255)
textFont(f,16)
fill(0)
text("Hello Strings!",10,100)
字体也可以使用"工具"→"创建字体"来创建。这将创建并放置一个VLW字体文件在你的数据目录中,你可以使用loadFont()将其加载到PFont对象中。
f = loadFont("ArialMT-16.vlw")
让我们看看与显示文本相关的两个更有用的Processing函数:
textAlign()——为文本指定RIGHT、LEFT或CENTER对齐。
def setup():
global f
size(400,200)
f = createFont("Arial",16,True)
def draw():
global f
background(255)
stroke(175)
line(width/2,0,width/2,height)
textFont(f)
fill(0)
textAlign(CENTER)
text("This text is centered.",width/2,60)
textAlign(LEFT)
text("This text is left aligned.",width/2,100)
textAlign(RIGHT)
text("This text is right aligned.",width/2,140)
textWidth()——计算并返回任何字符或文本字符串的宽度。
假设我们想创建一个新闻滚动条,其中文本从屏幕底部从左到右滚动。当新闻标题离开窗口时,它重新出现在右侧并再次滚动。如果我们知道文本开始位置的x坐标,并且我们知道该文本的宽度,我们就可以确定它何时不再可见。textWidth()给了我们那个宽度。
首先,我们声明headline、font和x位置变量,在setup()中初始化它们。
headline = "New study shows computer programming lowers cholesterol."
def setup():
global f, x
f = createFont("Arial",16,true) # 加载字体
x = width # 将标题初始化到屏幕右侧
在draw()中,我们在适当的位置显示文本。
# 在x位置显示标题
textFont(f,16)
textAlign(LEFT)
text(headline,x,180)
我们通过速度值改变x(在这种情况下是负数,所以文本向左移动。)
# 递减x
x = x - 3
现在来了更困难的部分。测试圆圈何时到达屏幕左侧很容易。我们只需问:x是否小于0?然而,对于文本,由于它是左对齐的,当x等于零时,它仍然在屏幕上可见。相反,当x小于0减去文本宽度时,文本将不可见(见下图)。当出现这种情况时,我们将x重置回窗口的右侧,即width。
# 如果x小于负宽度,那么它完全离开屏幕
w = textWidth(headline)
if (x < -w):
x = width
这是完整的示例,每次前一个标题离开屏幕时显示不同的标题。标题存储在字符串列表中。
# 新闻标题列表
headlines = [ "Processing downloads break downloading record.",
"New study shows computer programming lowers cholesterol."]
def setup():
global f, x, index
size(400,200)
f = createFont("Arial",16,True)
# 将标题初始化到屏幕右侧
x = width
index = 0
def draw():
global f, x, index, headlines
background(255)
fill(0)
# 在x位置显示标题
textFont(f,16)
textAlign(LEFT)
text(headlines[index],x,180)
# 递减x
x = x - 3
# 如果x小于负宽度,那么它离开屏幕
w = textWidth(headlines[index])
if (x < -w):
x = width
index = (index + 1) % len(headlines)
除了textAlign()和textWidth(),Processing还提供textLeading()、textMode()、textSize()函数用于额外的显示功能。
平移和旋转也可以应用于文本。例如,要围绕其中心旋转文本,平移到原点并使用textAlign(CENTER)来显示文本。
message = "this text is spinning"
def setup():
global f, theta
size(200, 200)
f = createFont("Arial",20,True)
theta = 0
def draw():
global message, f, theta
background(255)
fill(0)
textFont(f) # 设置字体
translate(width/2,height/2) # 平移到中心
rotate(theta) # 按theta旋转
textAlign(CENTER)
text(message,0,0)
theta += 0.05 # 增加旋转
在某些图形应用程序中,需要逐字符渲染显示文本。例如,如果每个字符需要独立移动或着色,那么简单地说
text("a bunch of letters",0,0)
是不够的。
解决方案是遍历字符串,一次显示一个字符。
让我们先看一个一次性显示所有文本的示例。
message = "Each character is not written individually."
def setup():
global f
size(400, 200)
f = createFont("Arial",20,True)
def draw():
global f, message
background(255)
fill(0)
textFont(f)
# 使用text()一次性显示文本块。
text(message,10,height/2)
我们可以重写代码以在循环中显示每个字符,使用[](方括号/获取项目)运算符。
message = "Each character is written individually."
# 第一个字符在像素10处。
x = 10
for i in range(len(message)):
# 每个字符一次显示一个,使用charAt()函数。
text(message[i],x,height/2)
# 所有字符间隔10像素。
x += 10
为每个字符调用text()函数将允许我们更多的灵活性(用于着色、调整大小和在字符串内单独放置字符)。然而,上面的代码有一个相当大的缺陷——x位置为每个字符增加10像素。虽然这大致正确,但因为每个字符不是正好10像素宽,间距是不对的。
可以使用textWidth()函数实现正确的间距,如下面的代码所示。注意这个示例如何实现正确的间距,即使每个字符都是随机大小!
message = "Each character is written individually."
def setup():
global f
size(400, 150)
f = createFont("Arial",20,True)
def draw():
global f, message
background(255)
fill(0)
textFont(f)
x = 10
for i in range(len(message)):
textSize(random(12,36))
text(message[i],x,height/2)
# textWidth()正确间隔字符。
x += textWidth(message[i])
noLoop()
这种"逐字母"方法也可以应用于字符串中的字符独立移动的草图。以下示例使用面向对象设计,使原始字符串中的每个字符成为Letter对象,允许它既在其正确位置显示,又可以在屏幕上单独移动。
message = "click mouse to shake it up"
def setup():
global f, message, letters
size(260, 200)
# 加载字体
f = createFont("Arial",20,True)
textFont(f)
# 创建一个与字符串相同大小的全0列表
letters = [0] * len(message)
# 在正确的x位置初始化Letters
x = 16
for i in range(len(message)):
letters[i] = Letter(x,100,message[i])
x += textWidth(message[i])
def draw():
global f, letters
background(255)
for i in range(len(letters)):
# 显示所有字母
letters[i].display()
# 如果鼠标被按下,字母会摇晃
# 如果没有,它们会回到原来的位置
if (mousePressed):
letters[i].shake()
else:
letters[i].home()
# 描述单个Letter的类
class Letter():
def __init__(self, x, y, letter):
self.homex = self.x = x
self.homey = self.y = y
self.letter = letter
# 显示Letter
def display(self):
fill(0)
textAlign(LEFT)
text(self.letter, self.x, self.y)
# 随机移动字母
def shake(self):
self.x += random(-2,2)
self.y += random(-2,2)
# 将字母带回家
def home(self):
self.x = self.homex
self.y = self.homey
逐字符方法也允许我们沿着曲线显示文本。在我们继续讨论字母之前,让我们先看看如何沿着曲线绘制一系列盒子。这个例子大量使用三角函数。
# 圆的半径
r = 100.0
# 盒子的宽度和高度
w = 40.0
h = 40.0
def setup():
size(320, 320)
smooth()
def draw():
global r, w, h
background(255)
# 从中心开始并绘制圆
translate(width / 2, height / 2)
noFill()
stroke(0)
# 我们的曲线是窗口中心半径为r的圆。
ellipse(0, 0, r*2, r*2)
# 沿着曲线的10个盒子
totalBoxes = 10
# 我们必须跟踪我们在曲线上的位置
arclength = 0.0
# 对于每个盒子
for i in range(totalBoxes):
# 每个盒子都是居中的,所以我们移动宽度的一半
arclength += w/2
# 弧度角是弧长除以半径
theta = arclength / r
pushMatrix()
# 极坐标到笛卡尔坐标转换
translate(r*cos(theta), r*sin(theta))
# 旋转盒子
rotate(theta)
# 显示盒子
fill(0,100)
rectMode(CENTER)
rect(0,0,w,h)
popMatrix()
# 再次移动一半
arclength += w/2
我们需要做的是用适合盒子内部的字符串字符替换每个盒子。由于字符的宽度都不相同,而不是使用保持恒定的变量"w",沿着曲线的每个盒子将根据textWidth()函数具有可变宽度。
# 要显示的消息
message = "text along a curve"
# 圆的半径
r = 100
def setup():
global f
size(320, 320)
f = createFont("Georgia",40,True)
textFont(f)
# 文本必须居中!
textAlign(CENTER)
smooth()
def draw():
global r, f
background(255)
# 从中心开始并绘制圆
translate(width / 2, height / 2)
noFill()
stroke(0)
ellipse(0, 0, r*2, r*2)
# 我们必须跟踪我们在曲线上的位置
arclength = 0
# 对于每个盒子
for i in range(len(message)):
# 而不是恒定宽度,我们检查每个字符的宽度。
currentChar = message[i]
w = textWidth(currentChar)
# 每个盒子都是居中的,所以我们移动宽度的一半
arclength += w/2
# 弧度角是弧长除以半径
# 通过添加PI从圆的左侧开始
theta = PI + arclength / r
pushMatrix()
# 极坐标到笛卡尔坐标转换
translate(r*cos(theta), r*sin(theta))
# 旋转盒子
rotate(theta+PI/2) # 旋转偏移90度
# 显示字符
fill(0)
text(currentChar,0,0)
popMatrix()
# 再次移动一半
arclength += w/2
特别感谢Ariel Malka对这个最后的曲线文本示例的建议。