<> = 处理基本的图像 = '''本章内容:''' * 装载图像和创建图像对象 * 创建设备上下文 * 绘制到设备上下文 * 绘制文本到设备上下文 * 管理画刷、画笔和设备坐标 任何用户界面工具的最基本的行为就是在屏幕上绘制。在最基本的层面上来说,定义在{{{wxPython}}}中的每个窗口部件都是由发送给屏幕的一系列命令构成的。那些绘制命令是否是在{{{wxPython}}}代码库中,这依赖于相关窗口部件对于本地操作系统是否是本地化的或完全由{{{wxPython}}}所定义的。在这一章,我们将给你展示如何在基本绘制命令层面上控制{{{wxPython}}}。我们也将给你展示如何管理和显示其它的图形化元素如图像和字体。 被{{{wxPython}}}用于绘制的主要的概念是设备上下文({{{device context}}})。设备上下文使用一个标准的{{{API}}}来管理对设备(如屏幕或打印机)的绘制。设备上下文类用于最基本的绘制功能,如绘制一条直线、曲线或文本。 == 使用图像工作 == 大多数的应用程序都需要载入至少一个图像,这些图像存储在外部的文件中。样例包括工具栏图片、光标、图标、启始画面或仅仅用于装饰以增加一些时髦感的图像。传统上,使用图像工作的复杂性是不得不处理用于储存图像的不同的图片文件格式。幸运的是,{{{wxPython}}}内部为你做了所有的这些。你将使用相同的抽象概念来处理任何图像,而不用关心它的原始格式。 在随后的几节中,我们将讨论{{{wxPython}}}用来管理图像的那么抽象概念,这包括大尺寸图像({{{large}}}-{{{scale images)}}},以及光标图像。你将看到如何装载图像到你的程序中,然后如何操作它们。 === 如何载入图像? === 在{{{wxPython}}}中,图像处理是一个双主管系统,与平台无关的图像处理由类{{{wx.Image}}}管理,而与平台有关的图像处理由类{{{wx.Bitmap}}}管理。实际上,意思就是外部文件格式由{{{wx.Image}}}装载和保存,而{{{wx.Bitmap}}}负责将图像显示到屏幕。图12.1显示了不同图像和位图的创建,按照不同的文件类型读入。 要从一个文件载入一个图像,使用{{{wx.Image}}}的构造函数: {{{#!python wx.Image(name, type=wx.BITMAP_TYPE_ANY, index=-1) }}} '''图12.1''' {{attachment:w12.1.gif}} 参数{{{name}}}是图像文件的名字,参数{{{type}}}(类型)是处理器类型。{{{type}}}的{{{ID}}}可以是{{{wx.BITMAP_TYPE_ANY}}}或表12.1中的一个。如果你使用{{{wx.BITMAP_TYPE_ANY}}},那么{{{wxPython}}}将试图自动检测该文件的类型。如果你使用一个特定的文件类型,那么{{{wxPython}}}将使用该类型转换这个文件。例12.1显示了如何使用{{{wx.BITMAP_TYPE_ANY}}}来载入图像。 '''例12.1''' '''载入并缩放简单图像''' {{{#!python import wx filenames = ["image.bmp", "image.gif", "image.jpg", "image.png" ] class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Loading Images") p = wx.Panel(self) fgs = wx.FlexGridSizer(cols=2, hgap=10, vgap=10) for name in filenames: #1 从文件载入图像 img1 = wx.Image(name, wx.BITMAP_TYPE_ANY) # Scale the oiginal to another wx.Image w = img1.GetWidth() h = img1.GetHeight() img2 = img1.Scale(w/2, h/2)#2 缩小图像 #3 转换它们为静态位图部件 sb1 = wx.StaticBitmap(p, -1, wx.BitmapFromImage(img1)) sb2 = wx.StaticBitmap(p, -1, wx.BitmapFromImage(img2)) # and put them into the sizer fgs.Add(sb1) fgs.Add(sb2) p.SetSizerAndFit(fgs) self.Fit() app = wx.PySimpleApp() frm = TestFrame() frm.Show() app.MainLoop() }}} 上面的代码应该很简单。代码开始是我们想要载入的图像文件的名字,我们使用{{{wx.BITMAP_TYPE_ANY}}}类型标记指示{{{wxPython}}}去断定图像文件的格式,而用不着我们操心。然后我们使用图像的方法将图像缩小一半,并将图像转换为位图。 你也可以显示地指定图像文件的格式,在下一节,我们将显示{{{wxPython}}}所支持的图像文件格式。 '''指定一个图像文件格式''' 图像由所用的图像处理器管理。一个图像处理器是{{{wx.ImageHandler}}}的一个实例,它为管理图像格式提供了一个插入式的结构。在通常的情况下,你不需要考虑图像处理器是如何工作的。你所需要知道的是每个处理器都有它自己唯一的{{{wxPython}}}标识符,用以载入相关格式的文件。表12.1列出了所支持的格式。 '''表12.1 {{{wxPython}}}支持的图像文件格式''' ||处理器类||类型标记||说明|| ||{{{wx.ANIHandler}}}||{{{wx.BITMAP_TYPE_ANI}}}||动画光标格式。这个处理器只载入图像而不保存它们。|| ||{{{wx.BMPHandler}}}||{{{wx.BITMAP_TYPE_BMP}}}||{{{Windows}}}和{{{OS}}}/2位图格式。|| ||{{{wx.CURHandle}}}||{{{wx.BITMAP_TYPE_CUR}}}||{{{Windows}}}光标 图标格式。|| ||{{{wx.GIFHandler}}}||{{{wx.BITMAP_TYPE_GIF}}}||图形交换格式。由于版权限制,这个处理器不保存图像。|| ||{{{wx.ICOHandler}}}||{{{wx.BITMAP_TYPE_ICO}}}||{{{Windows}}}图标格式。|| ||{{{wx.IFFHandler }}}||{{{wx.BITMAP_TYPE_IFF}}}||交换文件格式。这个处理器只载入图像,它不保存它们。|| ||{{{wx.JPEGHandler }}}||{{{wx.BITMAP_TYPE_JPEG}}}||联合图形专家组格式。|| ||{{{wx.PCXHandler }}}||{{{wx.BITMAP_TYPE_PCX}}}||{{{PC}}}画刷格式。当以这种格式保存时,{{{wxPython}}}计算在这个图像中的不同颜色的数量。如果可能的话,这个图像被保存为一个8位图像(也就是说,如果它有256种或更少的颜色)。否则它保存为24位。|| ||{{{wx.PNGHandler }}}||{{{wx.BITMAP_TYPE_PNG}}}||便携式网络图形格式。|| ||{{{wx.PNMHandler }}}||{{{wx.BITMAP_TYPE_PNM}}}||只能载入{{{ASCII}}}或原始的{{{RGB}}}图像。图像被该处理器保存为原始的{{{RGB}}}。|| ||{{{wx.TIFFHandler }}}||{{{wx.BITMAP_TYPE_TIF}}}||标签图像文件格式。|| ||{{{wx.XPMHandler }}}||{{{wx.BITMAP_TYPE_XPM}}}||{{{XPixMap}}}格式。|| ||自动 ||{{{wx.BITMAP_TYPE_ANY}}}||自动检测使用的格式,然后调用相应的处理器。|| 要使用一个{{{MIME}}}类型来标识文件,而不是一个处理器类型{{{ID}}}的话,请使用函数{{{wx.ImageFromMime(name, mimetype, index}}}=-1),其中的{{{name}}}是文件名,{{{mimetype}}}是有关文件类型的字符串。参数{{{index}}}表示在图像文件包含多个图像的情况下要载入的图像的索引。这仅仅被{{{GIF, ICO, }}}和{{{TIFF}}}处理器使用。默认值-1表示选择默认的图像,被{{{GIF}}}和{{{TIFF}}}处理顺解释为每一个图像({{{index}}}=0),被{{{ICO}}}处理器解释为最大且最多色彩的一个。 '''创建{{{image}}}(图像)对象''' {{{wxPython}}}使用不同的全局函数来创建不同种类的{{{wx.Image}}}对象。要创建一个有着特定尺寸的空图像,使用函数{{{wx.EmptyImage(width,height)}}}——在这个被创建的图像中所有的像素都是黑色。要创建从一个打开的流或{{{Python}}}文件类对象创建一个图像,使用{{{wx.ImageFromStream(stream,type}}}={{{wx.BITMAP_TYPE_ANY, index}}}=-1)。有时,根据一个原始的{{{RGB}}}数据来创建一个图像是有用的,这使用{{{wx.ImageFromData(width,height,data)}}},{{{data}}}是一个字符串,每套连续的三个字符代表一个像素的红,绿,蓝的组分。这个字符串的大小应该是{{{width}}}*{{{height}}}*3。 '''创建{{{bitmap}}}(位图)对象''' 有几个方法可以创建一个位图对象。其中最基本的{{{wx.Bitmap}}}构造函数是 {{{wx.Bitmap(name, type}}}={{{wx.BITMAP_TYPE_ANY)}}}。参数{{{name}}}是一个文件名,{{{type}}}可以是表12.1中的一个。如果{{{bitmap}}}类能够本地化地处理这个文件格式,那么它就处理,否则这个图像将自动地经由{{{wx.Image}}}载入并被转换为一个{{{wx.Bitmap}}}实例。 你可以使用方法{{{wx.EmptyBitmap(width,height,depth}}}=-1)来创建一个空的位图——参数{{{width}}}和{{{height}}}是位图的尺度,{{{depth}}}是结果图像的颜色深度。有两个函数使你能够根据原始的数据来创建一个位图。函数{{{wx.BitmapFromBits(bits, width, height, depth}}}=-1)创建一个位图,参数{{{bits}}}是一个{{{Python}}}字节列表。这个函数的行为依赖于平台。在大多数平台上,{{{bits}}}要么是1要么是0,并且这个函数创建一个单色的位图。在{{{Windows}}}平台上,数据被直接传递给{{{Windows}}}的{{{API}}}函数{{{CreateBitmap()}}}。函数{{{wxBitmapFromXPMData(listOfStrings)}}}一个{{{Python}}}字符串列表作为一个参数,以{{{XPM}}}格式读该字符串。 通过使用{{{wx.Bitmap}}}的构造函数{{{wx.BitmapFromImage(image, depth}}}=-1),你可以将一个图像转换为一个位图。参数{{{image}}}是一个实际{{{wx.Image}}}对象,{{{depth}}}是结果位图的颜色深度。如果这个深度没有指定,那么使用当前显示器的颜色深度。你可以使用函数{{{wx.ImageFromBitmap(bitmap)}}}将位图转回为一个图像,通过传递一个实际的{{{wx.Bitmap}}}对象。在例12.1中,位图对象的创建使用了位图的构造函数,然后被用于构建{{{wx.StaticBitmap}}}窗口部件,这使得它们能够像别的{{{wxPython}}}项目一样被放入一个容器部件中。 === 我们能够对图像作些什么? === 一旦你在{{{wxPython}}}中使用了图像,你就可以使用许多有用的方法来处理它,并且可以写一些强大的图像处理脚本。 你可以使用{{{GetWidth()}}}和{{{GetHeight()}}}方法来查询图像的尺寸。你也可以使用方法{{{GetRed(x, y), GetGreen(x, y), }}}和{{{GetBlue(x, y)}}}方法得到任意象素点的颜色值。这些颜色方法的返回值是一个位于0~255之间的整数(用C的术语,它是一个无符号整数,但是这个区别在{{{Python}}}中没有多大的意义)。同样,你能够使用{{{SetRGB(x, y, red, green, blue)}}}来设置一个像素点的颜色,其中的x和y是这个像素点的坐标,颜色的取值位于0~255之间。 你可以使用{{{GetData()}}}方法得到一大块区域中的所有数据。{{{GetData()}}}方法的返回值是一个大的字符串,其中的每个字符代表一个{{{RGB}}}元组,并且每个字符都可被认为是一个0~255之间整数值。这些值是有顺序的,第一个是位于像素点(0,0)的红色值,接下来的是位于像素点(0,0)的绿色值,然后是位于像素点(0,0)的蓝色值。再接下来的三个是像素点(0,1)的颜色值,如此等等。这个算法可以使用下面的{{{Python}}}伪代码来定义: {{{#!python def GetData(self): result = "" for y in range(self.GetHeight()): for x in range(self.GetWidth()): result.append(chr(self.GetRed(x,y))) result.append(chr(self.GetGreen(x,y))) result.append(chr(self.GetBlue(x,y))) return result }}} 当使用对应的{{{SetData(data)}}}方法读取类似格式的{{{RGB}}}字符串值时,有两件事需要知道。第一,{{{SetData(data)}}}方法不执行范围或边界检查,以确定你读入的字符串的值是否在正确的范围内或它的长度是否是给定图像的尺寸。如果你的值不正确,那么该行为是未定义的。第二,由于底层是C++代码管理内在,所以将{{{GetData()}}}的返回值传递给{{{SetData()}}}是一个坏的方法——你应该构造一个新的字符串。 图像数据字符串可以很容易地与别的{{{Python}}}类型作相互的转换,这使得很容易以整数的形式来访问和处理它,诸如数组或数字类型。例如,如果你太久的注视一个东西会损伤眼睛一样,试试这样: {{{#!python import array img = wx.EmptyImage(100,100) a = array.array('B', img.GetData()) for i in range(len(a)): a[i] = (25+i) % 256 img.SetData(a.tostring()) }}} 表12.2定义了一些{{{wx.Image}}}的方法,这些方法执行简单的图像处理。 这些方法只是图像处理的开始部分。在接下来的部分,我们将给你展示两个方法,它们处理透明和半透明图像这一更复杂的主题。 '''表12.2 {{{wx.Image}}}的图像处理方法''' {{{ConvertToMono(r, g, b)}}}:返回一个与原尺寸一致的{{{wx.Image}}},其中所有颜色值为({{{r, g, b)}}}的像素颜色改为白色,其余为黑色。原图像未改变。 {{{Mirror(horizontally}}}={{{True)}}}:返回原图像的一个镜像图像。如果{{{horizontally}}}参数是{{{True}}},那么镜像图像是水平翻转了的,否则是垂直翻转了的。原图像没有改变。 {{{Replace(r1, g1, b1, r2, g2, b2)}}}:改变调用该方法的图像的所有颜色值为{{{r1, g1, b1}}}的像素的颜色为{{{r2, g2, b2}}}。 {{{Rescale(width, height)}}}:改变图像的尺寸为新的宽度和高度。原图像也作了改变,并且颜色按比例地调整到新的尺寸。 {{{Rotate(angle, rotationCentre, interpolating}}}={{{True, offestAfterRotation}}}={{{None)}}}:返回旋转原图像后的一个新的图像。参数{{{angle}}}是一个浮点数,代表所转的弧度。{{{rotationCentre}}}是一个{{{wx.Point}}},代表旋转的中心。如果{{{interpolating}}}为{{{True}}},那么一个较慢而精确的算法被使用。{{{offsetAfterRotation}}}是一个坐标点,表明在旋转后图像应该移位多少。任何未被覆盖的空白像素将被设置为黑色,或如果该图像有一个遮罩色,设置为遮罩色({{{mask color}}})。 {{{Rotate90(clockwise}}}={{{True)}}}:按照参数{{{clockwise}}}的布尔值,控制图像按顺或逆时针方向作90度的旋转。 {{{Scale(width, height)}}}:返回一个原图像的拷贝,并按比例改变为新的宽度和高度。 '''设置图像的遮罩以指定一个透明的图像''' 图像遮罩是图像中的一个特殊的颜色集,当图像显示在其它显示部分之上时,它扮演透明度的角色。你可以使用{{{SetMaskColor(red, green, blue)}}}方法来设置一个图像遮罩,其中的{{{red, green, blue}}}定义图像遮罩的颜色。如果你想关闭遮罩,可以使用{{{SetMask(False)}}},重置使用{{{SetMask(True)}}}。方法{{{HasMask()}}}返回与当前遮罩状态相关的一个布尔值。你也可以使用方法{{{SetMaskFromImage(mask, mr, mg, mb)}}}根据同一尺寸的另一图像设置遮罩——在这种情况下,遮罩被定义为在遮罩{{{wx.Image}}}中有着颜色{{{mr, mg, mb}}}的所有像素,而不管在主图像中那些像素是什么颜色。这使得你在创建一个遮罩中有了很大的灵活性,因为你不必再担心在你原图像中的像素的颜色。你可以使用{{{GetMaskRed()}}},{{{GetMaskGreen(), }}}和{{{GetMaskBlue()}}}获取遮罩色。如果一个有遮罩的图像被转换为一个{{{wx.Bitmap}}},那么遮罩被自动转换为一个{{{wx.Mask}}}对象并赋给该位图。 '''设置{{{alpha}}}值来指定一个透明的图像''' {{{alpha}}}值是指定一个透明或部分透明图像的另一个方法。每个像素都有一个{{{alpha}}}值,取值位于0(如果图像在该像素是完全透明的)到255(如果图像在该像素点是完全不透明的)之间。你可以使用{{{SetAlphaData(data)}}}方法来设置{{{alpha}}}值,它要求类似于{{{SetData()}}}的字符串字节值,但是每个像素只有一个值。和{{{SetData()}}}一样,{{{SetAlphaData()}}}不进行范围检查。你可以使用{{{HasAlpha()}}}来看是否设置了{{{alpha}}}值,你也可以使用{{{GetAlphaData()}}}来得到全部的数据集。你也可以使用{{{SetAlpha(x, y, alpha)}}}来设定一个特定的像素的{{{alpha}}}值,并使用{{{GetAlpha(x, y)}}}来得到该值。 与{{{wx.Image}}}的图像处理功能相对照,{{{wx.Bitmap}}}的相对少些。几乎所有{{{wx.Bitmap}}}的方法都是简单得得到诸如宽度、高度和颜色深度这类的属性。