Surfarray介绍
使用surfarray模块,可以直接从python代码执行像素级操作。性能可以非常接近用C编写代码的水平。
数字Python
如果你没有安装python NumPy包,你需要现在就安装。你可以从NumPy下载页面下载这个包。
>>> from numpy import *
>>> a = array((1,2,3,4,5))
>>> a
array([1, 2, 3, 4, 5])
>>> a[2]
3
>>> a*2
array([ 2, 4, 6, 8, 10])
NumPy模块一个新的数据类型,数组。
该对象包含一个固定大小的数组,数组内的所有值都是相同类型的。
数组也可以是多维的,这就是我们将它们用于图像的方式。
还有一些比这更多的内容,但这已经足够让我们开始了。
NumPy数组上的数学运算应用于数组中的所有值。
这是“元素操作”。
这些数组也可以像普通列表一样进行切片。
切片语法与在标准python对象上使用的语法相同。
下面是使用数组的例子。
>>> len(a)
5
>>> a[2:]
array([3, 4, 5])
>>> a[:-2]
array([1, 2, 3])
>>> a[2:] + a[:-2]
array([4, 6, 8])
>>> array((1,2,3)) + array((3,4))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (3,) (2,)
我们在最后一个推荐中得到一个错误,因为我们试图将两个不同大小的数组相加。为了使两个数组相互操作,包括比较和赋值,它们必须具有相同的维数。重要的是要知道,通过对原始数组进行切片而创建的新数组都引用相同的值。因此,改变切片中的值也会改变原始值。
>>> a
array([1, 2, 3, 4, 5])
>>> aa = a[1:3]
>>> aa
array([2, 3])
>>> aa[1] = 13
>>> a
array([ 1, 2, 13, 4, 5])
>>> aaa = array(a)
>>> aaa
array([ 1, 2, 13, 4, 5])
>>> aaa[1:4] = 0
>>> aaa
array([1, 0, 0, 0, 5])
>>> a
array([ 1, 2, 13, 4, 5])
我们从二维数组开始。
>>> row1 = (1,2,3)
>>> row2 = (3,4,5)
>>> (row1,row2)
((1, 2, 3), (3, 4, 5))
>>> b = array((row1, row2))
>>> b
array([[1, 2, 3],
[3, 4, 5]])
>>> array(((1,2),(3,4),(5,6)))
array([[1, 2],
[3, 4],
[5, 6]])
这个二维数组(“2D”),我们可以索引特定的值并在两个维度上进行切片。只需使用逗号分隔索引,就可以在多个维度中进行查找/切片。只要使用":"作为索引(或没有提供足够的索引),就会得到该维度中的所有值。让我们看看它是如何工作的。
>>> b
array([[1, 2, 3],
[3, 4, 5]])
>>> b[0,1]
2
>>> b[1,:]
array([3, 4, 5])
>>> b[1]
array([3, 4, 5])
>>> b[:,2]
array([3, 5])
>>> b[:,:2]
array([[1, 2],
[3, 4]])
当使用NumPy时,切片还有一个功能。切片数组还允许指定切片增量。带有增量的片的语法是start_index: end_index: increment。
>>> c = arange(10)
>>> c
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> c[1:6:2]
array([1, 3, 5])
>>> c[4::4]
array([4, 8])
>>> c[8:1:-1]
array([8, 7, 6, 5, 4, 3, 2])
好吧,就是这样。这里有足够的信息让你开始使用NumPy和surfarray模块。NumPy当然还有很多,但这只是一个介绍。
导入 Surfarray
为了使用surfarray模块,我们需要导入它。因为surfarray和NumPy都是pygame的可选组件,所以最好在使用它们之前确保它们正确导入。在这些例子中,我将把NumPy导入到一个名为n的变量中。这将让你知道我使用的函数来自NumPy包。(而且比在每个函数之前输入NumPy要短得多)
try:
import numpy as N
import pygame.surfarray as surfarray
except ImportError:
raise ImportError, "NumPy and Surfarray are required."
Surfarray介绍
在surfarray中有两种主要类型的函数。
用于创建表面像素数据副本的数组的一组函数。
其他函数创建数组像素数据的引用副本,以便对数组的更改直接影响原始表面。
还有其他一些函数允许您以数组的形式访问每像素的alpha值,还有一些其他有用的函数。
稍后我们将讨论这些其他函数。
当使用这些表面数组时,有两种表示像素值的方法。
首先,它们可以表示为映射整数。
这种类型的数组是一个简单的2D数组,用一个整数表示表面的映射颜色值。
这种类型的数组适合于移动图像的各个部分。
另一种类型的数组使用三个RGB值来表示每个像素的颜色。
这种类型的数组使它非常简单的类型的效果,改变每个像素的颜色。
这种类型的数组处理起来也有点棘手,因为它本质上是一个3D数字数组。
不过,一旦你的思维进入正确的模式,这并不比使用普通的2D数组难多少。
NumPy模块使用机器的自然数类型来表示数据值,因此NumPy数组可以由8位、16位和32位的整数组成。
(数组也可以使用其他类型,如浮点数和双精度数,但对于我们的图像操作,我们主要需要担心整数类型)。
由于整数大小的这种限制,您必须特别注意引用像素数据的数组类型能够正确地映射到适当类型的数据。
函数从表面创建这些数组:
surfarray.pixels2d(surface):创建一个引用原始表面数据的2D数组(整数像素值)。这将适用于所有表面格式,除了24位。
surfarray.array2d(surface):创建从任何类型的表面复制的2D数组(整数像素值)。
surfarray.pixels3d(surface):创建一个引用原始表面数据的3D数组(RGB像素值)。这将只工作在24位和32位表面有RGB或BGR格式。
surfarray.array3d(surface):创建一个3D数组(RGB像素值),从任何类型的表面复制。
这里有一个小图表,可以更好地说明在哪个曲面上应该使用什么类型的函数。如您所见,这两个arrayXD函数都可以处理任何类型的曲面。
32-bit 24-bit 16-bit 8-bit(c-map)
pixel2d yes yes yes
array2d yes yes yes yes
pixel3d yes yes
array3d yes yes yes yes
例子
有了这些信息,我们就可以开始尝试表面阵列了。下面是创建NumPy数组并在pygame中显示它们的简短演示。这些不同的测试可以在arraydemo.py示例中找到。有一个名为surfdemo_show的简单函数,它在屏幕上显示一个数组。
我们的第一个例子创建了一个全黑色数组。当您需要创建一个特定大小的新数字数组时,最好使用zero函数。在这里,我们创建一个全0的2D数组并显示它。
allblack = N.zeros((128, 128))
surfdemo_show(allblack, ‘allblack’)
-------------------------------------------------------------------------------------------------------------------------------------
这里我们处理的是一个3D数组。我们首先创建一个全红色的图像。然后我们切片每第三行,并将它赋给一个蓝/绿颜色。正如您所看到的,我们可以将3D数组与2D数组几乎完全相同地对待,只是要确保为它们分配3个值,而不是单个映射整数。
striped = N.zeros((128, 128, 3))
striped[:] = (255, 0, 0)
striped[:,::3] = (0, 255, 255)
surfdemo_show(striped, ‘striped’)
在这里,我们用image模块加载一个图像,然后将其转换为一个由整数RGB颜色元素组成的3D数组。一个表面的RGB副本总是将颜色排列为:红色组件a[r,c,0],绿色组件a[r,c,1],蓝色组件a[r,c,2]。这可以在不关心实际表面像素如何配置的情况下使用,不像2D数组是映射(原始)表面像素的副本。我们将在其余的示例中使用此图像。
imgsurface = pygame.image.load(‘surfarray.png’)
rgbarray = surfarray.array3d(imgsurface)
surfdemo_show(rgbarray, ‘rgbarray’)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3dea858a3df1a8695b6452bfd4693e0f.png)
这里我们垂直翻转图像。我们需要做的就是取原始图像数组并使用负增量对其进行切片。
flipped = rgbarray[:,::-1]
surfdemo_show(flipped, ‘flipped’)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/00284d1128bf97feae21cfcc970c6c85.png)
根据上一个例子,缩小图像是很合理的。我们只需要在垂直和水平方向上分别增加2个像素就可以分割出所有的像素。
scaledown = rgbarray[::2,::2]
surfdemo_show(scaledown, ‘scaledown’)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d3d5a8e0abeebecda1547f3c6d5e4b0d.png)
放大图像需要做更多的工作,但与之前的缩小相似,我们都是通过切片来完成的。首先,我们创建一个数组,其大小是原始数组的两倍。首先,我们将原始数组复制到新数组的每一个其他像素。然后我们对每一个其他的奇数列像素再做一次。在这一点上,我们已经正确地缩放了图像,但是每隔一行都是黑色的,所以我们只需要将每一行复制到它下面的那一行。然后我们有一个两倍大的图像。
shape = rgbarray.shape
scaleup = N.zeros((shape[0]*2, shape[1]*2, shape[2]))
scaleup[::2,::2,:] = rgbarray
scaleup[1::2,::2,:] = rgbarray
scaleup[:,1::2] = scaleup[:,::2]
surfdemo_show(scaleup, ‘scaleup’)
现在我们使用3D数组来改变颜色。在这里,我们将所有绿色和蓝色的值设置为零。这就只剩下红色通道了。
redimg = N.array(rgbarray)
redimg[:,:,1:] = 0
surfdemo_show(redimg, ‘redimg’)
这里我们执行一个3x3卷积滤波器,将软化我们的图像。
这里看起来有很多步骤,但我们所做的是将图像向每个方向移动1个像素,并将它们加在一起(使用一些乘法来加权)。
然后求所有值的平均值。
它不是高斯分布,但速度很快。
在NumPy数组中,算术运算的精度由具有最大数据类型的数组决定。
因此,如果factor没有声明为类型numpy.int32的1个元素数组,那么乘法将使用numpy.int8执行,numpy.int8是每个rgbarray元素的8位整数类型。
这将导致值的截断。
为了避免截断,还必须声明软化数组的整数大小大于rgarray。
factor = N.array((8,), N.int32)
soften = N.array(rgbarray, N.int32)
soften[1:,:] += rgbarray[:-1,:] * factor
soften[:-1,:] += rgbarray[1:,:] * factor
soften[:,1:] += rgbarray[:,:-1] * factor
soften[:,:-1] += rgbarray[:,1:] * factor
soften //= 33
surfdemo_show(soften, ‘soften’)
最后,我们在原始图像和纯蓝色图像之间进行交叉衰落。这并不令人兴奋,但最远处的图像可以是任何东西,改变0.50乘数可以让你在两个图像之间的线性交叉淡出中选择任何步骤。
src = N.array(rgbarray)
dest = N.zeros(rgbarray.shape)
dest[:] = 20, 50, 100
diff = (dest - src) * 0.50
xfade = src + diff.astype(N.uint)
surfdemo_show(xfade, ‘xfade’)
锁定Surface
像其他的pygame, surfarray将锁定它需要自动访问像素数据的任何表面。不过,还有一件事需要注意。当创建像素数组时,原始表面将在像素数组的生命周期内被锁定。记住这一点很重要。确保“删除”像素数组或让它超出范围(例如,当函数返回时,等等)。
还要注意,您真的不希望在硬件表面(HWSURFACE)上进行太多的直接像素访问(如果有的话)。这是因为实际的表面数据保存在显卡上,而通过PCI/AGP总线传输像素变化的速度并不快。
透明度
surfarray模块有几个方法来访问Surface的alpha/colorkey值。所有的alpha函数都不受Surface整体透明度的影响,只受像素alpha值的影响。这是这些函数的列表。
surfarray.pixels_alpha(surface):创建一个引用原始表面alpha数据的2D数组(整数像素值)。这将只适用于32位图像与8位alpha组件。
surfarray.array_alpha(surface):创建从任何类型的表面复制的2D数组(整数像素值)。如果表面没有alpha值,数组将是完全不透明的值(255)。
surfarray.array_colorkey(surface):创建一个2D数组(整数像素值),在像素颜色匹配Surface颜色键的地方将其设置为透明(0)。
其他Surfarray功能
surfarray中只有几个其他的函数可用。你可以在surfarray参考页面得到一个更好的列表和更多的文档。有一个非常有用的函数。
surfarray.blit_array(surface, array):这将把任何类型的2D或3D表面数组转移到相同尺寸的表面上。这个数组比特通常比将一个数组赋值给一个引用的像素数组快。不过,它应该不会像普通的Surface blitt那样快,因为这些都是非常优化的。
更高级的NumPy
关于NumPy数组,还有几件事你应该知道。
当处理非常大的数组时,比如640x480大的数组,有一些额外的事情需要注意。
主要是,虽然在数组中使用+和*之类的操作符使它们易于使用,但在大型数组中使用这些操作符的代价也非常昂贵。
这些操作符必须创建数组的新临时副本,然后通常将其复制到另一个数组中。
这可能非常耗时。
幸运的是,所有的NumPy操作符都带有特殊的功能,可以执行“适当”的操作。
例如,您可能想要用更快的add(screen, brightmap, screen)替换screen[:] = screen + brightmap。
无论如何,你会想要阅读NumPy UFunc文档来了解更多这方面的信息。
这在处理数组时很重要。
使用NumPy数组时需要注意的另一件事是数组的数据类型。
一些数组(特别是映射像素类型)经常返回带有无符号8位值的数组。
如果不小心,这些数组很容易溢出。
NumPy将使用与C程序中相同的强制转换,因此混合使用8位数字和32位数字的操作将得到32位数字的结果。
您可以转换数组的数据类型,但一定要注意您所拥有的数组类型,如果NumPy处于精度会被破坏的情况下,它将引发异常。
最后,请注意,当将值赋给3D数组时,它们必须在0到255之间,否则你将得到一些未定义的截断。
好了,你知道了。
我的数字Python和surfarray快速入门。
希望现在您看到了什么是可能的,即使您自己从未使用过它们,当您看到这样的代码时也不必害怕。
查看vgrade示例以获得更多的数字数组操作。
也有一些“火焰”演示浮动,使用surfarray来创建一个实时的火焰效果。
最好的是,自己尝试一些事情。
一开始要慢慢发展,我已经看到了一些关于surfarray的很棒的东西,比如径向梯度等等。
祝你好运。