PS入门教程-ActionScript 入门教程

一 : ActionScript 入门教程

ActionScript 入门教程

描述
第 1-4 章,ActionScript 编程概述
讨论 ActionScript 3.0 核心概念,其中包括语言语法、语句和运算符、ECMAScript 第4 版语言规范草案、面向对象的 ActionScript 编程以及管理 Adobe® Flash® Player 9 显示列表中的显示对象的新方法。
第 5-10 章,ActionScript 3.0 核心数据类型和类
介绍 ActionScript 3.0 中的顶级数据类型(也是 ECMAScript 规范草案的一部分)。
第 11-26 章,Flash Player API
介绍在特定于 Adobe Flash Player 9 的包和类中实现的重要功能,其中包括事件处理、网络和通信、文件输入和输出、外部接口、应用程序安全模型等。

ActionScript 快速入门

关于 ActionScript
ActionScript 是由 Flash Player 中的 ActionScript 虚拟机 (AVM) 来执行的。ActionScript 代码通常被编译器编译成"字节码格式"(一种由计算机编写且能够为计算机所理解的编程语言),如 Adobe Flash CS3 Professional 或 Adobe® Flex™ Builder™ 的内置编译器或 Adobe® Flex™ SDK 和 Flex™ Data Services 中提供的编译器。字节码嵌入 SWF 文件中,SWF 文件由运行时环境 Flash Player 执行。
ActionScript 3.0 中的一些主要功能包括:
一个新增的 ActionScript 虚拟机,称为 AVM2,它使用全新的字节码指令集,可使性能显著提高。
一个更为先进的编译器代码库,它更为严格地遵循 ECMAScript (ECMA 262) 标准,并且相对于早期的编译器版本,可执行更深入的优化。
一个扩展并改进的应用程序编程接口 (API),拥有对对象的低级控制和真正意义上的面向对象的模型。
一种基于即将发布的 ECMAScript (ECMA-262) 第 4 版草案语言规范的核心语言。
一个基于 ECMAScript for XML (E4X) 规范(ECMA-357 第 2 版)的 XML API。E4X 是 ECMAScript 的一种语言扩展,它将 XML 添加为语言的本机数据类型。
一个基于文档对象模型 (DOM) 第 3 级事件规范的事件模型。

编程基础
计算机程序主要包括两个方面:一、程序是计算机执行的一系列指令或步骤。 二、每一步最终都涉及到对某一段信息或数据的处理。 通常认为,计算机程序只是您提供给计算机并让它逐步执行的指令列表。每个单独的指令都称为"语句"。在 ActionScript 中编写的每个语句的末尾都有一个分号。实质上,程序中给定指令所做的全部操作就是处理存储在计算机内存中的一些数据位。

变量和常量
"变量"是一个名称,它代表计算机内存中的值。在编写语句来处理值时,编写变量名来代替值;只要计算机看到程序中的变量名,就会查看自己的内存并使用在内存中找到的值。
在 ActionScript 3.0 中,一个变量实际上包含三个不同部分:
变量名 计算机将名称用作值的占位符。
可以存储在变量中的数据的类型
存储在计算机内存中的实际值
在 ActionScript 中,要创建一个变量(称为"声明"变量),应使用 var 语句。
在 Adobe Flash CS3 Professional 中,还包含另外一种变量声明方法。在将一个影片剪辑元件、按钮元件或文本字段放置在舞台上时,可以在"属性"检查器中为它指定一个实例名称。在后台,Flash 将创建一个与该实例名称同名的变量,您可以在 ActionScript 代码中使用该变量来引用该舞台项目。



数据类型
在 ActionScript 中,您可以将很多数据类型用作所创建的变量的数据类型。其中的某些数据类型可以看作是"简单"或"基本"数据类型:
String:一个文本值
Numeric:对于 numeric 型数据,ActionScript 3.0 包含三种特定的数据类型:
Number:任何数值,包括有小数部分或没有小数部分的值
Int:一个整数(不带小数部分的整数)
Uint:一个"无符号"整数,即不能为负数的整数
Boolean:一个 true 或 false 值
简单数据类型表示单条信息。然而,ActionScript 中定义的大部分数据类型都可以被描述为复杂数据类型,因为它们表示组合在一起的一组值。
大部分内置数据类型以及程序员定义的数据类型都是复杂数据类型。您可能认识下面的一些复杂数据类型:
MovieClip:影片剪辑元件
TextField:动态文本字段或输入文本字段
SimpleButton:按钮元件
Date:有关时间中的某个片刻的信息(日期和时间)
经常用作数据类型的同义词的两个词是类和对象。"类"仅仅是数据类型的定义 ─ 就像用于该数据类型的所有对象的模板,而"对象"仅仅是类的一个实际的实例。下面几条陈述虽然表达的方式不同,但意思是相同的:
变量 myVariable 的数据类型是 Number。
变量 myVariable 是一个 Number 实例。
变量 myVariable 是一个 Number 对象。
变量 myVariable 是 Number 类的一个实例。

处理对象
在面向对象的编程中,程序指令被划分到不同的对象中 ─ 代码构成功能块,因此相关类型的功能或相关的信息被组合到一个容器(即 类)中。
在 ActionScript 面向对象的编程中,任何类都可以包含三种类型的特性:属性、 方法 、事件。
属性 表示某个对象中绑定在一起的若干数据块中的一个。您可以像处理单个变量那样处理属性;事实上,可以将属性视为包含在对象中的"子"变量。
将变量用作对象的名称,后跟一个句点 (.) 和属性名。句点称为"点运算符",用于指示您要访问对象的某个子元素。整个结构"变量名-点-属性名"的使用类似于单个变量,变量是计算机内存中的单个值的名称。
方法 是指可以由对象执行的操作。可以通过依次写下对象名(变量)、句点、方法名和小括号来访问方法,这与属性类似。小括号是指示要"调用"某个方法的方式。有时,为了传递执行动作所需的额外信息,将值(或变量)放入小括号中。这些值称为方法"参数"。
与属性(和变量)不同的是,方法不能用作值占位符。然而,一些方法可以执行计算并返回可以像变量一样使用的结果。
事件 事件是确定计算机执行哪些指令以及何时执行的机制。本质上,"事件"就是所发生的、ActionScript 能够识别并可响应的事情。
基本事件处理
指定为响应特定事件而应执行的某些动作的技术称为"事件处理"。
在编写执行事件处理的 ActionScript 代码时,您需要识别三个重要元素:
事件源:发生该事件的是哪个对象?事件源也称为"事件目标",因为 Flash Player 将此
对象(实际在其中发生事件)作为事件的目标。
事件:将要发生什么事情,以及您希望响应什么事情?识别事件是非常重要的,因为许多
对象都会触发多个事件。
响应:当事件发生时,您希望执行哪些步骤?
无论何时编写处理事件的 ActionScript 代码,都会包括这三个元素,并且代码将遵循以下基本结构(以粗体显示的元素是您将针对具体情况填写的占位符):
function eventResponse(eventObject:EventType):void
{
// 此处是为响应事件而执行的动作。
}
eventSource.addEventListener(EventType.EVENT_NAME, eventResponse);
此代码执行两个操作。首先,定义一个函数,这是指定为响应事件而要执行的动作的方法。接下来,调用源对象的 addEventListener() 方法,实际上就是为指定事件"订阅"该函数,以便当该事件发生时,执行该函数的动作。
"函数"提供一种将若干个动作组合在一起、用类似于快捷名称的单个名称来执行这些动作的方法。函数与方法完全相同,只是不必与特定类关联(事实上,方法可以被定义为与特定类关联的函数)。在创建事件处理函数时,必须选择函数名称(本例中为 eventResponse),还必须指定一个参数(本例中的名称为 eventObject)。指定函数参数类似于声明变量,所以还必须指明参数的数据类型。将为每个事件定义一个 ActionScript 类,并且为函数参数指定的数据类型始终是与要响应的特定事件关联的类。(即 此处的这个类的事件特性中必须包含要您希望要响应的事件。)最后,在左大括号与右大括号之间 ({ ...}),编写您希望计算机在事件发生时执行的指令。
一旦编写了事件处理函数,就需要通知事件源对象(发生事件的对象)您希望在该事件发生时调用函数。可通过调用该对象的 addEventListener() 方法来实现此目的(所有具有事件的对象都同时具有 addEventListener() 方法)。addEventListener() 方法有两个参数:
第一个参数是您希望响应的特定事件的名称。同样,每个事件都与一个特定类关联,而该类将为每个事件预定义一个特殊值;类似于事件自己的唯一名称(应将其用于第一个参数)。
第二个参数是事件响应函数的名称。请注意,如果将函数名称作为参数进行传递,则在写入函数名称时不使用括号。
了解事件处理过程
下面分步描述了创建事件侦听器时执行的过程。在本例中,您将创建一个侦听器函数,在单击名为 myButton 的对象时将调用该函数。
程序员实际编写的代码如下所示:
function eventResponse(event:MouseEvent):void
{
// Actions performed in response to the event go here.
}
myButton.addEventListener(MouseEvent.CLICK, eventResponse);
下面是此代码在 Flash Player 中运行时的实际工作方式:
1.加载 SWF 文件时,Flash Player 会注意到以下情况:有一个名为 eventResponse() 的函数。

2.Flash Player 随后运行该代码(具体地说,是指不在函数中的代码行)。在本例中,只有一行代码:针对事件源对象(名为 myButton)调用 addEventListener() 方法,并将 eventResponse 函数作为参数进行传递。

1.在内部,myButton 包含正在侦听其每个事件的函数的列表,因此,当调用其 addEventListener() 方法时,myButton 将 eventResponse() 函数存储在其事件侦听器列表中。

3.在某一时刻,用户单击 myButton 对象以触发其 click 事件(在代码中将其标识为 MouseEvent.CLICK)。

此时发生了以下事件:
1.Flash Player 创建一个对象,它是与所述事件(本示例中为 MouseEvent)关联的类的实例。对于很多事件,这是 Event 类的实例;对于鼠标事件,这是 MouseEvent 实例;对于其它事件,这是与该事件关联的类的实例。创建的该对象称为"事件对象",它包含有关所发生的事件的特定信息:事件类型、发生位置以及其它特定于事件的信息(如果适用)。

2.Flash Player 随后查看 myButton 存储的事件侦听器的列表。它逐个查看这些函数,以调用每个函数并将事件对象作为参数传递给该函数。由于 eventResponse() 函数是 myButton 的侦听器之一,因此,Flash Player 将在此过程中调用 eventResponse() 函数。

3.当调用 eventResponse() 函数时,将运行该函数中的代码,因此,将执行您指定的动作。

创建对象实例
在 ActionScript 中使用对象之前,该对象首先必须存在。创建对象的步骤之一是声明变量;然而,声明变量仅仅是在计算机的内存中创建一个空位置。您必须为变量指定实际值 ─ 即创建一个对象并将它存储在该变量中 ─ 然后再尝试使用或处理该变量。创建对象的过程称为对象"实例化";也就是说,创建特定类的实例。
有一种创建对象实例的简单方法完全不必涉及 ActionScript。在 Flash 中,当将一个影片剪辑元件、按钮元件或文本字段放置在舞台上,并在"属性"检查器中为它指定实例名时,Flash 会自动声明一个拥有该实例名的变量、创建一个对象实例并将该对象存储在该变量中。同样,在 Adobe Flex Builder 中,当您以 Adobe Macromedia® MXML™ 创建一个组件(通过用 MXML 标签进行编码或通过将组件放置在处于设计模式下的编辑器中)并为该组件分配一个 ID(在 MXML 标记中或在 Flex 属性视图中)时,该 ID 将成为一个 ActionScript 变量的名称,并且会创建该组件的一个实例并将它存储在该变量中。 (直观地创建对象。)
通过几种方法来仅使用 ActionScript 创建对象实例。
首先,借助几个 ActionScript 数据类型,可以使用"文本表达式"(直接写入 ActionScript 代码的值)创建一个实例。ActionScript 为Number 、int 、uint 、String 、Boolean 、Array、RegExp、XML 、Object 和 Function 数据类型定义了文本表达式。
对于其它任何数据类型而言,要创建一个对象实例,应将 new 运算符与类名一起使用,通常,将使用 new 运算符创建对象称为"调用类的构造函数"。"构造函数"是一种特殊方法,在创建类实例的过程中将调用该方法。请注意,当以此方法创建实例时,请在类名后加上小括号,有时还可以指定参数值 ─ 这是在调用方法时另外可执行的两个操作。
甚至对于可使用文本表达式创建实例的数据类型,也可以使用 new 运算符来创建对象实例。
如果需要创建无可视化表示形式的 ActionScript 数据类型的一个实例(无法通过将项目放置在 Flash 舞台上来创建,也无法在Flex Builder MXML 编辑器的设计模式下创建),则只能通过使用 new 运算符在ActionScript 中直接创建对象来实现此目的。
具体到 Flash 中,new 运算符还可用于创建已在库中定义、但没有放在舞台上的影片剪辑元件的实例。




常用编程元素
运算符 是用于执行计算的特殊符号(有时候是词)。

注释 "代码注释"是一个工具,用于编写计算机应在代码中忽略的文本。ActionScript 包括两种注释:
单行注释:在一行中的任意位置放置两个斜杠来指定单行注释。计算机将忽略斜杠后直到该行末尾的所有内容。
多行注释:多行注释包括一个开始注释标记 (/*)、注释内容和一个结束注释标记 (*/)。无论注释跨多少行,计算机都将忽略开始标记与结束标记之间的所有内容。
注释的另一种常见用法是临时禁用一行或多行代码。
流控制 就是用于控制执行哪些动作。ActionScript 中提供了几种类型的流控制元素。
函数:函数类似于快捷方式,提供了一种将一系列动作组合到单个名称下的方法,并可用于执行计
算。函数对于处理事件尤为重要,但也可用作组合一系列指令的通用工具。
循环:使用循环结构,可指定计算机反复执行一组指令,直到达到设定的次数或某些条件改变为止。
通常借助循环并使用一个其值在计算机每执行完一次循环后就改变的变量来处理几个相关项。
条件语句:条件语句提供一种方法,用于指定仅在某些情况下才执行的某些指令或针对不同的条件
提供不同的指令集。最常见的一类条件语句是 if 语句。if 语句检查该语句括号中的值或表达式。如果值为 true,则执行大括号中的代码行;否则,将忽略它们。
使用 ActionScript 构建应用程序
您可以使用 ActionScript 3.0 代码来实现任何目的,从简单的图形动画到复杂的客户端 ─ 服务器事务处理系统都可以通过它来实现。
将代码存储在 Flash 时间轴中的帧中
在 Flash 创作环境中,可以向时间轴中的任何帧添加 ActionScript 代码。该代码将在影片播放期间播放头进入该帧时执行。构建较大的应用程序时,这会容易导致无法跟踪哪些帧包含哪些脚本,应用程序越来越难以维护。
许多开发人员将代码仅仅放在时间轴的第 1 帧中,或放在 Flash 文档中的特定图层上,以简化在 Flash 创作工具中组织其 ActionScript 代码的工作。这样,就可以容易地在 Flash FLA 文件中查找和维护代码。
如果想要以后能够在其它 Flash 项目中使用您的 ActionScript 代码,您需要将代码存储在外部 ActionScript 文件(扩展名为 .as 的文本文件)中。
将代码存储在 ActionScript 文件中
可以采用以下两种方式之一来设置 ActionScript 文件的结构:
非结构化 ActionScript 代码:编写 ActionScript 代码行(包括语句或函数定义),就好像它们是直接在时间轴脚本、MXML 文件等文件中输入的一样。
使用 ActionScript 中的 include 语句或 Adobe Flex MXML 中的 <mx:Script> 标签,可以访问以此方式编写的 ActionScript。ActionScript include 语句会导致在特定位置以及脚本中的指定范围内插入外部 ActionScript 文件的内容,就好像它们是直接在那里输入的一样。在 Flex MXML 语言中,可使用 <mx:Script> 标签来指定源属性,从而标识要在应用程序中的该点处加载的外部 ActionScript 文件。
ActionScript 类定义:定义一个 ActionScript 类,包含它的方法和属性。
定义一个类后,您就可以像对任何内置的 ActionScript 类所做的那样,通过创建该类的一个实例并使用它的属性、方法和事件来访问该类中的 ActionScript 代码。这要求做到下面两点:
使用 import语句来指定该类的全名,以便ActionScript 编译器知道可以在哪里找到它。
此规则的唯一例外是,如果在代码中引用的类为顶级类,没有在包中定义,则必须导入该类。
在 Flash 中,将自动为附加到时间轴上的帧的脚本导入内置类(在 flash.* 包中)。但是,如果您编写自己的类、处理 Flash 创作组件(fl.* 包)或在 Flex 中工作,则需要显式地导入任何类以编写用于创建该类实例的代码。
编写明确引用类名的代码(通常声明一个用该类作为其数据类型的变量,并创建该类的一个实例以便存储在该变量中)。在 ActionScript 代码中引用其它类名即通知编译器加载该类的定义。
选择合适的工具
Flash 创作工具
除了创建图形和动画的功能之外,Adobe Flash CS3 Professional 还包括处理 ActionScript 代码(附加到 FLA 文件中的元素的代码,或仅包含 ActionScript 代码的外部文件中的代码)的工具。Flash 创作工具最适合于涉及大量的动画或视频的项目,或者您希望自己创建大部分图形资源的项目,尤其适合于用户交互很少或者具有需要 ActionScript 的功能的项目。如果您希望在同一个应用程序中既创建可视资源又编写代码,也可能会选择使用 Flash 创作工具来开发 ActionScript 项目。如果您希望使用预置的用户界面组件,但 SWF 较小或便于设置可视外观是项目的主要考虑因素,那么您也可能会选择使用 Flash 创作工具。
Adobe Flash CS3 Professional 包括两个编写 ActionScript 代码的工具:
"动作"面板:在 FLA 文件中工作时可用,该面板允许您编写附加到时间轴上的帧的 ActionScript 代码。
"脚本"窗口:"脚本"窗口是专门用于处理 ActionScript (.as) 代码文件的文本编辑器。
Flex Builder
Adobe Flex Builder 是创建带有 Flex 框架的项目的首选工具。除了可视布局和 MXML 编辑工具之外,Flex Builder 还包括一个功能完备的 ActionScript 编辑器,因此可用于创建 Flex 或仅包含 ActionScript 的项目。Flex 应用程序具有以下几个优点:包含一组内容丰富的预置用户界面控件和灵活的动态布局控件,内置了用于处理外部数据源的机制,以及将外部数据链接到用户界面元素。但由于需要额外的代码来提供这些功能,因此 Flex 应用程序的 SWF 文件可能比较大,并且无法像 Flash 应用程序那样轻松地完全重设外观。
如果希望使用 Flex 创建功能完善、数据驱动且内容丰富的 Internet 应用程序,并在一个工具内编辑 ActionScript 代码,编辑 MXML 代码,直观地设置应用程序布局,则应使用 Flex Builder。
第三方 ActionScript 编辑器
由于 ActionScript (.as) 文件存储为简单的文本文件,因此任何能够编辑纯文本文件的程序都可以用来编写 ActionScript 文件。您可以使用任何文本编辑程序来编写 MXML 文件或 ActionScript 类。然后,可以使用 Flex SDK(包括 Flex 框架类和 Flex 编译器)来基于这些文件创建 SWF 应用程序(Flex 或仅包含 ActionScript 的应用程序)。或者,很多开发人员也可以使用第三方 ActionScript 编辑器来编写 ActionScript 类,并结合使用 Flash 创作工具来创建图形内容。
在以下情况下,您可以选择使用第三方 ActionScript 编辑器:
您希望在单独的程序中编写 ActionScript 代码,而在 Flash 中设计可视元素。
将某个应用程序用于非 ActionScript 编程(例如,创建 HTML 页或以其它编程语言构建应用程序),并希望将该应用程序也用于 ActionScript 编码。
您希望使用 Flex SDK 而不用 Flash 和 Flex Builder 来创建仅包含 ActionScript 的项目或 Flex 项目。
有一些提供特定于 ActionScript 的支持的代码编辑器值得注意,其中包括:Adobe Dreamweaver® CS3 、ASDT、 FDT、 FlashDevelop 、PrimalScript、 SE|PY、 Xcode(带有 ActionScript 模板和代码提示文件)。
ActionScript 开发过程
ActionScript 3.0 的应用程序的基本开发过程:
1.设计应用程序。
您应先以某种方式描述应用程序,然后再开始构建该应用程序。
2.编写 ActionScript 3.0 代码。
您可以使用 Flash、Flex Builder、Dreamweaver 或文本编辑器来创建 ActionScript 代码。
3.创建 Flash 或 Flex 应用程序文件来运行代码。
在 Flash 创作工具中,此步骤包括:创建新的 FLA 文件、设置发布设置、向应用程序添加用户界面组件以及引用 ActionScript 代码。在 Flex 开发环境中,创建新的应用程序文件涉及:定义该应用程序并使用 MXML 来添加用户界面组件以及引用 ActionScript 代码。
4.发布和测试 ActionScript 应用程序。
这涉及在 Flash 创作环境或 Flex 开发环境中运行应用程序,确保该应用程序执行您期望的所有操作。
不必按顺序执行这些步骤,或者说不必在完全完成一个步骤后再执行另一步骤。虽然记住开发过程的这 4 个阶段是十分有用的,但在实际的开发过程中适当地调整各个阶段的顺序通常有助于提高效率。
创建自己的类
类设计策略 下面还是给出了几条建议以帮助您着手进行面向对象的编程。
1.请考虑一下该类的实例将在应用程序中扮演的角色。通常,对象担任以下三种角色之一:
值对象:这些对象主要用作数据的容器 ─ 也就是说,它们可能拥有若干个属性和很少的几个方法(有时没有方法)。值对象通常是明确定义的项目的代码表示。
显示对象:它们是实际显示在屏幕上的对象。例如,用户界面元素(如下拉列表或状态显示)和图形元素(如视频游戏中的角色)等等就是显示对象。
应用程序结构:这些对象在应用程序执行的逻辑或处理方面扮演着广泛的支持角色。
2.确定类所需的特定功能。不同类型的功能通常会成为类的方法。
3.如果打算将类用作值对象,请确定实例将要包含的数据。这些项是很好的候选属性。
4.由于类是专门为项目而设计的,因此最重要的是提供应用程序所需的功能。回答下列问题可能会对
您很有帮助:
应用程序将存储、跟踪和处理哪些信息?确定这些信息有助于您识别可能需要的值对象和属性。
需要执行哪些操作 ─ 例如,在应用程序首次加载时,在单击特定的按钮时,在影片停止播放时,分别需要执行哪些操作?这些是很好的候选方法(如果"动作"仅涉及更改单个值,则是很好的候选属性)。
对于任何给定的动作,要执行该动作,该类需要了解哪些信息?这些信息将成为方法的参数。
随着应用程序开始工作,应用程序的其它部分需要了解类中的哪些内容将发生更改?这些是很好的候选事件。
5.如果有一个现有的对象与您需要的对象类似,只是缺少某些您需要添加的一些额外功能,应考虑创
建一个子类(在现有类的功能的基础之上构建的类,不需要定义它自己的所有功能)。
编写类的代码
下面是创建自己的 ActionScript 类的最基本步骤:
1.在特定于 ActionScript 的程序(如 Flex Builder 或 Flash)、通用编程工具(如 Dreamweaver)或者可用来处理纯文本文档的任何程序中打开一个新的文本文档。
2.输入 class 语句定义类的名称。为此,输入单词 public class,然后输入类名,后跟一个左大括号和一个右大括号,两个括号之间将是类的内容(方法和属性定义)。 单词 public 表示可以从任何其它代码中访问该类。
3.键入 package 语句以指示包含该类的包的名称。语法是单词 package,后跟完整的包名称,再跟左大括号和右大括号(括号之间将是 class 语句块)。
4.使用var语句,在类体内定义该类中的每个属性;语法与用于声明任何变量的语法相同(并增加了public 修饰符)。
5.使用与函数定义所用的相同语法来定义类中的每个方法。例如:
要创建普通方法。
要创建一个构造函数(在创建类实例的过程中调用的特殊方法),应创建一个名称与类名称完全匹配的方法。
如果没有在类中包括构造函数方法,编译器将自动在类中创建一个空构造函数(没有参数和语句)。
您还可以定义其它几个类元素。这些元素更为复杂。
"存取器"是方法与属性之间的一个特殊交点。在编写代码来定义类时,可以像编写方法一样来编写存取器,这样就可以执行多个动作(而不是像在定义属性时那样,只能读取值或赋值)。但是,在创建类的实例时,可将存取器视为属性 ─ 仅使用名称来读取值或赋值。
ActionScript 中的事件不是使用特定的语法来定义的。应使用 EventDispatcher 类的功能来定义类中的事件,以便跟踪事件侦听器并将事件通知给它们。
有关组织类的一些建议
Adobe 建议您始终将每个类的源代码保存在其自己的文件中,并为文件指定与类相同的名称。
ActionScript 语言及其语法
ActionScript 3.0 既包含 ActionScript 核心语言又包含 Adobe Flash Player 应用程序编程接口 (API)。ActionScript 核心语言是 ActionScript 的一部分,实现了 ECMAScript (ECMA-262) 第 4 版语言规范草案。Flash Player API 提供对 Flash Player 的编程访问。
语言概述
对象是 ActionScript 3.0 语言的核心 ─ 它们是 ActionScript 3.0 语言的基本构造块。您所声明的每个变量、所编写的每个函数以及所创建的每个类实例都是一个对象。可以将 ActionScript 3.0 程序视为一组执行任务、响应事件以及相互通信的对象。
在ECMAScript 第 4 版草案(ActionScript 3.0 所基于的标准)中,对象只是属性的集合。这些属性是一些容器,除了保存数据,还保存函数或其它对象。以这种方式附加到对象的函数称为方法。
尽管具有 Java 或 C++ 背景的程序员可能会觉得 ECMAScript 草案中的定义有些奇怪,但实际上,用 ActionScript 3.0 类定义对象类型与在 Java 或 C++ 中定义类的方式非常相似。在讨论 ActionScript 对象模型和其它高级主题时,了解这两种对象定义之间的区别很重要,但在其它大多数情况下,"属性"一词都表示类成员变量(而不是方法),使用"方法"一词表示作为类的一部分的函数。
ActionScript 中的类与 Java 或 C++ 中的类之间有一个细小的差别,那就是 ActionScript 中的类不仅仅是抽象实体。ActionScript 类由存储类的属性和方法的类对象 表示,这样便可以使用可能不为 Java 和 C++ 程序员所熟悉的方法,例如,在类或包的顶级包括语句或可执行代码。
ActionScript类与Java 或 C++ 类之间还有一个区别,那就是每个 ActionScript 类都有一个"原型对象"。在早期的 ActionScript 版本中,原型对象链接成"原型链",它们共同作为整个类继承层次结构的基础。但是,在 ActionScript 3.0 中,原型对象在继承系统中仅扮演很小的角色。但是,原型对象仍非常有用,如果您希望在类的所有实例中共享某个属性及其值,可以使用原型对象来代替静态属性和方法。
对象和类
在 ActionScript 3.0 中,每个对象都是由类定义的。可将类视为某一类对象的模板或蓝图。类定义中可以包括变量和常量以及方法,前者用于保存数据值,后者是封装绑定到类的行为的函数。存储在属性中的值可以是"基元值",也可以是其它对象。基元值是指数字、字符串或布尔值。
ActionScript 中包含许多属于核心语言的内置类。其中的某些内置类(如 Number、Boolean 和 String)表示 ActionScript 中可用的基元值。其它类(如 Array、Math 和 XML)定义属于 ECMAScript 标准的更复杂对象。
所有的类(无论是内置类还是用户定义的类)都是从 Object 类派生的。以前在 ActionScript 方面有经验的程序员一定要注意到,Object 数据类型不再是默认的数据类型,尽管其它所有类仍从它派生。在 ActionScript 2.0 中,下面的两行代码等效,因为缺乏类型注释意味着变量为 Object 类型:
var someObj:Object;
var someObj;
但是,ActionScript 3.0 引入了无类型变量这一概念,这一类变量可通过以下两种方法来指定:
var someObj:*;
var someObj;
无类型变量与 Object 类型的变量不同。二者的主要区别在于无类型变量可以保存特殊值 undefined,而 Object 类型的变量则不能保存该值。
您可以使用 class 关键字来定义自己的类。在方法声明中,可通过以下三种方法来声明类属性 (property):用 const 关键字定义常量,用 var 关键字定义变量,用 get 和 set 属性 (attribute) 定义 getter 和 setter 属性 (property)。可以用 function 关键字来声明方法。
可使用 new 运算符来创建类的实例。
包和命名空间
包和命名空间是两个相关的概念。使用包,可以通过有利于共享代码并尽可能减少命名冲突的方式将多个类定义捆绑在一起。使用命名空间,可以控制标识符(如属性名和方法名)的可见性。无论命名空间位于包的内部还是外部,都可以应用于代码。包可用于组织类文件,命名空间可用于管理各个属性和方法的可见性。


在 ActionScript 3.0 中,包是用命名空间实现的,但包和命名空间并不同义。在声明包时,可以隐式创建一个特殊类型的命名空间并保证它在编译时是已知的。显式创建的命名空间在编译时不必是已知的。
类位于包中,因此编译器在编译时会自动将其类名称限定为完全限定名称,编译器还限定任何属性或方法的名称。完全限定的包引用点运算符 (.) 来表示。
许多开发人员(尤其是那些具有 Java 编程背景的人)可能会选择只将类放在包的顶级。但是,ActionScript 3.0 不但支持将类放在包的顶级,而且还支持将变量、函数甚至语句放在包的顶级。此功能的一个高级用法是,在包的顶级定义一个命名空间,以便它对于该包中的所有类均可用。但是,请注意,在包的顶级只允许使用两个访问说明符:public 和 internal。Java 允许将嵌套类声明为私有,而 ActionScript 3.0 则不同,它既不支持嵌套类也不支持私有类。
您还可以在包名称中嵌入点来创建嵌套包,这样就可以创建包的分层结构。
创建包
ActionScript 3.0 允许在一个源文件中包括多个类,但是,每个文件中只有一个类可供该文件外部的代码使用。换言之,每个文件中只有一个类可以在包声明中进行声明。您必须在包定义的外部声明其它任何类,以使这些类对于该源文件外部的代码不可见。在包定义内部声明的类的名称必须与源文件的名称匹配。
在 ActionScript 3.0 中,尽管包仍表示目录,但是它现在不只包含类。在 ActionScript 3.0 中,使用 package 语句来声明包,这意味着您还可以在包的顶级声明变量、函数和命名空间,甚至还可以在包的顶级包括可执行语句。如果在包的顶级声明变量、函数或命名空间,则在顶级只能使用 public 和 internal 属性,并且每个文件中只能有一个包级声明使用 public 属性(无论该声明是类声明、变量声明、函数声明还是命名空间声明)。
包的作用是组织代码并防止名称冲突。您不应将包的概念与类继承这一不相关的概念混淆。位于同一个包中的两个类具有共同的命名空间,但是它们在其它任何方面都不必相关。同样,在语义方面,嵌套包可以与其父包无关。
导入包
如果您希望使用位于某个包内部的特定类,则必须导入该包或该类。
通常,import 语句越具体越好。如果您只打算使用包中的某个类,则应只导入该类,而不应导入该类所属的整个包。导入整个包可能会导致意外的名称冲突。
还必须将定义包或类的源代码放在类路径 内部。类路径是用户定义的本地目录路径列表,它决定了编译器将在何处搜索导入的包和类。类路径有时称为"生成路径"或"源路径"。
在正确地导入类或包之后,可以使用类的完全限定名称,也可以只使用类名称本身。
当同名的类、方法或属性会导致代码不明确时,完全限定的名称非常有用,但是,如果将它用于所有的标识符,则会使代码变得难以管理。包的嵌套级别越高,代码的可读性越差。如果您确信不明确的标识符不会导致问题,就可以通过使用简单的标识符来提高代码的可读性。
如果您尝试使用标识符名称,而不先导入相应的包或类,编译器将找不到类定义。另一方面,即便您导入了包或类,只要尝试定义的名称与所导入的名称冲突,也会产生错误。
创建包时,该包的所有成员的默认访问说明符是 internal,这意味着,默认情况下,包成员仅对其所在包的其它成员可见。如果您希望某个类对包外部的代码可用,则必须将该类声明为 public。
不能将 public 属性应用于包声明。
完全限定的名称可用来解决在使用包时可能发生的名称冲突。如果您导入两个包,但它们用同一个标识符来定义类,就可能会发生名称冲突。要解决此冲突,必须使用每个类的完全限定名称。
ActionScript 3.0有一个include 指令,但是它的作用不是为了导入类和包。要在ActionScript 3.0中导入类或包,必须使用 import 语句,并将包含该包的源文件放在类路径中。
命名空间
通过命名空间可以控制所创建的属性和方法的可见性。请将 public、private、protected 和 internal 访问控制说明符视为内置的命名空间。如果这些预定义的访问控制说明符无法满足您的要求,您可以创建自己的命名空间。
要了解命名空间的工作方式,有必要先了解属性或方法的名称总是包含两部分:标识符和命名空间。标识符通常被视为名称。
只要定义不以命名空间属性开头,就会用默认 internal 命名空间限定其名称,这意味着,它们仅对同一个包中的调用方可见。如果编译器设置为严格模式,则编译器会发出一个警告,指明 internal 命名空间将应用于没有命名空间属性的任何标识符。为了确保标识符可在任何位置使用,您必须在标识符名称的前面明确加上 public 属性。
使用命名空间时,应遵循以下三个基本步骤。
第一,必须使用 namespace 关键字来定义命名空间。例如,下面的代码定义 version1 命名空间:
namespace version1;
第二,在属性或方法声明中,使用命名空间(而非访问控制说明符)来应用命名空间。下面的示例将一个名为 myFunction() 的函数放在 version1 命名空间中:
version1 function myFunction() {}
第三,在应用了该命名空间后,可以使用 use 指令引用它,也可以使用该命名空间来限定标识符的名称。下面的示例通过 use 指令来引用 myFunction() 函数:
use namespace version1;
myFunction();
您还可以使用限定名称来引用 myFunction() 函数,如下面的示例所示:
version1::myFunction();
定义命名空间
命名空间中包含一个名为统一资源标识符 (URI) 的值,该值有时称为命名空间名称。使用 URI 可确保命名空间定义的唯一性。(统一资源标识符 (Uniform Resource Identifier, URI) 用于唯一地标识元素或属性的数字或名称。URI 包括统一资源名称 (URN) 和统一资源定位器 (URL)。)
可通过使用以下两种方法之一来声明命名空间定义,以创建命名空间:像定义 XML 命名空间那样使用显式 URI 定义命名空间;省略 URI。
下面的示例说明如何使用 URI 来定义命名空间:
namespace flash_proxy = "http://www.loach.net.cnflash/proxy";
URI 用作该命名空间的唯一标识字符串。如果您省略 URI(如下面的示例所示),则编译器将创建一个唯一的内部标识字符串来代替 URI。您对于这个内部标识字符串不具有访问权限。
namespace flash_proxy;
在定义了命名空间(具有 URI 或没有 URI)后,就不能在同一个作用域内重新定义该命名空间。如果尝试定义的命名空间以前在同一个作用域内定义过,则将生成编译器错误。
如果在某个包或类中定义了一个命名空间,则该命名空间可能对于此包或类外部的代码不可见,除非使用了相应的访问控制说明符。例如,下面的代码显示了在 flash.utils 包中定义的 flash_proxy 命名空间。在下面的示例中,缺乏访问控制说明符意味着 flash_proxy 命名空间将仅对于 flash.utils 包内部的代码可见,而对于该包外部的任何代码都不可见:
package flash.utils
{
namespace flash_proxy;
}
下面的代码使用 public 属性以使 flash_proxy 命名空间对该包外部的代码可见:
package flash.utils
{
public namespace flash_proxy;
}
应用命名空间
应用命名空间意味着在命名空间中放置定义。可以放在命名空间中的定义包括函数、变量和常量(不能将类放在自定义命名空间中)。
例如,请考虑一个使用 public 访问控制命名空间声明的函数。在函数的定义中使用 public 属性会将该函数放在 public 命名空间中,从而使该函数对于所有的代码都可用。在定义了某个命名空间之后,可以按照与使用 public 属性相同的方式来使用所定义的命名空间,该定义将对于可以引用您的自定义命名空间的代码可用。例如,如果您定义一个名为 example1 的命名空间,则可以添加一个名为 myFunction() 的方法并将 example1 用作属性,如下面的示例所示:
namespace example1;
class someClass
{
example1 myFunction() {}
}
如果在声明 myFunction() 方法时将 example1 命名空间用作属性,则意味着该方法属于 example1 命名空间。
在应用命名空间时,应切记以下几点:
对于每个声明只能应用一个命名空间。
不能一次将同一个命名空间属性应用于多个定义。换言之,如果您希望将自己的命名空间应用于 10 个不同的函数,则必须将该命名空间作为属性分别添加到这 10 个函数的定义中。
如果您应用了命名空间,则不能同时指定访问控制说明符,因为命名空间和访问控制说明符是互斥的。换言之,如果应用了命名空间,就不能将函数或属性声明为 public、private、protected 或 internal。
引用命名空间
在使用借助于任何访问控制命名空间(如 public、private、protected 和 internal)声明的方法或属性时,无需显式引用命名空间。这是因为对于这些特殊命名空间的访问由上下文控制。例如,放在 private 命名空间中的定义会自动对于同一个类中的代码可用。但是,对于您所定义的命名空间,并不存在这样的上下文相关性。要使用已经放在某个自定义命名空间中的方法或属性,必须引用该命名空间。
可以用 use namespace 指令来引用命名空间,也可以使用名称限定符 (::) 来引用命名空间限定名称。用 use namespace 指令引用命名空间会打开该命名空间,这样它便可以应用于任何未限定的标识符。例如,如果您已经定义了 example1 命名空间,则可以通过使用 use namespace example1 来访问该命名空间中的名称:
use namespace example1;
myFunction();
一次可以打开多个命名空间。在使用 use namespace 打开了某个命名空间之后,它会在打开它的整个代码块中保持打开状态。不能显式关闭命名空间。
但是,如果同时打开多个命名空间则会增加发生名称冲突的可能性。如果您不愿意打开命名空间,则可以用命名空间和名称限定符来限定方法或属性名,从而避免使用 use namespace 指令。例如,下面的代码说明如何用 example1 命名空间来限定 myFunction() 名称:
example1::myFunction();
使用命名空间
在 Flash Player API 中的 flash.utils.Proxy 类中,可以找到用来防止名称冲突的命名空间的实例。Proxy 类取代了 ActionScript 2.0 中的 Object.__resolve 属性,可用来截获对未定义的属性或方法的引用,以免发生错误。为了避免名称冲突,将 Proxy 类的所有方法都放在 flash_proxy 命名空间中。
为了更好地了解 flash_proxy 命名空间的使用方法,您需要了解如何使用 Proxy 类。Proxy 类的功能仅对于继承它的类可用。换言之,如果您要对某个对象使用 Proxy 类的方法,则该对象的类定义必须是对 Proxy 类的扩展。例如,如果您希望截获对未定义的方法的调用,则应扩展 Proxy 类,然后覆盖 Proxy 类的 callProperty() 方法。
前面已讲到,实现命名空间的过程通常分为三步,即定义、应用然后引用命名空间。但是,由于您从不显式调用 Proxy 类的任何方法,因此只是定义和应用 flash_proxy 命名空间,而从不引用它。Flash Player API 定义 flash_proxy 命名空间并在 Proxy 类中应用它。在您的代码中,只需要将 flash_proxy 命名空间应用于扩展 Proxy 类的类。
flash_proxy 命名空间按照与下面类似的方法在 flash.utils 包中定义:
package flash.utils
{
public namespace flash_proxy;
}
该命名空间将应用于 Proxy 类的方法,如下面摘自 Proxy 类的代码所示:
public class Proxy
{
flash_proxy function callProperty(name:*, ... rest):*
flash_proxy function deleteProperty(name:*):Boolean
...
}
如下面的代码所示,您必须先导入 Proxy 类和 flash_proxy 命名空间。随后必须声明自己的类,以便它对 Proxy 类进行扩展(如果是在严格模式下进行编译,则还必须添加 dynamic 属性)。在覆盖 callProperty() 方法时,必须使用 flash_proxy 命名空间。
package
{
import flash.utils.Proxy;
import flash.utils.flash_proxy;

dynamic class MyProxy extends Proxy
{
flash_proxy override function callProperty(name:*, ...rest):*
{
trace("method call intercepted: " + name);
}
}
}
如果您创建 MyProxy 类的一个实例,并调用一个未定义的方法(如在下面的示例中调用的 testing() 方法),Proxy 对象将截获对该方法的调用,并执行覆盖后的 callProperty() 方法内部的语句(在本例中为一个简单的 trace() 语句)。
var mySample:MyProxy = new MyProxy();
mySample.testing(); // 已截获方法调用:测试
将 Proxy 类的方法放在 flash_proxy 命名空间内部有两个好处。第一个好处是,在扩展 Proxy 类的任何类的公共接口中,拥有单独的命名空间可提高代码的可读性。(在 Proxy 类中大约有 12 个可以覆盖的方法,所有这些方法都不能直接调用。将所有这些方法都放在公共命名空间中可能会引起混淆。)第二个好处是,当 Proxy 子类中包含名称与 Proxy 类方法的名称匹配的实例方法时,使用 flash_proxy 命名空间可避免名称冲突。例如,您可能希望将自己的某个方法命名为 callProperty()。下面的代码是可接受的,因为您所用的 callProperty() 方法位于另一个命名空间中:
dynamic class MyProxy extends Proxy
{
public function callProperty() {}
flash_proxy override function callProperty(name:*, ...rest):*
{
trace("method call intercepted: " + name);
}
}
当您希望以一种无法由四个访问控制说明符(public、private、internal 和 protected)实现的方式提供对方法或属性的访问时,命名空间也可能会非常有用。例如,您可能有几个分散在多个包中的实用程序方法。您希望这些方法对于您的所有包均可用,但是您不希望这些方法成为公共方法。为此,您可以创建一个新的命名空间,并将它用作您自己的特殊访问控制说明符。
下面的示例使用用户定义的命名空间将两个位于不同包中的函数组合在一起。通过将它们组合到同一个命名空间中,可以通过一条 use namespace 语句使这两个函数对于某个类或某个包均可见。
本示例使用四个文件来说明此方法。所有的文件都必须位于您的类路径中。第一个文件 (myInternal.as) 用来定义 myInternal 命名空间。由于该文件位于名为 example 的包中,因此您必须将该文件放在名为 example 的文件夹中。该命名空间标记为 public,因此可以导入到其它包中。
// example 文件夹中的 myInternal.as
package example
{
public namespace myInternal = "http://www.loach.net.cn2006/actionscript/examples";
}
第二个文件 (Utility.as) 和第三个文件 (Helper.as) 定义的类中包含应可供其它包使用的方法。Utility 类位于 example.alpha 包中,这意味着该文件应放在 example 文件夹下的 alpha 子文件夹中。Helper 类位于 example.beta 包中,这意味着该文件应放在 example 文件夹下的 beta 子文件夹中。这两个包(example.alpha 和 example.beta)在使用命名空间之前必须先导入它。
// example/alpha 文件夹中的 Utility.as
package example.alpha
{
import example.myInternal;

public class Utility
{
private static var _taskCounter:int = 0;

public static function someTask()
{
_taskCounter++;
}

myInternal static function get taskCounter():int
{
return _taskCounter;
}
}
}

// example/beta 文件夹中的 Helper.as
package example.beta
{
import example.myInternal;

public class Helper
{
private static var _timeStamp:Date;

public static function someTask()
{
_timeStamp = new Date();
}

myInternal static function get lastCalled():Date
{
return _timeStamp;
}
}
}
第四个文件 (NamespaceUseCase.as) 是主应用程序类,应是 example 文件夹的同级。在 Adobe Flash CS3 Professional 中,将此类用作 FLA 的文档类。NamespaceUseCase 类还导入 myInternal 命名空间,并使用它来调用位于其它包中的两个静态方法。在本示例中,使用静态方法的目的仅在于简化代码。在 myInternal 命名空间中既可以放置静态方法也可以放置实例方法。
// NamespaceUseCase.as
package
{
import flash.display.MovieClip;
import example.myInternal; // 导入命名空间
import example.alpha.Utility; // 导入 Utility 类
import example.beta.Helper; // 导入 Helper 类

public class NamespaceUseCase extends MovieClip
{
public function NamespaceUseCase()
{
use namespace myInternal;

Utility.someTask();
Utility.someTask();
trace(Utility.taskCounter); // 2

Helper.someTask();
trace(Helper.lastCalled); // [上次调用 someTask() 的时间]
}
}
}
变量
变量可用来存储程序中使用的值。要声明变量,必须将 var 语句和变量名结合使用。在 ActionScript 3.0 中,总是需要使用 var 语句。
如果在声明变量时省略了 var 语句,在严格模式下将出现编译器错误,在标准模式下将出现运行时错误。
要将变量与一个数据类型相关联,则必须在声明变量时进行此操作。在声明变量时不指定变量的类型是合法的,但这在严格模式下将产生编译器警告。可通过在变量名后面追加一个后跟变量类型的冒号 (:) 来指定变量类型。
可以使用赋值运算符 (=) 为变量赋值。您可能会发现在声明变量的同时为变量赋值可能更加方便。通常,在声明变量的同时为变量赋值的方法不仅在赋予基元值(如整数和字符串)时很常用,而且在创建数组或实例化类的实例时也很常用。
如果要声明多个变量,则可以使用逗号运算符 (,) 来分隔变量,从而在一行代码中声明所有这些变量。也可以在同一行代码中为其中的每个变量赋值。
了解变量的作用域
变量的"作用域"是指可在其中通过引用词汇来访问变量的代码区域。"全局"变量是指在代码的所有区域中定义的变量,而"局部"变量是指仅在代码的某个部分定义的变量。在 ActionScript 3.0 中,始终为变量分配声明它们的函数或类的作用域。全局变量是在任何函数或类定义的外部定义的变量。全局变量在函数定义的内部和外部均可用。
可以通过在函数定义内部声明变量来将它声明为局部变量。可定义局部变量的最小代码区域就是函数定义。在函数内部声明的局部变量仅存在于该函数中。该变量在该函数外部将不可用。
如果用于局部变量的变量名已经被声明为全局变量,那么,当局部变量在作用域内时,局部定义会隐藏(或遮蔽)全局定义。全局变量在该函数外部仍然存在。
代码块是指左大括号 ({) 与右大括号 (}) 之间的任意一组语句。在在ActionScript 中,如果您在某个代码块中声明一个变量,那么,该变量不仅在该代码块中可用,而且还在该代码块所属函数的其它任何部分都可用。
有趣的是,如果缺乏块级作用域,那么,只要在函数结束之前对变量进行声明,就可以在声明变量之前读写它。这是由于存在一种名为"提升"的方法,该方法表示编译器会将所有的变量声明移到函数的顶部。
但是,编译器将不会提升任何赋值语句。
这意味着您甚至可以在声明变量之前为变量赋值。
默认值
"默认值"是在设置变量值之前变量中包含的值。首次设置变量的值实际上就是"初始化"变量。如果您声明了一个变量,但是没有设置它的值,则该变量便处于"未初始化"状态。未初始化的变量的值取决于它的数据类型。
下表说明了变量的默认值,并按数据类型对这些值进行组织:
数据类型
默认值
Boolean
false
int
0
Number
NaN
Object
null
String
null
uint
0
未声明(与类型注释 * 等效)
undefined
其它所有类(包括用户定义的类)。
null
对于 Number 类型的变量,默认值是 NaN(而非某个数字),NaN 是一个由 IEEE-754 标准定义的特殊值,它表示非数字的某个值。
如果您声明某个变量,但是未声明它的数据类型,则将应用默认数据类型 *,这实际上表示该变量是无类型变量。如果您没有用值初始化无类型变量,则该变量的默认值是 undefined。
对于 Boolean、Number、int 和 uint 以外的数据类型,所有未初始化变量的默认值都是 null。这适用于由 Flash Player API 定义的所有类以及您创建的所有自定义类。
对于 Boolean、Number、int 或 uint 类型的变量,null 不是有效值。如果您尝试将值 null 赋予这样的变量,则该值会转换为该数据类型的默认值。对于 Object 类型的变量,可以赋予 null 值。如果您尝试将值 undefined 赋予 Object 类型的变量,则该值会转换为 null。
对于 Number 类型的变量,有一个名为 isNaN() 的特殊的顶级函数。如果变量不是数字,该函数将返回布尔值 true,否则将返回 false。
数据类型
"数据类型"用来定义一组值。ActionScript 3.0 中的所有值均是对象,而与它们是基元值还是复杂值无关。
"基元值"是一个属于下列数据类型之一的值:Boolean、int、Number、String 和 uint。基元值的处理速度通常比复杂值的处理速度快,因为 ActionScript 按照一种尽可能优化内存和提高速度的特殊方式来存储基元值。
ActionScript 在内部将基元值作为不可改变的对象进行存储。这意味着按引用传递与按值传递同样有效。这可以减少内存的使用量并提高执行速度,因为引用通常比值本身小得多。
"复杂值"是指基元值以外的值。定义复杂值的集合的数据类型包括:Array、Date、Error、Function、RegExp、XML 和 XMLList。
在 ActionScript 3.0 中,出于实用的目的,不对基元值及其包装对象加以区分。所有的值(甚至基元值)都是对象。Flash Player 将这些基元类型视为特例 ─ 它们的行为与对象相似,但是不需要创建对象所涉及的正常开销。
上面列出的所有基元数据类型和复杂数据类型都是由 ActionScript 3.0 核心类定义的。通过 ActionScript 3.0 核心类,可以使用字面值(而非 new 运算符)创建对象。
类型检查
ActionScript 3.0 是动态类型的语言,它在运行时执行类型检查,同时也支持在名为"严格模式"的特殊编译器模式下在编译时执行类型检查。在严格模式下,类型检查既发生在编译时也发生在运行时,但是在标准模式下,类型检查仅发生在运行时。
在构造代码时,动态类型的语言带来了极大的灵活性,但代价是在运行时可能出现类型错误。静态类型的语言在编译时报告类型错误,但代价是要求类型信息在编译时是已知的。
编译时类型检查
在较大的项目中通常建议使用编译时类型检查,因为随着项目变大,相对于尽早捕获类型错误,数据类型的灵活性通常会变得不那么重要。这就是为什么将 Adobe Flash CS3 Professional 和 Adobe Flex Builder 2 中的 ActionScript 编译器默认设置为在严格模式下运行的原因。
为了提供编译时类型检查,编译器需要知道代码中的变量或表达式的数据类型信息。为了显式声明变量的数据类型,请在变量名后面添加后跟数据类型的冒号运算符 (:) 作为其后缀。要将数据类型与参数相关联,应使用后跟数据类型的冒号运算符。
在严格模式下,ActionScript 编译器将类型不匹配报告为编译器错误。
但是,即使在严格模式下,也可以选择不在赋值语句右侧指定类型,从而退出编译时类型检查。可以通过省略类型注释或使用特殊的星号 (*) 类型注释,来将变量或表达式标记为无类型。
运行时类型检查
在 ActionScript 3.0 中,无论是在严格模式下还是在标准模式下编译,在运行时都将进行类型检查。
还可能会出现如下情形:即使在严格模式下运行,也可能会获得运行时类型错误。如果您使用严格模式,但是通过使用无类型变量而退出了编译时类型检查,就可能会出现上述情形。当您使用无类型变量时,并不会消除类型检查,而只是将其延迟到运行时执行。
与编译时类型检查相比,运行时类型检查还允许更灵活地使用继承。标准模式会将类型检查延迟到运行时执行,从而允许您引用子类的属性,即使您"上传"也是如此。当您使用基类来声明类实例的类型,但是使用子类构造函数来实例化类实例时,就会发生上传。上传被视为安全操作,这是因为基类不包含子类中没有的任何属性或方法。但是,子类中则包含其基类中没有的属性或方法。
is 运算符
is 运算符是 ActionScript 3.0 中的新增运算符,它可用来测试变量或表达式是否为给定数据类型的成员。is 运算符检查正确的继承层次结构,不但可以用来检查对象是否为特定类的实例,而且还可以检查对象是
否是用来实现特定接口的类的实例。
as 运算符
as 运算符是 ActionScript 3.0 中的新增运算符,也可用来检查表达式是否为给定数据类型的成员。但是,与 is 运算符不同的是,as 运算符不返回布尔值,而是返回表达式的值(代替 true)或 null(代替 false)。
在使用 as 运算符时,右侧的操作数必须是数据类型。如果尝试使用表达式(而非数据类型)作为右侧的操作数,将会产生错误。
动态类
"动态"类定义在运行时可通过添加/更改属性和方法来改变的对象。非动态类(如 String 类)是"密封"类。您不能在运行时向密封类中添加属性或方法。
在声明类时,可以通过使用 dynamic 属性来创建动态类。
如果要在以后实例化动态类的实例,则可以在类定义的外部向该类中添加属性或方法。添加到动态类实例中的属性是运行时实体,因此会在运行时完成所有类型检查。不能向以这种方式添加的属性中添加类型注释。您还可以定义一个函数并将该函数附加到动态类实例的某个属性,从而向动态类实例中添加方法。但是,以这种方式创建的方法对于动态类的任何私有属性或方法都不具有访问权限。而且,即使对动态类的公共属性或方法的引用也必须用 this 关键字或类名进行限定。
数据类型说明
基元数据类型包括 Boolean、int、Null、Number、String、uint 和 void。ActionScript 核心类还定义下列复杂数据类型:Object、Array、Date、Error、Function、RegExp、XML 和 XMLList。
Boolean 数据类型
Boolean 数据类型包含两个值:true 和 false。对于 Boolean 类型的变量,其它任何值都是无效的。已经声明但尚未初始化的布尔变量的默认值是 false。
int 数据类型
int 数据类型在内部存储为 32 位整数,它包含一组介于 -2,147,483,648 (-231) 和 2,147,483,647 (231 - 1) 之间的整数(包括 -2,147,483,648 和 2,147,483,647)。早期的 ActionScript 版本仅提供 Number 数据类型,该数据类型既可用于整数又可用于浮点数。在 ActionScript 3.0 中,现在可以访问 32 位带符号整数和无符号整数的低位机器类型。如果您的变量将不会使用浮点数,那么,使用 int 数据类型来代替 Number 数据类型应会更快更高效。
对于小于 int 的最小值或大于 int 的最大值的整数值,应使用 Number 数据类型。Number 数据类型可以处理 -9,007,199,254,740,992 和 9,007,199,254,740,992(53 位整数值)之间的值。int 数据类型的变量的默认值是 0。
Null 数据类型
Null 数据类型仅包含一个值:null。这是 String 数据类型和用来定义复杂数据类型的所有类(包括 Object 类)的默认值。其它基元数据类型(如 Boolean、Number、int 和 uint)均不包含 null 值。如果您尝试向 Boolean、Number、int 或 uint 类型的变量赋予 null,则 Flash Player 会将 null 值转换为相应的默认值。不能将 Null 数据类型用作类型注释。
Number 数据类型
在 ActionScript 3.0 中,Number 数据类型可以表示整数、无符号整数和浮点数。但是,为了尽可能提高性能,应将 Number 数据类型仅用于浮点数,或者用于 int 和 uint 类型可以存储的、大于 32 位的整数值。要存储浮点数,数字中应包括一个小数点。如果您省略了小数点,数字将存储为整数。
Number 数据类型使用由 IEEE 二进制浮点算术标准 (IEEE-754) 指定的 64 位双精度格式。此标准规定如何使用 64 个可用位来存储浮点数。其中的 1 位用来指定数字是正数还是负数。11 位用于指数,它以二进制的形式存储。其余的 52 位用于存储"有效位数"(又称为"尾数"),有效位数是 2 的 N 次幂,N 即前面所提到的指数。
可以将 Number 数据类型的所有位都用于有效位数,也可以将 Number 数据类型的某些位用于存储指数,后者可存储的浮点数比前者大得多。
Number 类型可以表示的最小值和最大值存储在 Number 类的名为 Number.MAX_VALUE 和 Number.MIN_VALUE 的静态属性中。
Number.MAX_VALUE == 1.79769313486231e+308
Number.MIN_VALUE == 4.940656458412467e-324
尽管这个数字范围很大,但代价是此范围的精度有所降低。Number 数据类型使用 52 位来存储有效位数,因此,那些要求用 52 位以上的位数才能精确表示的数字(如分数 1/3)将只是近似值。如果应用程序要求小数达到绝对精度,则需要使用实现小数浮点算术(而非二进制浮点算术)的软件。
如果用 Number 数据类型来存储整数值,则仅使用 52 位有效位数。Number 数据类型使用 52 位和一个特殊的隐藏位来表示介于 -9,007,199,254,740,992 (-253) 和 9,007,199,254,740,992 (253) 之间的整数。
Flash Player 不但将 NaN 值用作 Number 类型的变量的默认值,而且还将其用作应返回数字、却没有返回数字的任何运算的结果。其它特殊的 Number 值包括"正无穷大"和"负无穷大"。
在被 0 除时,如果被除数也是 0,则结果只有一个,那就是 NaN。在被 0 除时,如果被除数是正数,则结果为正无穷大;如果被除数是负数,则结果为负无穷大。
String 数据类型
String 数据类型表示一个 16 位字符的序列。字符串在内部存储为 Unicode 字符,并使用 UTF-16 格式。字符串是不可改变的值,就像在 Java 编程语言中一样。对字符串值执行运算会返回字符串的一个新实例。用 String 数据类型声明的变量的默认值是 null。虽然 null 值与空字符串 ("") 均表示没有任何字符,但二者并不相同。
uint 数据类型
uint 数据类型在内部存储为 32 位无符号整数,它包含一组介于 0 和 4,294,967,295 (232- 1) 之间的整数(包括 0 和 4,294,967,295)。uint 数据类型可用于要求非负整数的特殊情形。例如,必须使用 uint 数据类型来表示像素颜色值,因为 int 数据类型有一个内部符号位,该符号位并不适合处理颜色值。对于大于 uint 的最大值的整数值,应使用 Number 数据类型,该数据类型可以处理 53 位整数值。uint 数据类型的变量的默认值是 0。
void 数据类型
void 数据类型仅包含一个值:undefined。在 ActionScript 3.0 中,Object 实例的默认值是 null。如果您尝试将值 undefined 赋予 Object 类的实例,Flash Player 会将该值转换为 null。您只能为无类型变量赋予 undefined 这一值。无类型变量是指缺乏类型注释或者使用星号 (*) 作为类型注释的变量。只能将 void 用作返回类型注释。
Object 数据类型
Object 数据类型是由 Object 类定义的。Object 类用作 ActionScript 中的所有类定义的基类。ActionScript 3.0 中的 Object 数据类型与早期版本中的 Object 数据类型存在以下三方面的区别:第一,Object 数据类型不再是指定给没有类型注释的变量的默认数据类型。第二,Object 数据类型不再包括 undefined 这一值,该值以前是 Object 实例的默认值。第三,在 ActionScript 3.0 中,Object 类实例的默认值是 null。
在早期的 ActionScript 版本中,会自动为没有类型注释的变量赋予 Object 数据类型。ActionScript 3.0 现在包括真正无类型变量这一概念,因此不再为没有类型注释的变量赋予 Object 数据类型。没有类型注释的变量现在被视为无类型变量。如果您希望向代码的读者清楚地表明您是故意将变量保留为无类型,可以使用新的星号 (*) 表示类型注释,这与省略类型注释等效。
只有无类型变量才能保存值 undefined。如果您尝试将值 undefined 赋给具有数据类型的变量,Flash Player 会将该值 undefined 转换为该数据类型的默认值。对于 Object 数据类型的实例,默认值是 null,这意味着,如果尝试将 undefined 赋给 Object 实例,Flash Player 会将值 undefined 转换为 null。
类型转换
在将某个值转换为其它数据类型的值时,就说发生了类型转换。类型转换可以是"隐式的",也可以是"显式的"。隐式转换又称为"强制",有时由 Flash Player 在运行时执行。显式转换又称为"转换",在代码指示编译器将一个数据类型的变量视为属于另一个数据类型时发生。在涉及基元值时,转换功能将一个数据类型的值实际转换为另一个数据类型的值。
要将对象转换为另一类型,请用小括号括起对象名并在它前面加上新类型的名称。
隐式转换
对于用户定义的类型,当要转换的值是目标类(或者派生自目标类的类)的实例时,隐式转换会成功。如果隐式转换不成功,就会出现错误。
对于基元类型而言,隐式转换是通过调用内部转换算法来处理的,该算法与显式转换函数所调用的算法相同。
显式转换
在严格模式下进行编译时,使用显式转换会非常有用,因为您有时可能会不希望因类型不匹配而生成编译时错误。当您知道强制功能会在运行时正确转换您的值时,可能就属于这种情况。
转换为 int、uint 和 Number
您可以将任何数据类型转换为以下三种数字类型之一:int、uint 和 Number。如果 Flash Player 由于某种原因而无法转换数字,则会为 int 和 uint 数据类型赋予默认值 0,为 Number 数据类型赋予默认值 NaN。如果将布尔值转换为数字,则 true 变成值 1,false 变成值 0。
仅包含数字的字符串值可以成功地转换为数字类型之一。看上去像负数的字符串或者表示十六进制值的字符串(例如,0x1A)也可以转换为数字类型。转换过程中会忽略字符串值中的前导或尾随空白字符。还可以使用 Number() 来转换看上去像浮点数的字符串。如果包含小数点,则会导致 uint() 和 int() 返回一个整数,小数点和它后面的字符被截断。
对于包含非数字字符的字符串值,在用 int() 或 uint() 转换时,将返回 0;在用 Number() 转换时,将返回 NaN。转换过程中会忽略前导和尾随空白,但是,如果字符串中包含将两个数字隔开的空白,则将返回 0 或 NaN。
在 ActionScript 3.0 中,Number() 函数不再支持八进制数或基数为 8 的数。对于 ActionScript 3.0 中的 Number() 函数, 会忽略前导 0。
将一种数值类型的值赋给另一种数值类型的变量时,转换并不是必需的。即使在严格模式下,数值类型也会隐式转换为其它数值类型。这意味着,在某些情况下,在超出类型的范围时,可能会生成意外的值。
var myUInt:uint = -3; // 将 int/Number 值赋给 uint 变量
trace(myUInt); // 4294967293
var myNum:Number = sampleUINT; // 将 int/uint 值赋给 Number 变量
trace(myNum) // 4294967293
var myInt:int = uint.MAX_VALUE + 1; // 将 Number 值赋给 uint 变量
trace(myInt); // 0
myInt = int.MAX_VALUE + 1; // 将 uint/Number 值赋给 int 变量
trace(myInt); // -2147483648
下表概述了将其它数据类型转换为 Number、int 或 uint 数据类型的结果。
数据类型或值
转换为 Number、int 或 uint 时的结果
Boolean
如果值为 true,则结果为 1;否则为 0。
Date
Date 对象的内部表示形式,即从 1970 年 1 月 1 日午夜(通用时间)以来所经过的毫秒数。
null
0
Object
如果实例为 null 并转换为 Number,则结果为 NaN;否则为 0。
String
如果 Flash Player 可以将字符串转换为数字,则结果为数字;否则,如果转换为 Number,则结果为 NaN,如果转换为 int 或 uint,则结果为 0。
undefined
如果转换为 Number,则结果为 NaN;如果转换为 int 或 uint,则结果为 0。
转换为 Boolean
在从任何数值数据类型(uint、int 和 Number)转换为 Boolean 时,如果数值为 0,则结果为 false;否则为 true。对于 Number 数据类型,如果值为 NaN,则结果也为 false。
在将字符串值转换为 Boolean 数据类型时,如果字符串为 null 或空字符串 (""),则会返回 false。否则,将返回 true。
在将 Object 类的实例转换为 Boolean 数据类型时,如果该实例为 null,则将返回 false;否则将返回 true。
在严格模式下,系统会对布尔变量进行特殊处理,因为您不必转换即可向布尔变量赋予任何数据类型的值。即使在严格模式下,也可以将所有的数据类型隐式强制为 Boolean 数据类型。换言之,与几乎其它所有数据类型不同,转换为 Boolean 数据类型不是避免在严格模式下出错所必需的。
下表概述了在从其它数据类型转换为 Boolean 数据类型时的结果:
数据类型或值
转换为 Boolean 数据类型时的结果
String
如果值为 null 或空字符串 (""),则结果为 false;否则为 true。
null
false
Number、int 或 uint
如果值为 NaN 或 0,则结果为 false;否则为 true。
Object
如果实例为 null,则结果为 false;否则为 true。
转换为 String
从任何数值数据类型转换为 String 数据类型时,都会返回数字的字符串表示形式。在将布尔值转换为 String 数据类型时,如果值为 true,则返回字符串 "true";如果值为 false,则返回字符串 "false"。
在从 Object 类的实例转换为 String 数据类型时,如果该实例为 null,则返回字符串 "null"。否则,将返回字符串 "[object Object]"。
在从 Array 类的实例转换为 String 时,会返回一个字符串,其中包含所有数组元素的逗号分隔列表。
在从 Date 类的实例转换为 String 时,会返回该实例所包含日期的字符串表示形式。(输出结果显示的是太平洋夏令时)


下表概述了在将其它数据类型转换为 String 数据类型时的结果:
数据类型或值
转换为 String 数据类型时的结果
Array
一个包含所有数组元素的字符串。
Boolean
"true" 或 "false"。
Date
Date 对象的字符串表示形式。
null
"null"
Number、int 或 uint
数字的字符串表示形式。
Object
如果实例为 null,则结果为 "null";否则为 "[object Object]"。
语法
语言的语法定义了一组在编写可执行代码时必须遵循的规则。
区分大小写
ActionScript 3.0 是一种区分大小写的语言。只是大小写不同的标识符会被视为不同。
点语法
可以通过点运算符 ( . ) 来访问对象的属性和方法。使用点语法,可以使用后跟点运算符和属性名或方法名的实例名来引用类的属性或方法。
定义包时,可以使用点语法。可以使用点运算符来引用嵌套包。
字面值
"字面值"是直接出现在代码中的值。
字面值还可以组合起来构成复合字面值。数组文本括在中括号字符 ([ ]) 中,各数组元素之间用逗号隔开。数组文本可用于初始化数组。您可以使用 new 语句将复合字面值作为参数传递给 Array 类构造函数,但是,您还可以在实例化下面的 ActionScript 核心类的实例时直接赋予字面值:Object、Array、String、Number、int、uint、XML、XMLList 和 Boolean。
字面值还可用来初始化通用对象。通用对象是 Object 类的一个实例。对象字面值括在大括号 ({ }) 中,各对象属性之间用逗号隔开。每个属性都用冒号字符 ( : ) 进行声明,冒号用于分隔属性名和属性值。
可以使用 new 语句创建一个通用对象并将该对象的字面值作为参数传递给 Object 类构造函数,也可以在声明实例时直接将对象字面值赋给实例。
分号
可以使用分号字符 ( ; ) 来终止语句。如果您省略分号字符,则编译器将假设每一行代码代表一条语句。由于很多程序员都习惯使用分号来表示语句结束,因此,如果您坚持使用分号来终止语句,则代码会更易于阅读。
使用分号终止语句可以在一行中放置多个语句,但是这样会使代码变得难以阅读。
小括号
在 ActionScript 3.0 中,可以通过三种方式来使用小括号 (())。
首先,可以使用小括号来更改表达式中的运算顺序。组合到小括号中的运算总是最先执行。
第二,可以结合使用小括号和逗号运算符 (,) 来计算一系列表达式并返回最后一个表达式的结果。
第三,可以使用小括号来向函数或方法传递一个或多个参数。
注释
ActionScript 3.0 代码支持两种类型的注释:单行注释和多行注释。这些注释机制与 C++ 和 Java 中的注释机制类似。编译器将忽略标记为注释的文本。
单行注释以两个正斜杠字符 (//) 开头并持续到该行的末尾。
多行注释以一个正斜杠和一个星号 (/*) 开头,以一个星号和一个正斜杠 (*/) 结尾。
关键字和保留字
"保留字"是一些单词,因为这些单词是保留给 ActionScript 使用的,所以,不能在代码中将它们用作标识符。保留字包括"词汇关键字",编译器将词汇关键字从程序的命名空间中删除。如果您将词汇关键字用作标识符,则编译器会报告一个错误。



下表列出了 ActionScript 3.0 词汇关键字:
as
break
case
catch
class
const
continue
default
delete
do
else
extends
false
finally
for
function
if
implements
import
in
instanceof
interface
internal
is
native
new
null
package
private
protected
public
return
super
switch
this
throw
to
true
try
typeof
use
var
void
while
with



有一小组名为"句法关键字"的关键字,这些关键字可用作标识符,但是在某些上下文中具有特殊的含义。下表列出了 ActionScript 3.0 句法关键字:
each
get
set
namespace
include
dynamic
final
native
override
static


还有几个有时称为"供将来使用的保留字"的标识符。这些标识符不是为 ActionScript 3.0 保留的,但是其中的一些可能会被采用 ActionScript 3.0 的软件视为关键字。您可以在自己的代码中使用其中的许多标识符,但是 Adobe 不建议您使用它们,因为它们可能会在以后的 ActionScript 版本中作为关键字出现。
abstract
boolean
byte
cast
char
debugger
double
enum
export
float
goto
intrinsic
long
prototype
short
synchronized
throws
to
transient
type
virtual
volatile


常量
ActionScript 3.0 支持 const 语句,该语句可用来创建常量。常量是指具有无法改变的固定值的属性。只能为常量赋值一次,而且必须在最接近常量声明的位置赋值。例如,如果将常量声明为类的成员,则只能在声明过程中或者在类构造函数中为常量赋值。
如果您尝试以其它任何方法向常量赋予初始值,则会出现错误。
Flash Player API 定义了一组广泛的常量供您使用。按照惯例,ActionScript 中的常量全部使用大写字母,各个单词之间用下划线字符 ( _) 分隔。
运算符
运算符是一种特殊的函数,它们具有一个或多个操作数并返回相应的值。"操作数"是被运算符用作输入的值,通常是字面值、变量或表达式。
运算符可以是一元、二元或三元的。"一元"运算符有 1 个操作数。"二元"运算符有 2 个操作数。"三元"运算符有 3 个操作数。
有些运算符是"重载的",这意味着它们的行为因传递给它们的操作数的类型或数量而异。例如,加法运算符 (+) 就是一个重载运算符,其行为因操作数的数据类型而异。如果两个操作数都是数字,则加法运算符会返回这些值的和。如果两个操作数都是字符串,则加法运算符会返回这两个操作数连接后的结果。
运算符的行为还可能因所提供的操作数的数量而异。减法运算符 (-) 既是一元运算符又是二元运算符。对于减法运算符,如果只提供一个操作数,则该运算符会对操作数求反并返回结果;如果提供两个操作数,则减法运算符返回这两个操作数的差。

运算符的优先级和结合律
运算符的优先级和结合律决定了运算符的处理顺序。虽然对于熟悉算术的人来说,编译器先处理乘法运算符 (*) 然后再处理加法运算符 (+) 似乎是自然而然的事情,但实际上编译器要求显式指定先处理哪些运算符。此类指令统称为"运算符优先级"。ActionScript 定义了一个默认的运算符优先级,您可以使用小括号运算符 (()) 来改变它。
您可能会遇到这样的情况:同一个表达式中出现两个或更多个具有相同的优先级的运算符。在这些情况下,编译器使用"结合律"的规则来确定先处理哪个运算符。除了赋值运算符之外,所有二进制运算符都是"左结合"的,也就是说,先处理左边的运算符,然后再处理右边的运算符。赋值运算符和条件运算符 (?:) 都是"右结合"的,也就是说,先处理右边的运算符,然后再处理左边的运算符。
如果将具有相同的优先级的两个运算符用于同一个表达式中,那么,由于这两个运算符都是左结合的,因此先处理左边的运算符。您可以用括号运算符来改变默认的左结合律。您可以通过用小括号括起小于运算符及其操作数来命令编译器先处理小于运算符。
下表按优先级递减的顺序列出了 ActionScript 3.0 中的运算符。该表内同一行中的运算符具有相同的优先级。在该表中,每行运算符都比位于其下方的运算符的优先级高。

运算符
主要
[] {x:y} () f(x) new x.y x[y] <></> @ :: ..
后缀
x++ x--
一元
++x --x + - ~ ! delete typeof void
乘法
* / %
加法
+ -
按位移位
<< >> >>>
关系
< > <= >= as in instanceof is
等于
== != === !==
按位"与"
&
按位"异或"
^
按位"或"
|
逻辑"与"
&&
逻辑"或"
||
条件
?:
赋值
= *= /= %= += -= <<= >>= >>>= &= ^= |=
逗号
,
主要运算符
主要运算符包括用来创建 Array 和 Object 字面值、对表达式进行分组、调用函数、实例化类实例以及访问属性的运算符。下表列出了所有主要运算符,它们具有相同的优先级。属于E4X 规范的运算符用 (E4X) 来表示。
运算符
执行的运算
[]
初始化数组
{x:y}
初始化对象
()
对表达式进行分组
f(x)
调用函数
new
调用构造函数
x.y x[y]
访问属性
<></>
初始化 XMLList 对象 (E4X)
@
访问属性 (E4X)
::
限定名称 (E4X)
..
访问子级 XML 元素 (E4X)
后缀运算符
后缀运算符只有一个操作数,它递增或递减该操作数的值。虽然这些运算符是一元运算符,但是它们有别于其它一元运算符,被单独划归到了一个类别,因为它们具有更高的优先级和特殊的行为。在将后缀运算符用作较长表达式的一部分时,会在处理后缀运算符之前返回表达式的值。
下表列出了所有的后缀运算符,它们具有相同的优先级:
运算符
执行的运算
++
递增(后缀)
--
递减(后缀)
一元运算符
一元运算符只有一个操作数。这一组中的递增运算符 (++) 和递减运算符 (--) 是"前缀运算符",这意味着它们在表达式中出现在操作数的前面。前缀运算符与它们对应的后缀运算符不同,因为递增或递减操作是在返回整个表达式的值之前完成的。
下表列出了所有的一元运算符,它们具有相同的优先级:
运算符
执行的运算
++
递增(前缀)
--
递减(前缀)
+
一元 +
-
一元 -(非)
!
逻辑"非"
~
按位"非"
delete
删除属性
typeof
返回类型信息
void
返回 undefined 值
乘法运算符
乘法运算符具有两个操作数,它执行乘、除或求模计算。
下表列出了所有的乘法运算符,它们具有相同的优先级:
运算符
执行的运算
*
乘法
/
除法
%
求模
加法运算符
加法运算符有两个操作数,它执行加法或减法计算。下表列出了所有加法运算符,它们具有相同的优先级:
运算符
执行的运算
+
加法
-
减法
按位移位运算符
按位移位运算符有两个操作数,它将第一个操作数的各位按第二个操作数指定的长度移位。下表列出了所有按位移位运算符,它们具有相同的优先级:
运算符
执行的运算
<<
按位向左移位
>>
按位向右移位
>>>
按位无符号向右移位



关系运算符
关系运算符有两个操作数,它比较两个操作数的值,然后返回一个布尔值。下表列出了所有关系运算符,它们具有相同的优先级:
运算符
执行的运算
<
小于
>
大于
<=
小于或等于
>=
大于或等于
as
检查数据类型
in
检查对象属性
instanceof
检查原型链
is
检查数据类型
等于运算符
等于运算符有两个操作数,它比较两个操作数的值,然后返回一个布尔值。下表列出了所有等于运算符,它们具有相同的优先级:
运算符
执行的运算
==
等于
!=
不等于
===
严格等于
!==
严格不等于
按位逻辑运算符
按位逻辑运算符有两个操作数,它执行位级别的逻辑运算。按位逻辑运算符具有不同的优先级;下表按优先级递减的顺序列出了按位逻辑运算符:
运算符
执行的运算
&
按位"与"
^
按位"异或"
|
按位"或"
逻辑运算符
逻辑运算符有两个操作数,它返回布尔结果。逻辑运算符具有不同的优先级;下表按优先级递减的顺序列出了逻辑运算符:
运算符
执行的运算
&&
逻辑"与"
||
逻辑"或"
条件运算符
条件运算符是一个三元运算符,也就是说它有三个操作数。条件运算符是应用 if..else 条件语句的一种简便方法。
运算符
执行的运算
?:
条件
赋值运算符
赋值运算符有两个操作数,它根据一个操作数的值对另一个操作数进行赋值。下表列出了所有赋值运算符,它们具有相同的优先级:




运算符
执行的运算
=
赋值
*=
乘法赋值
/=
除法赋值
%=
求模赋值
+=
加法赋值
-=
减法赋值
<<=
按位向左移位赋值
>>=
按位向右移位赋值
>>>=
按位无符号向右移位赋值
&=
按位"与"赋值
^=
按位"异或"赋值
|=
按位"或"赋值

条件语句
ActionScript 3.0 提供了三个可用来控制程序流的基本条件语句。 if..else if..else if switch
if..else
if..else 条件语句用于测试一个条件,如果该条件存在,则执行一个代码块,否则执行替代代码块。
如果您不想执行替代代码块,可以仅使用 if 语句,而不用 else 语句。
if..else if
可以使用 if..else if 条件语句来测试多个条件。
switch
如果多个执行路径依赖于同一个条件表达式,则 switch 语句非常有用。它的功能大致相当于一系列 if..else if 语句,但是它更便于阅读。switch 语句不是对条件进行测试以获得布尔值,而是对表达式进行求值并使用计算结果来确定要执行的代码块。代码块以 case 语句开头,以 break 语句结尾。
循环
循环语句允许您使用一系列值或变量来反复执行一个特定的代码块。
for for..in for each..in while do..while
for
for 循环用于循环访问某个变量以获得特定范围的值。必须在 for 语句中提供 3 个表达式:一个设置了初始值的变量,一个用于确定循环何时结束的条件语句,以及一个在每次循环中都更改变量值的表达式。
for..in
for..in 循环用于循环访问对象属性或数组元素。
如果对象是自定义类的一个实例,则除非该类是动态类,否则将无法循环访问该对象的属性。即便对于动态类的实例,也只能循环访问动态添加的属性。
for each..in
for each..in 循环用于循环访问集合中的项目,它可以是 XML 或 XMLList 对象中的标签、对象属性保存的值或数组元素。您可以使用 for each..in 循环来循环访问通用对象的属性,但是与 for..in 循环不同的是,for each..in 循环中的迭代变量包含属性所保存的值,而不包含属性的名称。
如果对象是密封类的实例,则您将无法循环访问该对象的属性。即使对于动态类的实例,也无法循环访问任何固定属性(即,作为类定义的一部分定义的属性)。
while
while 循环与 if 语句相似,只要条件为 true,就会反复执行。
使用 while 循环(而非 for 循环)的一个缺点是,编写的 while 循环中更容易出现无限循环。如果省略了用来递增计数器变量的表达式,则 for 循环示例代码将无法编译,而 while 循环示例代码仍然能够编译。若没有用来递增 i 的表达式,循环将成为无限循环。
do..while
do..while 循环是一种 while 循环,它保证至少执行一次代码块,这是因为在执行代码块后才会检查条件。

函数
"函数"是执行特定任务并可以在程序中重用的代码块。ActionScript 3.0 中有两类函数:"方法"和"函数闭包"。将函数称为方法还是函数闭包取决于定义函数的上下文。如果您将函数定义为类定义的一部分或者将它附加到对象的实例,则该函数称为方法。如果您以其它任何方式定义函数,则该函数称为函数闭包。
函数的基本概念
调用函数
可通过使用后跟小括号运算符 (()) 的函数标识符来调用函数。要发送给函数的任何函数参数都括在小括号中。
如果要调用没有参数的函数,则必须使用一对空的小括号。
定义您自己的函数
在 ActionScript 3.0 中可通过两种方法来定义函数:使用函数语句和使用函数表达式。您可以根据自己的编程风格(偏于静态还是偏于动态)来选择相应的方法。如果您倾向于采用静态或严格模式的编程,则应使用函数语句来定义函数。如果您有特定的需求,需要用函数表达式来定义函数,则应这样做。函数表达式更多地用在动态编程或标准模式编程中。
函数语句
函数语句是在严格模式下定义函数的首选方法。函数语句以 function 关键字开头,后跟:
函数名
用小括号括起来的逗号分隔参数列表
用大括号括起来的函数体 ─ 即,在调用函数时要执行的 ActionScript 代码
函数表达式
声明函数的第二种方法就是结合使用赋值语句和函数表达式,函数表达式有时也称为函数字面值或匿名函数。这是一种较为繁杂的方法,在早期的 ActionScript 版本中广为使用。
带有函数表达式的赋值语句以 var 关键字开头,后跟:
函数名
冒号运算符 (:)
指示数据类型的 Function 类
赋值运算符 (=)
function 关键字
用小括号括起来的逗号分隔参数列表
用大括号括起来的函数体 ─ 即,在调用函数时要执行的 ActionScript 代码
函数表达式和函数语句的另一个重要区别是,函数表达式是表达式,而不是语句。这意味着函数表达式不能独立存在,而函数语句则可以。函数表达式只能用作语句(通常是赋值语句)的一部分。
在函数语句和函数表达式之间进行选择
原则上,除非在特殊情况下要求使用表达式,否则应使用函数语句。函数语句较为简洁,而且与函数表达式相比,更有助于保持严格模式和标准模式的一致性。
函数语句比包含函数表达式的赋值语句更便于阅读。与函数表达式相比,函数语句使代码更为简洁而且不容易引起混淆,因为函数表达式既需要 var 关键字又需要 function 关键字。
函数语句更有助于保持严格模式和标准模式的一致性,因为在这两种编译器模式下,均可以借助点语法来调用使用函数语句声明的方法。但这对于用函数表达式声明的方法却不一定成立。
一般认为,函数表达式更适合于关注运行时行为或动态行为的编程。如果您喜欢使用严格模式,但是还需要调用使用函数表达式声明的方法,则可以使用这两种方法中的任一方法。
首先,可以使用中括号 ([ ]) 代替点运算符 ( . ) 来调用该方法。
第二,您可以将整个类声明为动态类。尽管这样您就可以使用点运算符来调用方法,但缺点是,该类的所有实例在严格模式下都将丢失一些功能。例如,如果您尝试访问动态类实例的未定义属性,则编译器不生成错误。
函数语句与函数表达式之间有两个细微的区别,在选择要使用的方法时,应考虑这两个区别。
第一个区别体现在内存管理和垃圾回收方面,因为函数表达式不像对象那样独立存在。换言之,当您将某个函数表达式分配给另一个对象(如数组元素或对象属性)时,就会在代码中创建对该函数表达式的唯一引用。如果该函数表达式所附加到的数组或对象脱离作用域或由于其它原因不再可用,您将无法再访问该函数表达式。如果删除该数组或对象,该函数表达式所使用的内存将符合垃圾回收条件,这意味着内存符合回收条件并且可重新用于其它用途。
函数语句与函数表达式之间的第二个区别是,函数语句存在于定义它们的整个作用域(包括出现在该函数语句前面的语句)内。与之相反,函数表达式只是为后续的语句定义的。函数表达式只有在定义之后才可用。
从函数中返回值
要从函数中返回值,请使用后跟要返回的表达式或字面值的 return 语句。
请注意,return 语句会终止该函数,因此,不会执行位于 return 语句下面的任何语句。
在严格模式下,如果您选择指定返回类型,则必须返回相应类型的值。
嵌套函数
您可以嵌套函数,这意味着函数可以在其它函数内部声明。除非将对嵌套函数的引用传递给外部代码,否则嵌套函数将仅在其父函数内可用。
在将嵌套函数传递给外部代码时,它们将作为函数闭包传递,这意味着嵌套函数保留在定义该函数时处于作用域内的任何定义。
函数参数
按值或按引用传递参数
在许多编程语言中,一定要了解按值传递参数与按引用传递参数之间的区别,二者之间的区别会影响代码的设计方式。
按值传递意味着将参数的值复制到局部变量中以便在函数内使用。按引用传递意味着将只传递对参数的引用,而不传递实际值。这种方式的传递不会创建实际参数的任何副本,而是会创建一个对变量的引用并将它作为参数传递,并且会将它赋给局部变量以便在函数内部使用。局部变量是对函数外部的变量的引用,它使您能够更改初始变量的值。
在 ActionScript 3.0 中,所有的参数均按引用传递,因为所有的值都存储为对象。但是,属于基元数据类型(包括 Boolean、Number、int、uint 和 String)的对象具有一些特殊运算符,这使它们可以像按值传递一样工作。例如,下面的代码创建一个名为 passPrimitives() 的函数,该函数定义了两个类型均为 int、名称分别为 xParam 和 yParam 的参数。这些参数与在 passPrimitives() 函数体内声明的局部变量类似。当使用 xValue 和 yValue 参数调用函数时,xParam 和 yParam 参数将用对 int 对象的引用进行初始化,int 对象由 xValue 和 yValue 表示。因为参数是基元值,所以它们像按值传递一样工作。尽管 xParam 和 yParam 最初仅包含对 xValue 和 yValue 对象的引用,但是,对函数体内的变量的任何更改都会导致在内存中生成这些值的新副本。
function passPrimitives(xParam:int, yParam:int):void
{
xParam++;
yParam++;
trace(xParam, yParam);
}

var xValue:int = 10;
var yValue:int = 15;
trace(xValue, yValue); // 10 15
passPrimitives(xValue, yValue); // 11 16
trace(xValue, yValue); // 10 15
在 passPrimitives() 函数内部,xParam 和 yParam 的值递增,但这不会影响 xValue 和 yValue 的值,如上一条 trace 语句所示。即使参数的命名与 xValue 和 yValue 变量的命名完全相同也是如此,因为函数内部的 xValue 和 yValue 将指向内存中的新位置,这些位置不同于函数外部同名的变量所在的位置。
其它所有对象(即不属于基元数据类型的对象)始终按引用传递,这样您就可以更改初始变量的值。例如,下面的代码创建一个名为 objVar 的对象,该对象具有两个属性:x 和 y。该对象作为参数传递给 passByRef() 函数。因为该对象不是基元类型,所以它不但按引用传递,而且还保持一个引用。这意味着对函数内部的参数的更改将会影响到函数外部的对象属性。
function passByRef(objParam:Object):void
{
objParam.x++;
objParam.y++;
trace(objParam.x, objParam.y);
}
var objVar:Object = {x:10, y:15};
trace(objVar.x, objVar.y); // 10 15
passByRef(objVar); // 11 16
trace(objVar.x, objVar.y); // 11 16
objParam 参数与全局 objVar 变量引用相同的对象。正如在本示例的 trace 语句中所看到的一样,对 objParam 对象的 x 和 y 属性所做的更改将反映在 objVar 对象中。
默认参数值
ActionScript 3.0 中新增了为函数声明"默认参数值"的功能。如果在调用具有默认参数值的函数时省略了具有默认值的参数,那么,将使用在函数定义中为该参数指定的值。所有具有默认值的参数都必须放在参数列表的末尾。指定为默认值的值必须是编译时常量。如果某个参数存在默认值,则会有效地使该参数成为"可选参数"。没有默认值的参数被视为"必需的参数"。
arguments 对象
在将参数传递给某个函数时,可以使用 arguments 对象来访问有关传递给该函数的参数的信息。arguments 对象的一些重要方面包括:
arguments 对象是一个数组,其中包括传递给函数的所有参数。
arguments.length 属性报告传递给函数的参数数量。
arguments.callee 属性提供对函数本身的引用,该引用可用于递归调用函数表达式。
如果将任何参数命名为 arguments,或者使用 ...(rest) 参数,则 arguments 对象不可用。
在 ActionScript 3.0 中,函数调用中所包括的参数的数量可以大于在函数定义中所指定的参数数量,但是,如果参数的数量小于必需参数的数量,在严格模式下将生成编译器错误。您可以使用 arguments 对象的数组样式来访问传递给函数的任何参数,而无需考虑是否在函数定义中定义了该参数。
arguments.callee 属性通常用在匿名函数中以创建递归。您可以使用它来提高代码的灵活性。如果递归函数的名称在开发周期内的不同阶段会发生改变,而且您使用的是 arguments.callee(而非函数名),则不必花费精力在函数体内更改递归调用。
如果您在函数声明中使用 ...(rest) 参数,则不能使用 arguments 对象,而必须使用为参数声明的参数名来访问参数。还应避免将 "arguments" 字符串作为参数名,因为它将遮蔽 arguments 对象。
...(rest) 参数
ActionScript 3.0 中引入了一个称为 ...(rest) 参数的新参数声明。此参数可用来指定一个数组参数以接受任意多个以逗号分隔的参数。此参数可以拥有保留字以外的任意名称。此参数声明必须是最后一个指定的参数。使用此参数会使 arguments 对象变得不可用。尽管 ...(rest) 参数提供了与 arguments 数组和 arguments.length 属性相同的功能,但是它不提供与 arguments.callee 类似的功能。使用 ...(rest) 参数之前,应确保不需要使用 arguments.callee。
...(rest) 参数还可与其它参数一起使用,前提是它是最后一个列出的参数。并且其它参数不再属于由 ...(rest) 参数创建的数组。
函数作为对象
ActionScript 3.0 中的函数是对象。当您创建函数时,就是在创建对象,该对象不仅可以作为参数传递给另一个函数,而且还可以有附加的属性和方法。
作为参数传递给另一个函数的函数是按引用(而不是按值)传递的。在将某个函数作为参数传递时,只能使用标识符,而不能使用在调用方法时所用的小括号运算符。
尽管刚接触 ActionScript 的程序员可能对此感觉有些奇怪,但是,函数确实可以像其它任何对象那样具有属性和方法。实际上,每个函数都有一个名为 length 的只读属性,它用来存储为该函数定义的参数数量。该属性与 arguments.length 属性不同,后者报告发送给函数的参数数量。回想一下,在 ActionScript 中,发送给函数的参数数量可以超过为该函数定义的参数数量。
您可以定义自己的函数属性,方法是在函数体外部定义它们。函数属性可以用作准静态属性,用来保存与该函数有关的变量的状态。
函数作用域
函数的作用域不但决定了可以在程序中的什么位置调用函数,而且还决定了函数可以访问哪些定义。适用于变量标识符的作用域规则同样也适用于函数标识符。在全局作用域中声明的函数在整个代码中都可用。嵌套函数(即在另一个函数中声明的函数)可以用在声明它的函数中的任意位置。
作用域链
无论何时开始执行函数,都会创建许多对象和属性。首先,会创建一个称为"激活对象"的特殊对象,该对象用于存储在函数体内声明的参数以及任何局部变量或函数。由于激活对象属于内部机制,因此您无法直接访问它。接着,会创建一个"作用域链",其中包含由 Flash Player 检查标识符声明的对象的有序列表。所执行的每个函数都有一个存储在内部属性中的作用域链。对于嵌套函数,作用域链始于其自己的激活对象,后跟其父函数的激活对象。作用域链以这种方式延伸,直到到达全局对象。全局对象是在 ActionScript 程序开始时创建的,其中包含所有的全局变量和函数。
函数闭包
"函数闭包"是一个对象,其中包含函数的快照及其"词汇环境"。函数的词汇环境包括函数作用域链中的所有变量、属性、方法和对象以及它们的值。无论何时在对象或类之外的位置执行函数,都会创建函数闭包。函数闭包保留定义它们的作用域,这样,在将函数作为参数或返回值传递给另一个作用域时,会产生有趣的结果。
例如,下面的代码创建两个函数:foo()(返回一个用来计算矩形面积的嵌套函数 rectArea())和 bar()(调用 foo() 并将返回的函数闭包存储在名为 myProduct 的变量中)。即使 bar() 函数定义了自己的局部变量 x(值为 2),当调用函数闭包 myProduct() 时,该函数闭包仍保留在函数 foo() 中定义的变量 x(值为 40)。因此,bar() 函数将返回值 160,而不是 8。
function foo():Function
{
var x:int = 40;
function rectArea(y:int):int // 定义函数闭包
{
return x * y
}
return rectArea;
}
function bar():void
{
var x:int = 2;
var y:int = 4;
var myProduct:Function = foo();
trace(myProduct(4)); // 调用函数闭包
}
bar(); // 160
方法的行为与函数闭包类似,因为方法也保留有关创建它们的词汇环境的信息。当方法提取自它的实例(这会创建绑定方法)时,此特征尤为突出。函数闭包与绑定方法之间的主要区别在于,绑定方法中 this 关键字的值始终引用它最初附加到的实例,而函数闭包中 this 关键字的值可以改变。




ActionScript 中面向对象的编程
面向对象的编程基础知识
面向对象的编程简介
面向对象的编程 (OOP) 是一种组织程序代码的方法,它将代码划分为对象,即包含信息(数据值)和功能的单个元素。通过使用面向对象的方法来组织程序,您可以将特定信息及其关联的通用功能或动作组合在一起。这些项目将合并为一个项目,即对象。能够将这些值和功能捆绑在一起会带来很多好处,其中包括只需跟踪单个变量而非多个变量、将相关功能组织在一起,以及能够以更接近实际情况的方式构建程序。
常见的面向对象编程任务
实际上,面向对象的编程包含两个部分。一部分是程序设计策略和技巧(通常称为"面向对象的设计")。这是一个很广泛的主题,本章中不对其进行讨论。OOP 的另一部分是在给定编程语言中提供的实际编程结构,以便使用面向对象的方法来构建程序。本章介绍了 OOP 中的以下常见任务:
定义类
创建属性、方法以及 get 和 set 存取器(存取器方法)
控制对类、属性、方法和存取器的访问
创建静态属性和方法
创建与枚举类似的结构
定义和使用接口
处理继承(包括覆盖类元素)
重要概念和术语
属性 (Attribute):在类定义中为类元素(如属性或方法)分配的特性。属性通常用于定义程序的其它部分中的代码能否访问属性或方法。例如,private 和 public 都是属性。私有方法只能由类中的代码调用;而公共方法可以由程序中的任何代码调用。
类 (Class):某种类型的对象的结构和行为定义(与该数据类型的对象的模板或蓝图类似)。
类层次结构 (Class hierarchy):多个相关的类的结构,用于指定哪些类继承了其它类中的功能。
构造函数 (Constructor):可以在类中定义的特殊方法,创建类的实例时将调用该方法。构造函数通常用于指定默认值,或以其它方式执行对象的设置操作。
数据类型 (Data type):特定变量可以存储的信息类型。通常,"数据类型"表示与"类"相同的内容。
点运算符 (Dot operator):句点符号 (.),在 ActionScript(和很多其它编程语言)中,它用于指示某个名称引用对象的子元素(如属性或方法)。
枚举 (Enumeration):一组相关常数值,为方便起见而将其作为一个类的属性组合在一起。
继承 (Inheritance):一种 OOP 机制,它允许一个类定义包含另一个类定义的所有功能(通常会添加到该功能中)。
实例 (Instance):在程序中创建的实际对象。
命名空间 (Namespace):实质上是一个自定义属性,它可以更精确地控制代码对其它代码的访问。

类是对象的抽象表示形式。类用来存储有关对象可保存的数据类型及对象可表现的行为的信息。
类定义
正确的类定义语法中要求 class 关键字后跟类名。类体要放在大括号 ({}) 内,且放在类名后面。
在 ActionScript 3.0 中,引入了 package 语句,包名称必须包含在包声明中,而不是包含在类声明中。
类属性
在 ActionScript 3.0 中,可使用以下四个属性之一来修改类定义:
属性
定义
dynamic
允许在运行时向实例添加属性。
final
不得由其它类扩展。
internal(默认)
对当前包内的引用可见。
公共
对所有位置的引用可见。
使用 internal 以外的每个属性时,必须显式包含该属性才能获得相关的行为。例如,如果定义类时未包含 dynamic 属性 (attribute),则不能在运行时向类实例中添加属性 (property)。通过在类定义的开始处放置属性,可显式地分配属性。
请注意,列表中未包含名为 abstract 的属性。这是因为 ActionScript 3.0 不支持抽象类。同时还请注意,列表中未包含名为 private 和 protected 的属性。这些属性只在类定义中有意义,但不可以应用于类本身。
如果不希望某个类在包以外公开可见,请将该类放在包中,并用 internal 属性标记该类。或者,可以省略 internal 和 public 这两个属性,编译器会自动为您添加 internal 属性。如果不希望某个类在定义该类的源文件以外可见,请将类放在包定义右大括号下面的源文件底部。
类体
类体放在大括号内,用于定义类的变量、常量和方法。还可以在类体中定义命名空间。
ActionScript 3.0 不但允许在类体中包括定义,而且还允许包括语句。如果语句在类体中但在方法定义之外,这些语句只在第一次遇到类定义并且创建了相关的类对象时执行一次。
ActionScript 3.0 中允许在同一类体中定义同名的静态属性和实例属性。
类属性 (property) 的属性 (attribute)
讨论 ActionScript 对象模型时,术语"属性"指可以成为类成员的任何成员,包括变量、常量和方法。这与《ActionScript 3.0 语言和组件参考》中该术语的使用方式有所不同,后者中该术语的使用范围更窄,只包括作为变量的类成员或用 getter 或 setter 方法定义的类成员。在 ActionScript 3.0 中,提供了可以与类的任何属性 (property) 一起使用的一组属性 (attribute)。下表列出了这组属性。
属性
定义
internal(默认)
对同一包中的引用可见。
private
对同一类中的引用可见。
protected
对同一类及派生类中的引用可见。
public
对所有位置的引用可见。
static
指定某一属性属于该类,而不属于该类的实例。
UserDefinedNamespace
用户定义的自定义命名空间名。
访问控制命名空间属性
ActionScript 3.0 提供了四个特殊的属性 (attribute) 来控制对在类中定义的属性 (property) 的访问:public、private、protected 和 internal。
使用 public 属性 (attribute) 可使某一属性 (property) 在脚本的任何位置可见。例如,要使某个方法可用于包外部的代码,必须使用 public 属性声明该方法。这适用于任何属性,不管属性是使用 var、const 还是 function 关键字声明的。
使用 private 属性 (attribute) 可使某一属性 (property) 只对属性 (property) 的定义类中的调用方可见。
在 ActionScript 3.0 中使用严格模式时,尝试使用点运算符访问私有属性会导致编译时错误。否则,会在运行时报告错误,就像使用属性访问运算符时一样。
下表汇总了试图访问属于密封(非动态)类的 private 属性的结果:

严格模式
标准模式
点运算符 (.)
编译时错误
运行时错误
中括号运算符 ([])
运行时错误
运行时错误
在使用 dynamic 属性声明的类中尝试访问私有变量时,不会导致运行时错误。只是变量不可见,所以 Flash Player 返回值 undefined。但是,如果在严格模式下使用点运算符,则会发生编译时错误。
当类外部的代码尝试访问 private 属性时,动态类通常会返回值 undefined,而不是生成错误。
下表说明了只有在严格模式下使用点运算符访问 private 属性时才会生成错误:

严格模式
标准模式
点运算符 (.)
编译时错误
undefined
中括号运算符 ([])
undefined
undefined
protected 属性 (attribute) 是 ActionScript 3.0 中的新增属性 (attribute),可使属性 (property) 对所属类或子类中的调用方可见。换句话说,protected 属性在所属类中可用,或者对继承层次结构中该类下面的类可用。无论子类在同一包中还是在不同包中,这一点都适用。
internal 属性 (attribute) 是 ActionScript 3.0 的新增属性 (attribute),可使属性 (property) 对所在包中的调用方可见。该属性是包中代码的默认属性 (attribute),它适用于没有以下任意属性 (attribute) 的任何属性 (property):
public
private
protected
用户定义的命名空间
static 属性
static 属性 (attribute) 可以与用 var、const 或 function 关键字声明的那些属性 (property) 一起使用,使用该属性 (attribute) 可将属性 (property) 附加到类而不是类的实例。类外部的代码必须使用类名(而不是使用实例名)调用静态属性 (property)。
静态属性不由子类继承的,但是这些属性是子类作用域链中的一部分。这意味着在子类体中,不必引用在其中定义静态变量或方法的类,就可以使用静态变量或方法。
用户定义的命名空间属性
作为预定义访问控制属性的替代方法,您可以创建自定义命名空间以用作属性。每个定义只能使用一个命名空间属性,而且不能将命名空间属性与任何访问控制属性(public、private、protected 和 internal)组合使用。
变量
静态变量
可以使用 var 或 const 关键字声明变量。在脚本的整个执行过程中,使用 var 关键字声明的变量可多次更改其变量值。使用 const 关键字声明的变量称为"常量",只能赋值一次。尝试给已初始化的常量分配新值,将生成错误。
静态变量是使用 static 关键字和 var 或 const 语句共同声明的。静态变量附加到类而不是类的实例,对于存储和共享应用于对象的整个类的信息非常有用。
必须在声明常量的同时初始化使用 static 和 const 关键字声明的变量,您不能为构造函数或实例方法中的静态变量赋值。
实例变量
实例变量包括使用 var 和 const 关键字但未使用 static 关键字声明的属性。实例变量附加到类实例而不是整个类,对于存储特定于实例的值很有用。例如,Array 类有一个名为 length 的实例属性,用来存储 Array 类的特定实例保存的数组元素的个数。
不能覆盖子类中声明为 var 或 const 的实例变量。但是,通过覆盖 getter 和 setter 方法,可以实现类似于覆盖变量的功能。
方法
方法是类定义中的函数。创建类的一个实例后,该实例就会捆绑一个方法。与在类外部声明的函数不同,不能将方法与附加方法的实例分开使用。
方法是使用 function 关键字定义的,或者,也可以使用分配了函数表达式的变量。
多数情况下,您需要使用函数语句而不是函数表达式,原因如下:
函数语句更为简洁易读。
函数语句允许使用 override 和 final 关键字。
函数语句在标识符(即函数名)与方法体代码之间创建了更强的绑定。由于可以使用赋值语句更改
变量值,可随时断开变量与其函数表达式之间的连接。虽然可通过使用 const(不是 var)声明变量来解决这个问题,但这种方法并不是最好的做法,因为这会使代码难以阅读,还会禁止使用 override 和 final 关键字。
必须使用函数表达式的一种情况是:选择将函数附加到原型对象时。

构造函数方法
构造函数方法有时简单称为"构造函数",是与在其中定义函数的类共享同一名称的函数。只要使用 new 关键字创建了类实例,就会执行构造函数方法中包括的所有代码。
构造函数方法只能是公共方法,但可以选择性地使用 public 属性。不能对构造函数使用任何其它访问控制说明符(包括使用 private、protected 或 internal)。也不能对函数构造方法使用用户定义的命名空间。
构造函数可以使用 super() 语句显式地调用其直接超类的构造函数。如果未显式调用超类构造函数,编译器会在构造函数体中的第一个语句前自动插入一个调用。还可以使用 super 前缀作为对超类的引用来调用超类的方法。如果决定在同一构造函数中使用 super() 和 super,务必先调用 super()。否则,super 引用的行为将会与预期不符。另外,super() 构造函数也应在 throw 或 return 语句之前调用。
虽然在构造函数中使用 return 语句是合法的,但是不允许返回值。换句话说,return 语句不得有相关的表达式或值。因此,不允许构造函数方法返回值,这意味着不可以指定任何返回值。
如果没有在类中定义构造函数方法,编译器将会为您自动创建一个空构造函数。如果某个类扩展了另一个类,编译器将会在所生成的构造函数中包括 super() 调用。
静态方法
静态方法也叫做"类方法",它们是使用 static 关键字声明的方法。静态方法附加到类而不是类的实例,因此在封装对单个实例的状态以外的内容有影响的功能时,静态方法很有用。由于静态方法附加到整个类,所以只能通过类访问静态方法,而不能通过类实例访问。
静态方法为封装所提供的功能不仅仅在影响类实例状态的方面。换句话说,如果方法提供的功能对类实例的值没有直接的影响,该方法应是静态方法。
由于静态方法不绑定到单个实例,因此不能在静态方法体中使用关键字 this 或 super。this 和 super 这两个引用只在实例方法上下文中有意义。
与其它基于类的编程语言不同,ActionScript 3.0 中的静态方法不是继承的。
实例方法
实例方法指的是不使用 static 关键字声明的方法。实例方法附加到类实例而不是整个类,在实现对类的各个实例有影响的功能时,实例方法很有用。
在实例方法体中,静态变量和实例变量都在作用域中,这表示使用一个简单的标识符可以引用同一类中定义的变量。虽然在 ActionScript 3.0 中不继承静态属性,但是超类的静态属性在作用域中。
实例方法体中的 this 引用的值是对方法所附加实例的引用。
使用关键字 override 和 final 可以控制实例方法的继承。可以使用 override 属性重新定义继承的方法,以及使用 final 属性禁止子类覆盖方法。
get 和 set 存取器方法
get 和 set 存取器函数还分别称为 getter 和 setter,可以使用这些函数为创建的类提供易于使用的编程接口,并遵循信息隐藏和封装的编程原则。使用 get 和 set 函数可保持类的私有类属性,但允许类用户访问这些属性,就像他们在访问类变量而不是调用类方法。
这种方法的好处是,可避免出现具有不实用名称的传统存取器函数,如 getPropertyName() 和 setPropertyName()。getter 和 setter 的另一个好处是,使用它们可避免允许进行读写访问的每个属性有两个面向公共的函数。
使用 getter 和 setter 函数还可以覆盖从超类继承来的属性,这是使用常规类成员变量时不能做到的。在子类中不能覆盖使用 var 关键字声明的类成员变量。但是,使用 getter 和 setter 函数创建的属性没有此限制。可以对从超类继承的 getter 和 setter 函数使用 override 属性。
绑定方法
绑定方法有时也叫做"闭包方法",就是从它的实例提取的方法。作为参数传递给函数的方法或作为值从函数返回的方法都是绑定方法。在 ActionScript 3.0 中,新增的绑定方法类似于闭包函数,其中保留了词汇环境,即使从其实例中提取出来也是如此。绑定方法与闭包函数之间的主要不同差别是,绑定函数的 this 引用保留到实现方法的实例的链接或绑定。换句话说,绑定方法中的 this 引用总是指向实现方法的原始对象。对于闭包函数,this 引用是通用的,这意味着调用函数时,该引用指向与函数关联的任何对象。
如果使用 this 关键字,了解绑定方法就很重要。重新调用 this 关键字可提供对方法父对象的引用。大多数 ActionScript 程序员都希望 this 关键字总是引用包含方法定义的对象或类。但是,如果不使用方法绑定,并不是总是做到这样。例如,在以前版本的 ActionScript 中,this 引用并不总是引用实现方法的实例。从 ActionScript 2.0 的实例中提取方法后,不但 this 引用不绑定到原始实例,而且实例类的成员变量和方法也不可用。在 ActionScript 3.0 中不存在这样的问题,这是因为将方法当作参数传递时会自动创建绑定方法。绑定方法用于确保 this 关键字总是引用在其中定义了方法的对象或类。
下面的代码定义了名为 ThisTest 的类,该类包含一个名为 foo() 的方法(该方法定义绑定方法)和一个名为 bar() 的方法(该方法返回绑定方法)。类外部的代码创建 ThisTest 类的实例,然后调用 bar() 方法,最后将返回值存储在名为 myFunc 的变量中。
class ThisTest
{
private var num:Number = 3;
function foo():void // 定义的绑定方法
{
trace("foo's this: " + this);
trace("num: " + num);
}
function bar():Function
{
return foo; // 返回的绑定方法
}
}

var myTest:ThisTest = new ThisTest();
var myFunc:Function = myTest.bar();
trace(this); // 输出:[全局对象]
myFunc();
/* 输出:
foo's this: [object ThisTest]
output: num: 3 */
代码的最后两行表明:虽然前一行中的 this 引用指向全局对象,但绑定方法 foo() 中的 this 引用仍然指向 ThisTest 类的实例。另外,存储在 myFunc 变量中的绑定方法仍然可以访问 ThisTest 类的成员变量。如果以上代码在 ActionScript 2.0 中运行,this 引用会匹配,但 num 变量将为 undefined。
绑定方法最值得注意的一种情况是使用事件处理函数,因为 addEventListener() 方法要求将函数或方法作为参数来传递。
类的枚举
枚举"是您创建的一些自定义数据类型,用于封装一小组值。ActionScript 3.0 并不支持具体的枚举工具,不过,您可以使用类或静态常量创建枚举。按照惯例,枚举类是使用 final 属性声明的,因为不需要扩展该类。该类仅由静态成员组成,这表示不创建该类的实例。而是直接通过类对象来访问枚举值。
Flash Player API 中的所有枚举类都只包含 String、int 或 uint 类型的变量。使用枚举而不使用文本字符串或数字值的好处是,使用枚举更易于发现字面错误。如果枚举名输入错误,ActionScript 编译器会生成一个错误。如果使用字面值,存在拼写错误或使用了错误数字时,编译器并不会报错。
创建枚举的第二种方法还包括使用枚举的静态属性创建单独的类。这种方法的不同之处在于每一个静态属性都包含一个类实例,而不是字符串或整数值。
Flash Player API 并不使用这种方法,但是许多开发人员都使用,他们更喜欢使用这种方法提供的改进类型检查功能。
嵌入资源类
ActionScript 3.0 使用称为"嵌入资源类"的特殊类来表示嵌入的资源。"嵌入资源"指的编译时包括在 SWF 文件中的资源,如声音、图像或字体。嵌入资源而不是动态加载资源,可以确保资源在运行时可用,但代价是增加了 SWF 文件的大小。
在 Flash 中使用嵌入资源类
要嵌入资源,首先将该资源放入 FLA 文件的库中。接着,使用资源的链接属性,提供资源的嵌入资源类的名称。如果无法在类路径中找到具有该名称的类,则将自动生成一个类。然后,可以创建嵌入资源类的实例,并使用任何由该类定义或继承的属性和方法。
接口的基础是方法的接口与方法的实现之间的区别。方法的接口包括调用该方法必需的所有信息,包括方法名、所有参数和返回类型。方法的实现不仅包括接口信息,而且还包括执行方法的行为的可执行语句。接口定义只包含方法接口,实现接口的所有类负责定义方法实现。
另一种描述接口的方法是:接口定义了数据类型,就像类一样。因此,接口可以用作类型注释,也像类一样。作为数据类型,接口还可以与需要指定数据类型的运算符一起使用,如 is 和 as 运算符。但是与类不同的是,接口不可以实例化。这个区别使很多程序员认为接口是抽象的数据类型,认为类是具体的数据类型。
定义接口
接口定义的结构类似于类定义的结构,只是接口只能包含方法但不能包含方法体。接口不能包含变量或常量,但是可以包含 getter 和 setter。要定义接口,请使用 interface 关键字。
只能使用 public 和 internal 访问控制说明符来修饰接口定义。接口定义中的方法声明不能包含任何访问控制说明符。
Flash Player API 遵循一种约定,其中接口名以大写 I 开始,但是可以使用任何合法的标识符作为接口名。接口定义经常位于包的顶级。接口定义不能放在类定义或另一个接口定义中。
接口可扩展一个或多个其它接口。
在类中实现接口
类是唯一可实现接口的 ActionScript 3.0 语言元素。在类声明中使用 implements 关键字可实现一个或多个接口。
在实现接口的类中,实现的方法必须:
使用 public 访问控制标识符。
使用与接口方法相同的名称。
拥有相同数量的参数,每一个参数的数据类型都要与接口方法参数的数据类型相匹配。
使用相同的返回类型。
不过在命名所实现方法的参数时,您有一定的灵活性。虽然实现的方法的参数数和每个参数的数据类型必须与接口方法的参数数和数据类型相匹配,但参数名不需要匹配。
另外,使用默认参数值也具有一定的灵活性。接口定义可以包含使用默认参数值的函数声明。实现这种函数声明的方法必须采用默认参数值,默认参数值是与接口定义中指定的值具有相同数据类型的一个成员,但是实际值不一定匹配。
提供这种灵活性的原因是,实现接口的规则的设计目的是确保数据类型兼容性,因此不必要求采用相同的参数名和默认参数名,就能实现目标。
继承
继承是指一种代码重用的形式,允许程序员基于现有类开发新类。现有类通常称为"基类"或"超类",新类通常称为"子类"。继承的主要优势是,允许重复使用基类中的代码,但不修改现有代码。此外,继承不要求改变其它类与基类交互的方式。不必修改可能已经过彻底测试或可能已被使用的现有类,使用继承可将该类视为一个集成模块,可使用其它属性或方法对它进行扩展。因此,您使用 extends 关键字指明类从另一类继承。
通过继承还可以在代码中利用"多态"。有一种方法在应用于不同数据类型时会有不同行为,多态就是对这样的方法应用一个方法名的能力。使用继承能实现多态,实现的方式是允许子类继承和重新定义或"覆盖"基类中的方法。
因为每个类定义一个数据类型,所以使用继承会在基类和扩展基类的类之间创建一个特殊关系。子类保证拥有其基类的所有属性,这意味着子类的实例总是可以替换基类的实例。
实例属性和继承
对于实例属性 (property),无论是使用 function、var 还是使用 const 关键字定义的,只要在基类中未使用 private 属性 (attribute) 声明该属性 (property),这些属性都可以由子类继承。
访问控制说明符和继承
如果某一属性是用 public 关键字声明的,则该属性对任何位置的代码可见。这表示 public 关键字与 private、protected 和 internal 关键字不同,它对属性继承没有任何限制。
如果属性是使用 private 关键字声明的,该属性只在定义该属性的类中可见,这表示它不能由任何子类继承。
protected 关键字指出某一属性不仅在定义该属性的类中可见,而且还在所有子类中可见。ActionScript 3.0 中的 protected 关键字并不使属性对同一包中的所有其它类可见。此外,protected 属性对子类可见,不管子类和基类是在同一包中,还是在不同包中。
要限制某一属性在定义该属性的包中的可见性,请使用 internal 关键字或者不使用任何访问控制说明符。未指定访问控制说明符时,应用的默认访问控制说明符是 internal 访问控制说明符。标记为 internal 的属性将只由位于在同一包中的子类继承。
不允许覆盖变量
将继承使用 var 或 const 关键字声明的属性,但不能对其进行覆盖。覆盖某一属性就表示在子类中重新定义该属性。唯一可覆盖的属性类型是方法,即使用 function 关键字声明的属性。虽然不能覆盖实例变量,但是通过为实例变量创建 getter 和 setter 方法并覆盖这些方法,可实现类似的功能。
覆盖方法
覆盖方法表示重新定义已继承方法的行为。静态方法不能继承,也不能覆盖。但是,实例方法可由子类继承,也可覆盖,只要符合以下两个条件:
实例方法在基类中不是使用 final 关键字声明的。当 final 关键字与实例方法一起使用时,该关键字指明程序员的设计目的是要禁止子类覆盖方法。
实例方法在基类中不是使用 private 访问控制说明符声明的。如果某个方法在基类中标记为 private,则在子类中定义同名方法时不需要使用 override 关键字,因为基类方法在子类中不可见。
要覆盖符合这些条件的实例方法,子类中的方法定义必须使用 override 关键字,且必须在以下几个方面与方法的超类版本相匹配:
覆盖方法必须与基类方法具有相同级别的访问控制。标记为内部的方法与没有访问控制说明符的方法具有相同级别的访问控制。
覆盖方法必须与基类方法具有相同的参数数。
覆盖方法参数必须与基类方法参数具有相同的数据类型注释。
覆盖方法必须与基类方法具有相同的返回类型。
但是,覆盖方法中的参数名不必与基类中的参数名相匹配,只要参数数和每个参数的数据类相匹配即可。
super 语句
覆盖方法时,程序员经常希望在要覆盖的超类方法的行为上添加行为,而不是完全替换该行为。这需要通过某种机制来允许子类中的方法调用它本身的超类版本。super 语句就提供了这样一种机制,其中包含对直接超类的引用。
覆盖 getter 和 setter
虽然不能覆盖超类中定义的变量,但是可以覆盖 getter 和 setter。
不继承静态属性
静态属性不由子类继承。这意味着不能通过子类的实例访问静态属性。只能通过定义静态属性的类对象(即 类本身而非实例)来访问静态属性。
但允许使用与静态属性相同的名称定义实例属性。可以在与静态属性相同的类中或在子类中定义这样的实例属性。
静态属性和作用域链
虽然并不继承静态属性,但是静态属性在定义它们的类或该类的任何子类的作用域链中。同样,可以认为静态属性在定义它们的类和任何子类的"作用域"中。这意味着在定义静态属性的类体及该类的任何子类中可直接访问静态属性。
如果使用与同类或超类中的静态属性相同的名称定义实例属性,则实例属性在作用域链中的优先级比较高。因此认为实例属性"遮蔽"了静态属性,这意味着会使用实例属性的值,而不使用静态属性的值。


高级主题
本节开始先简单介绍 ActionScript 和 OOP 的历史,然后讨论 ActionScript 3.0 对象模型,以及该模型如何启用新的 ActionScript 虚拟机 (AVM2) 显著提供运行速度(与包含旧 ActionScript 虚拟机 (AVM1) 的以前版本的 Flash Player 相比)。
ActionScript OOP 支持的历史
由于 ActionScript 3.0 是在以前版本的 ActionScript 基础上构建的,了解 ActionScript 对象模型的发展过程可能有所帮助。ActionScript 最初作为早期版本的 Flash 创作工具的简单编写脚本机制。后来,程序员开始使用 ActionScript 建立更加复杂的应用程序。为了迎合这些程序员的需要,每个后续版本都添加了一些语言功能以帮助创建复杂的应用程序。
ActionScript 1.0
ActionScript 1.0 指在 Flash Player 6 和更早版本中使用的语言版本。即使在这个早期开发阶段,ActionScript 对象模型也是建立在基础数据类型对象的概念的基础上。ActionScript 对象是由一组"属性"构成的复合数据类型。讨论对象模型时,术语"属性"包括附加到对象的所有内容,如变量、函数或方法。
尽管第一代 ActionScript 不支持使用 class 关键字定义类,但是可以使用称为原型对象的特殊对象来定义类。Java 和 C++ 等基于类的语言中使用 class 关键字创建要实例化为具体对象的抽象类定义,而 ActionScript 1.0 等基于原型的语言则将现有对象用作其它对象的模型(或原型)。基于类的语言中的对象可能指向作为其模板的类,而基于原型的语言中的对象则指向作为其模板的另一个对象(即其原型)。
要在 ActionScript 1.0 中创建类,可以为该类定义一个构造函数。在 ActionScript 中,函数不只是抽象定义,还是实际对象。您创建的构造函数用作该类实例的原型对象。以下代码创建了一个名为 Shape 的类,还定义了一个名为 visible 的属性,该属性默认情况下设置为 true:
// 基类
function Shape() {}
// 创建名为 visible 的属性。
Shape.prototype.visible = true;
此构造函数定义了可以使用 new 运算符实例化的 Shape 类,如下所示:
myShape = new Shape();
就像 Shape() 构造函数对象用作 Shape 类实例的原型一样,它还可以用作 Shape 的子类(即扩展 Shape 类的其它类)的原型。
创建作为 Shape 类的子类的类的过程分两个步骤。首先,通过定义类的构造函数创建该类,如下所示:
// 子类
function Circle(id, radius)
{
this.id = id;
this.radius = radius;
}
然后,使用 new 运算符将 Shape 类声明为 Circle 类的原型。默认情况下,创建的所有类都使用 Object 类作为其原型,这意味着 Circle.prototype 当前包含一个通用对象(Object 类的实例)。要指定 Circle 的原型是 Shape 而不是 Object,请使用以下代码更改 Circle.prototype 的值,使其包含 Shape 对象而不是通用对象。
// 使 Circle 成为 Shape 的子类。
Circle.prototype = new Shape();
Shape 类和 Circle 类现在通过通常所说的"原型链"的继承关系联系在一起。下图说明了原型链中的关系:

每个原型链末端的基类是 Object 类。Object 类包含一个名为 Object.prototype 的静态属性,该属性指向在 ActionScript 1.0 中创建的所有对象的基础原型对象。示例原型链中的下一个对象是 Shape 对象。这是因为从不显式设置 Shape.prototype 属性,所以它仍然包含通用对象(Object 类的实例)。此链中的最后一个链环是 Circle 类,该类链接到其原型 Shape 类(Circle.prototype 属性包含 Shape 对象)。
如果创建了 Circle 类的实例(如下面的示例所示),该实例会继承 Circle 类的原型链:
// 创建 Circle 类的实例。
myCircle = new Circle();
回想一下,我们创建了一个名为 visible 的属性作为 Shape 类的成员。在示例中,visible 属性并不作为 myCircle 对象的一部分,只是 Shape 对象的一个成员,但以下代码行的输出为 true:
trace(myCircle.visible); // 输出:true
Flash Player 能够沿着原型链检查 myCircle 对象是否继承了 visible 属性。执行此代码时,Flash Player 首先在 myCircle 对象的属性中搜索名为 visible 的属性,但是未发现这样的属性。Flash Player 然后在下一个 Circle.prototype 对象中查找,但是仍未发现名为 visible 的属性。继续检查原型链,Flash Player 最终发现了在 Shape.prototype 对象上定义的 visible 属性,并输出该属性的值。
为了简便,本节省略了原型链的很多难懂之处和细节,目的是为了提供足够的信息帮助您了解 ActionScript 3.0 对象模型。
ActionScript 2.0
在 ActionScript 2.0 中引入了 class、extends、public 和 private 等新关键字,通过使用这些关键字,您可以按 Java 和 C++ 等基于类的语言用户所熟悉的方式来定义类。ActionScript 1.0 与 ActionScript 2.0 之间的基本继承机制并没有改变,了解这一点很重要。ActionScript 2.0 中只是添加了用于定义类的新语法。在该语言的两个版本中,原型链的作用方式是一样的。
ActionScript 2.0 中引入了新语法(如以下摘录中所示),可允许以数程序员认为更直观的方式定义类:
// 基类
class Shape
{
var visible:Boolean = true;
}
注意,ActionScript 2.0 还引入了用于编译时类型检查的类型注释。使用类型注释,可以将上个示例中的 visible 属性声明为应只包含布尔值。新 extends 关键字还简化了创建子类的过程。在下面的示例中,通过使用 extends 关键字,可以一步完成在 ActionScript 1.0 中需要分两步完成的过程:
// 子类
class Circle extends Shape
{
var id:Number;
var radius:Number;
function Circle(id, radius)
{
this.id = id;
this.radius = radius;
}
}
构造函数现在声明为类定义的一部分,还必须显式声明类属性 id 和 radius。
ActionScript 2.0 中还增加了对接口定义的支持,以使您能够使用为对象间通信正式定义的协议来进一步改进面向对象的程序。
ActionScript 3.0 类对象
常见的面向对象的编程范例多数常与 Java 和 C++ 相关联,这种范例使用类定义对象的类型。采用这种范例的编程语言也趋向使用类来构造类定义的数据类型的实例。ActionScript 使用类是为了实现以上两个目的,但其根本还是一种基于原型的语言,并带有有趣的特征。ActionScript 为每一类定义创建了特殊的类对象,允许共享行为和状态。但是,对多数 ActionScript 程序员而言,这个特点可能与实际编码没有什么牵连。ActionScript 3.0 的设计目的是,不使用(甚至是不必了解)这些特殊类对象,就可创建复杂的面向对象的 ActionScript 应用程序。对于要利用类对象的高级程序员,本节提供有关问题的深入讨论。
下图显示一个类对象的结构,该类对象表示使用语句 class A {} 定义的名为 A 的简单类:

图中的每个矩形表示一个对象。图中的每一个对象都有下标字符 A,这表示该对象属于类 A。类对象 (CA) 包含对许多其它重要对象的引用。实例 traits 对象 (TA) 用于存储在类定义中定义的实例属性。类 traits 对象 (TCA) 表示类的内部类型,用于存储该类定义的静态属性(下标字符 C 代表"类")。原型对象 (PA) 始终指的是最初通过 constructor 属性附加到的类对象。
traits 对象
traits 对象是 ActionScript 3.0 中的新增对象,它是为了提高性能而实现的。在以前版本的 ActionScript 中,名称查找是一个耗时的过程,因为 Flash Player 要搜索原型链。在 ActionScript 3.0 中,名称查找更有效、耗时更少,因为可以将继承属性从超类复制到子类的 traits 对象。
编程代码不能直接访问 traits 对象,但是性能和内存使用情况的改善可反映它的存在。traits 对象给 AVM2 提供了关于类的布局和内容的详细信息。借助这些信息,AVM2 可显著减少执行时间,因为它可以经常生成直接机器指令来直接访问属性或直接调用方法,而省去了查找名称所耗费的时间。
由于使用了 traits 对象,与以前版本中 ActionScript 类似对象相比,该版本中对象占用内存的时间明显减少。例如,如果某个类已密封(即,该类未声明为 dynamic),则该类实例不需要动态添加属性的哈希表,只保留一个到 traits 对象的指针和该类中定义的固定属性的某些位置。因此,如果对象在 ActionScript 2.0 中需要占用 100 个字节的内存,在 ActionScript 3.0 中只需要占用 20 个字节的内存。
traits 对象是内部实现详细信息,不保证在将来版本的 ActionScript 中此对象不更改,甚至消失。
原型对象
每个 ActionScript 类对象都有一个名为 prototype 属性,它表示对类的原型对象的引用。 ActionScript 根本上是基于原型的语言,原型对象是旧内容。
prototype 属性是只读属性,这表示不能将其修改为指向其它对象。这不同于以前版本 ActionScript 中的类 prototype 属性,在以前版本中可以重新分配 prototype,使它指向其它类。虽然 prototype 属性是只读属性,但是它所引用的原型对象不是只读的。换句话说,可以向原型对象添加新属性。向原型对象添加的属性可在类的所有实例中共享。
原型链是以前版本的 ActionScript 中的唯一继承机制,在 ActionScript 3.0 中只充当一个辅助角色。主要的继承机制固定属性继承由 traits 对象内部处理。固定属性指的是定义为类定义的一部分的变量或方法。固定属性继承也叫做类继承,因为它是与 class、extends 和 override 等关键字相关的继承机制。
原型链提供了另一种继承机制,该机制的动态性比固定属性继承的更强。既可以将属性作为类定义的一部分,也可以在运行时通过类对象的 prototype 属性向类的原型对象中添加属性。但是,请注意,如果将编译器设置为严格模式,则不能访问添加到原型对象中的属性,除非使用 dynamic 关键字声明类。
Object 类就是这样类的示例,它的原型对象附加了若干属性。Object 类的 toString() 和 valueOf() 方法实际上是一些函数,它们分配给 Object 类原型对象的属性。以下是一个示例,说明这些方法的声明理论上是怎样的(实际实现时会因实现详细信息而稍有不同):
public dynamic class Object
{
prototype.toString = function()
{
// 语句
};
prototype.valueOf = function()
{
// 语句
};
}
正如前面提到的那样,可以将属性附加到类定义外部的类原型对象。例如,也可以在 Object 类定义外部定义 toString() 方法,如下所示:
Object.prototype.toString = function()
{
// 语句
};
但是,原型继承与固定属性继承不一样,如果要重新定义子类中的方法,原型继承不需要 override 关键字。例如,如果要重新定义 Object 类的子类中的 valueOf() 方法,您有以下三种选择。第一,可以在类定义中的子类原型对象上定义 valueOf() 方法。以下代码创建一个名为 Foo 的 Object 子类,还将 Foo 原型对象的 valueOf() 方法重新定义为类定义的一部分。因为每个类都是从 Object 继承的,所以不需要使用 extends 关键字。
dynamic class Foo
{
prototype.valueOf = function()
{
return "Instance of Foo";
};
}
第二,可以在类定义外部对 Foo 原型对象定义 valueOf() 方法,如以下代码中所示:
Foo.prototype.valueOf = function()
{
return "Instance of Foo";
};
第三,可以将名为 valueOf() 的固定属性定义为 Foo 类的一部分。这种方法与其它混合了固定属性继承与原型继承的方法有所不同。要重新定义 valueOf() 的 Foo 的任何子类必须使用 override 关键字。以下代码显示 valueOf() 定义为 Foo 中的固定属性:
class Foo
{
function valueOf():String
{
return "Instance of Foo";
}
}
AS3 命名空间
由于存在两种继承机制,即固定属性继承和原型继承,所以涉及到核心类的属性和方法时,就存在两种机制的兼容性问题。如果与 ECMAScript 第 4 版语言规范草案兼容,则要求使用原型继承,这意味着核心类的属性和方法是在该类的原型对象上定义的。另一方面,如果与 Flash Player API 兼容,则要求使用固定属性继承,这意味着核心类的属性和方法是使用 const、var 和 function 关键字在类定义中定义的。此外,如果使用固定属性而不是原型属性,将显著提升运行时性能。
在 ActionScript 3.0 中,通过同时将原型继承和固定属性继承用于核心类,解决了这个问题。每一个核心类都包含两组属性和方法。一组是在原型对象上定义的,用于与 ECMAScript 规范兼容,另一组使用固定属性定义和 AS3 命名空间定义,以便与 Flash Player API 兼容。
AS3 命名空间提供了一种约定机制,用来在两组属性和方法之间做出选择。如果不使用 AS3 命名空间,核心类的实例会继承在核心类的原型对象上定义的属性和方法。如果决定使用 AS3 命名空间,核心类的实例会继承 AS3 版本,因为固定属性的优先级始终高于原型属性。换句话说,只要固定属性可用,则始终使用固定属性,而不使用同名的原型属性。
通过用 AS3 命名空间限定属性或方法,可以选择使用 AS3 命名空间版本的属性或方法。例如,下面的代码使用 AS3 版本的 Array.pop() 方法:
var nums:Array = new Array(1, 2, 3);
nums.AS3::pop();
trace(nums); // 输出:1,2
或者,也可以使用 use namespace 指令打开代码块中所有定义的 AS3 命名空间。例如,以下代码使用 use namespace 指令打开 pop() 和 push() 方法的 AS3 命名空间:
use namespace AS3;

var nums:Array = new Array(1, 2, 3);
nums.pop();
nums.push(5);
trace(nums) // 输出:1,2,5
ActionScript 3.0 还为每组属性提供了编译器选项,以便将 AS3 命名空间应用于整个程序。-as3 编译器选项表示 AS3 命名空间,-es 编译器选项表示原型继承选项(es 代表 ECMAScript)。要打开整个程序的 AS3 命名空间,请将 -as3 编译器选项设置为 true,将 -es 编译器选项设置为 false。要使用原型版本,请将编译器选项设置为相反值。Adobe Flex Builder 2 和 Adobe Flash CS3 Professional 的默认编译器选项是 -as3 = true 和 -es = false。
如果计划扩展任何核心类并覆盖任何方法,应了解 AS3 命名空间对声明覆盖方法的方式有什么影响。如果要使用 AS3 命名空间,覆盖核心类方法的任何方法都必须使用 AS3 命名空间以及 override 属性。如果不打算使用 AS3 命名空间且要重新定义子类中的核心类方法,则不应使用 AS3 命名空间或 override 关键字。
处理日期和时间
ActionScript 3.0 提供了多种强大的手段来管理日历日期、时间和时间间隔。以下两个主类提供了大部分的计时功能:Date 类和 flash.utils 包中的新 Timer 类。
日期和时间基础知识
处理日期和时间简介
在 ActionScript 中,可以使用 Date 类来表示某一时刻,其中包含日期和时间信息。Date 实例中包含各个日期和时间单位的值,其中包括年、月、日、星期、小时、分钟、秒、毫秒以及时区。对于更高级的用法,ActionScript 还包括 Timer 类,您可以使用该类在一定延迟后执行动作,或按重复间隔执行动作。
重要概念和术语
UTC 时间 (UTC time):通用协调时间,即"零小时"基准时区。所有其它时区均被定义为相对于 UTC 时间快
或慢一定的小时数。
管理日历日期和时间
创建 Date 对象
Date 类是所有核心类中构造函数方法形式最为多变的类之一。您可以用以下四种方式来调用 Date 类。
第一,如果未给定参数,则 Date() 构造函数将按照您所在时区的本地时间返回包含当前日期和时间的 Date 对象。
第二,如果仅给定了一个数字参数,则 Date() 构造函数将其视为自 1970 年 1 月 1 日以来经过的毫秒数,并且返回对应的 Date 对象。请注意,您传入的毫秒值将被视为自 1970 年 1 月 1 日(UTC 时间)以来经过的毫秒数。但是,该 Date 对象会按照您所在的本地时区来显示值,除非您使用特定于 UTC 的方法来检索和显示这些值。如果仅使用一个毫秒参数来创建新的 Date 对象,则应确保考虑到您的当地时间和 UTC 之间的时区差异。
第三,您可以将多个数值参数传递给 Date() 构造函数。该构造函数将这些参数分别视为年、月、日、小时、分钟、秒和毫秒,并将返回一个对应的 Date 对象。
第四,您可以将单个字符串参数传递给 Date() 构造函数。该构造函数将尝试把字符串解析为日期或时间部分,然后返回对应的 Date 对象。如果您使用此方法,最好将 Date() 构造函数包含在 try..catch 块中以捕获任何解析错误。
如果 Date() 构造函数无法成功解析该字符串参数,它将不会引发异常。但是,所得到的 Date 对象将包含一个无效的日期值。
获取时间单位值
可以使用 Date 类的属性或方法从 Date 对象中提取各种时间单位的值。下面的每个属性为您提供了 Date 对象中的一个时间单位的值:
fullYear 属性
month 属性,以数字格式表示,分别以 0 到 11 表示一月到十二月
date 属性,表示月中某一天的日历数字,范围从 1 到 31
day 属性,以数字格式表示一周中的某一天,其中 0 表示星期日
hours 属性,范围从 0 到 23
minutes 属性
seconds 属性
milliseconds 属性
实际上,Date 类为您提供了获取这些值的多种方式。
执行日期和时间运算
您可以使用 Date 类对日期和时间执行加法和减法运算。日期值在内部以毫秒的形式保存,因此您应将其它值转换成毫秒,然后再将它们与 Date 对象进行加减。
如果应用程序将执行大量的日期和时间运算,您可能会发现创建常量来保存常见时间单位值(以毫秒的形式)非常有用,如下所示:
public static const millisecondsPerMinute:int = 1000 * 60;
public static const millisecondsPerHour:int = 1000 * 60 * 60;
public static const millisecondsPerDay:int = 1000 * 60 * 60 * 24;
设置日期值的另一种方式是仅使用一个毫秒参数创建新的 Date 对象。
在时区之间进行转换
在需要将日期从一种时区转换成另一种时区时,使用日期和时间运算十分方便。也可以使用 getTimezoneOffset() 方法,该方法返回的值表示 Date 对象的时区与 UTC 之间相差的分钟数。此方法之所以返回以分钟为单位的值是因为并不是所有时区之间都正好相差一个小时,有些时区与邻近的时区仅相差半个小时。
控制时间间隔
循环与计时器之比较
在某些编程语言中,您必须使用循环语句(如 for 或 do..while)来设计您自己的计时方案。
通常,循环语句会以本地计算机所允许的速度尽可能快地执行,这表明应用程序在某些计算机上的运行速度较快而在其它计算机上则较慢。如果应用程序需要一致的计时间隔,则您需要将其与实际的日历或时钟时间联系在一起。许多应用程序(如游戏、动画和实时控制器)需要在不同计算机上均能保持一致的、规则的时间驱动计时机制。
ActionScript 3.0 的 Timer 类提供了一个功能强大的解决方案。使用 ActionScript 3.0 事件模型,Timer 类在每次达到指定的时间间隔时都会调度计时器事件。
Timer 类
在 ActionScript 3.0 中处理计时函数的首选方式是使用 Timer 类 (flash.utils.Timer),可以使用它在每次达到间隔时调度事件。
要启动计时器,请先创建 Timer 类的实例,并告诉它每隔多长时间生成一次计时器事件以及在停止前生成多少次事件。
例如,下列代码创建一个每秒调度一个事件且持续 60 秒的 Timer 实例:
var oneMinuteTimer:Timer = new Timer(1000, 60);
Timer 对象在每次达到指定的间隔时都会调度 TimerEvent 对象。TimerEvent 对象的事件类型是 timer(由常量 TimerEvent.TIMER 定义)。TimerEvent 对象包含的属性与标准 Event 对象包含的属性相同。
如果将 Timer 实例设置为固定的间隔数,则在达到最后一次间隔时,它还会调度 timerComplete 事件(由常量 TimerEvent.TIMER_COMPLETE 定义)。
处理字符串
String 类包含使您能够使用文本字符串的方法。字符串是字符的序列。ActionScript 3.0 支持 ASCII 字符和 Unicode 字符。
字符串基础知识
处理字符串简介
在编程语言中,字符串是指一个文本值,即串在一起而组成单个值的一系列字母、数字或其它字符。ActionScript String 类是一种可用来处理文本值的数据类型。
在 ActionScript 中,可使用双引号或单引号将本文引起来以表示字符串值。
重要概念和术语
以下参考列表包含您将会在本章中遇到的重要术语:
ASCII:用于在计算机程序中表示文本字符和符号的系统。ASCII 系统支持 26 个字母英文字母表,以及有限的一组其它字符。
字符 (Character):文本数据的最小单位(单个字母或符号)。
连接 (Concatenation):通过将一个字符串值添加到另一个字符串值结尾,将多个字符串值连接在一起,从而创建一个新的字符串值。
空字符串 (Empty string):不包含任何文本、空白或其它字符的字符串,可以写为 ""。空字符串值不同于具有空值的 String 变量;空 String 变量是指没有赋予 String 实例的变量,而空字符串则包含一个实例,其值不包含任何字符。
字符串 (String):一个文本值(字符序列)。
字符串文本或文本字符串(String literal 或 literal string):在代码中显式编写的字符串值,编写为用双引号或单引号引起来的文本值。
子字符串 (Substring):作为另一个字符串一部分的字符串。
Unicode:用于在计算机程序中表示文本字符和符号的标准系统。Unicode 系统允许使用任何编写系统中的任何字符。
创建字符串
在 ActionScript 3.0 中,String 类用于表示字符串(文本)数据。ActionScript 字符串既支持 ASCII 字符也支持 Unicode 字符。
创建字符串的最简单方式是使用字符串文本。要声明字符串文本,请使用双直引号(")或单直引号(')字符。
您还可以使用 new 运算符来声明字符串。
下面的两个字符串是等效的。
var str1:String = "hello";
var str2:String = new String("hello");
要在使用单引号 (') 分隔符定义的字符串文本内使用单引号 ('),请使用反斜杠转义符 ()。类似地,要在使用双引号 (") 分隔符定义的字符串文本内使用双引号 ("),请使用反斜杠转义符 ()。
您可以根据字符串文本中存在的任何单引号或双引号来选择使用单引号或双引号,如下所示:
var str1:String = "ActionScript <span class='heavy'>3.0</span>";
var str2:String = '<item id="155">banana</item>';
请务必记住 ActionScript 可区分单直引号 (') 和左右单引号 (' 或 ')。对于双引号也同样如此。请使用直引号来分割字符串文本。在将文本从其它来源粘贴到 ActionScript 中时,请确保使用正确的字符。
如下表所示,可以使用反斜杠转义符 () 在字符串文本中定义其它字符:
转义序列
字符
b
退格符
f
换页符
n
换行符
r
回车符
t
制表符
unnnn
Unicode 字符,字符代码由十六进制数字 nnnn 指定;例如,u263a 为笑脸字符。
xnn
ASCII 字符,字符代码由十六进制数字 nn 指定。
'
单引号
"
双引号

单个反斜杠字符
length 属性
每个字符串都有 length 属性,其值等于字符串中的字符数。
空字符串和 null 字符串的长度均为 0。
处理字符串中的字符
字符串中的每个字符在字符串中都有一个索引位置(整数)。第一个字符的索引位置为 0。
您可以使用 charAt() 方法和 charCodeAt() 方法检查字符串各个位置上的字符:
var str:String = "hello world!";
for (var:i = 0; i < str.length; i++)
{
Trace ( str.charAt (i), "-", str.charCodeAt (i) );
}
此外,您还可以通过字符代码,使用 fromCharCode() 方法定义字符串,如下例所示:
var myStr:String = String.fromCharCode (104,101,108,108,111,32,119,111,114,108,100,33);
// 将 myStr 设置为"hello world!"
比较字符串
可以使用以下运算符比较字符串:<、<=、!=、==、=> 和 >。
在将这些运算符用于字符串时,ActionScript 会使用字符串中每个字符的字符代码值从左到右比较各个字符。
使用 == 和 != 运算符可比较两个字符串,也可以将字符串与其它类型的对象进行比较,如下例所示:

var str1:String = "1";
var total:uint = 1;
trace(str1 == total); // true
获取其它对象的字符串表示形式
可以获取任何类型对象的字符串表示形式。所有对象都提供了 toString() 方法来实现此目的:
在使用 + 连接运算符连接 String 对象和不属于字符串的对象时,无需使用 toString() 方法。对于给定对象,String() 全局函数返回的值与调用该对象的 toString() 方法返回的值相同。
连接字符串
字符串连接的含义是:将两个字符串按顺序合并为一个字符串。
可以使用 + 运算符来连接两个字符串,还可以使用 += 运算符来得到相同的结果,此外,String 类还包括 concat() 方法,可按如下方式对其进行使用:
var str1:String = "Bonjour";
var str2:String = "from";
var str3:String = "Paris";
var str4:String = str1.concat(" ", str2, " ", str3); // str4 == "Bonjour from Paris"
如果使用 + 运算符(或 += 运算符)对 String 对象和"非"字符串的对象进行运算,ActionScript 会自动将非字符串对象转换为 String 对象以计算该表达式。
但是,可以使用括号进行分组,为 + 运算符提供运算的上下文,如下例所示:
trace("Total: $" + (4.55 + 1.45)); // 输出:Total: $6
在字符串中查找子字符串和模式
子字符串是字符串内的字符序列。
模式是在 ActionScript 中通过字符串或正则表达式定义的。
通过字符位置查找子字符串
substr() 和 substring() 方法非常类似。两个方法都返回字符串的一个子字符串。并且两个方法都具有两个参数。在这两个方法中,第一个参数是给定字符串中起始字符的位置。不过,在 substr() 方法中,第二个参数是要返回的子字符串的"长度",而在 substring() 方法中,第二个参数是子字符串的"结尾"处字符的位置(该字符未包含在返回的字符串中)。
slice() 方法的功能类似于 substring() 方法。当指定两个非负整数作为参数时,其运行方式将完全一样。但是,slice() 方法可以使用负整数作为参数,此时字符位置将从字符串末尾开始向前算起。可以结合使用非负整数和负整数作为 slice() 方法的参数。
查找匹配子字符串的字符位置
可以使用 indexOf() 和 lastIndexOf() 方法在字符串内查找匹配的子字符串。
indexOf() 方法区分大小写。可以指定第二个参数以指出在字符串中开始进行搜索的起始索引位置。
lastIndexOf() 方法在字符串中查找子字符串的最后一个匹配项。如果为 lastIndexOf() 方法提供了第二个参数,搜索将从字符串中的该索引位置反向(从右到左)进行。
创建由分隔符分隔的子字符串数组
可使用 split() 方法创建子字符串数组,该数组根据分隔符进行划分。
split() 方法的第二个参数是可选参数,该参数定义所返回数组的最大大小。此外,还可以使用正则表达式作为分隔符。
在字符串中查找模式并替换子字符串
String 类提供了在字符串中处理模式的以下方法:
使用 match() 和 search() 方法可查找与模式相匹配的子字符串。
使用 replace() 方法可查找与模式相匹配的子字符串并使用指定子字符串替换它们。
查找匹配的子字符串
search() 方法返回与给定模式相匹配的第一个子字符串的索引位置,您还可以使用正则表达式定义要匹配的模式。search() 方法仅查找一个匹配项并返回其起始索引位置,即便在正则表达式中设置了 g(全局)标志。
match() 方法的工作方式与此类似。它搜索一个匹配的子字符串。但是,如果在正则表达式模式中使用了全局标志,match() 将返回一个包含匹配子字符串的数组。
替换匹配的子字符串
您可以使用 replace() 方法在字符串中搜索指定模式并使用指定的替换字符串替换匹配项。
在正则表达式中设置了 i (ignoreCase) 标志,则匹配的字符串是不区分大小写的;设置了 g (global) 标志,则会替换多个匹配项。
可以在替换字符串中包括以下 $ 替换代码。下表中显示的替换文本将被插入并替换 $ 替换代码:
$ 代码
替换文本
$$
$
$&
匹配的子字符串。
$`
字符串中位于匹配的子字符串前面的部分。该代码使用左单直引号字符 (`) 而不是单直引号 (') 或左单弯引号 (')。
$'
字符串中位于匹配的子字符串后的部分。该代码使用单直引号 (')。
$n
第 n 个捕获的括号组匹配项,其中 n 是 1-9 之间的数字,并且 $n 后面没有十进制数字。
$nn
第 nn 个捕获的括号组匹配项,其中 nn 是一个十进制的两位数 (01-99)。如果未定义第 nn 个捕获内容,则替换文本为空字符串。
也可以使用函数作为 replace() 方法的第二个参数。
在大小写之间转换字符串
如下例所示,toLowerCase() 方法和 toUpperCase() 方法分别将字符串中的英文字母字符转换为小写和大写。执行完这些方法后,源字符串仍保持不变。
这些方法可处理扩展字符,而并不仅限于 a-z 和 A-Z。
处理数组
使用数组可以在单数据结构中存储多个值。可以使用简单的索引数组(使用固定有序整数索引存储值),也可以使用复杂的关联数组(使用任意键存储值)。数组也可以是多维的,即包含本身是数组的元素。
数组基础知识
处理数组简介
数组是一种编程元素,它用作一组项目的容器。通常,数组中的所有项目都是相同类的实例,但这在 ActionScript 中并不是必需的。数组中的各个项目称为数组的"元素"。可以将数组视为变量的"文件柜"。可以将变量作为元素添加到数组中,也可以将数组作为单个变量使用;将这些变量作为组使用;或者,可以分别访问单个变量。
最常见的 ActionScript 数组类型是"索引数组",此数组将每个项目存储在编号位置(称为"索引"),您可以使用该编号来访问项目,如地址。Array 类用于表示索引数组。索引数组可以很好地满足大多数编程需要。索引数组的一个特殊用途是多维数组,此索引数组的元素也是索引数组(这些数组又包含其它元素)。另一种数组类型是"关联数组",该数组使用字符串"键"来标识各个元素,而不是使用数字索引。最后,对于高级用户,ActionScript 3.0 还包括 Dictionary 类(表示"字典"),在此数组中,您可以将任何类型的对象用作键来区分元素。
重要概念和术语
以下参考列表包含将会在本章中遇到的重要术语:
数组 (Array):用作容器以将多个对象组合在一起的对象。
关联数组 (Associative array):使用字符串键来标识各个元素的数组。
字典 (Dictionary):其项目由一对对象(称为键和值)组成的数组。它使用键来标识单个元素,而不是使用数字索引。
元素 (Element):数组中的单个项目。
索引 (Index):用于标识索引数组中的单个元素的数字"地址"。
索引数组 (Indexed array):这是一种标准类型的数组,它将每个元素存储在编号元素中,并使用数字(索引)来标识各个元素。
键 (Key):用于标识关联数组或字典中的单个元素的字符串或对象。
多维数组 (Multidimensional array):此数组包含的项目是数组,而不是单个值。
索引数组
索引数组存储一系列经过组织的单个或多个值,其中的每个值都可以通过使用一个无符号整数值进行访问。第一个索引始终是数字 0,且添加到数组中的每个后续元素的索引以 1 为增量递增。可以调用 Array 类构造函数或使用数组文本初始化数组来创建索引数组。
Array 类中还包含可用来修改索引数组的属性和方法。这些属性和方法几乎是专用于索引数组而非关联数组的。
索引数组使用无符号 32 位整数作为索引号。索引数组的最大大小为 232-1,即 4,294,967,295。如果要创建的数组大小超过最大值,则会出现运行时错误。
数组元素的值可以为任意数据类型。ActionScript 3.0 不支持"指定类型的数组"概念,也就是说,不能指定数组的所有元素都属于特定数据类型。
创建数组
Array 构造函数的使用有三种方式。
第一种,如果调用不带参数的构造函数,会得到空数组。
第二种,如果将一个数字用作 Array 构造函数的唯一参数,则会创建长度等于此数值的数组,并且每个元素的值都设置为 undefined。参数必须为介于值 0 和 4,294,967,295 之间的无符号整数。
第三种,如果调用构造函数并传递一个元素列表作为参数,将创建具有与每个参数对应的元素的数组。
也可以创建具有数组文本或对象文本的数组。
插入数组元素
可以使用 Array 类的三种方法(push()、unshift() 和 splice())将元素插入数组。push() 方法用于在数组末尾添加一个或多个元素。换言之,使用 push() 方法在数组中插入的最后一个元素将具有最大索引号。unshift() 方法用于在数组开头插入一个或多个元素,并且始终在索引号 0 处插入。splice() 方法用于在数组中的指定索引处插入任意数目的项目。
push() 和 unshift() 方法均返回一个无符号整数,它们表示修改后的数组长度。在用于插入元素时,splice() 方法返回空数组,这看上去也许有点奇怪,但考虑到 splice() 方法的多用途性,您便会觉得它更有意义。通过使用 splice() 方法,不仅可以将元素插入到数组中,而且还可以从数组中删除元素。用于删除元素时,splice() 方法将返回包含被删除元素的数组。
删除数组元素
可以使用 Array 类的三种方法(pop()、shift() 和 splice())从数组中删除元素。pop() 方法用于从数组末尾删除一个元素。换言之,它将删除位于最大索引号处的元素。shift() 方法用于从数组开头删除一个元素,也就是说,它始终删除索引号 0 处的元素。splice() 方法既可用来插入元素,也可以删除任意数目的元素,其操作的起始位置位于由发送到此方法的第一个参数指定的索引号处。
pop() 和 shift() 方法均返回已删除的项。由于数组可以包含任意数据类型的值,因而返回值的数据类型为 Object。splice() 方法将返回包含被删除值的数组。
在数组元素上使用 delete 运算符。delete 运算符用于将数组元素的值设置为 undefined,但它不会从数组中删除元素。
可以使用数组的 length 属性截断数组。如果将数组的 length 属性设置为小于数组当前长度的值,则会截断数组,在索引号高于 length 的新值减 1 处所存储的任何元素将被删除。
对数组排序
可以使用三种方法(reverse()、sort() 和 sortOn())通过排序或反向排序来更改数组的顺序。所有这些方法都用来修改现有数组。reverse() 方法用于按照以下方式更改数组的顺序:最后一个元素变为第一个元素,倒数第二个元素变为第二个元素,依此类推。sort() 方法可用来按照多种预定义的方式对数组进行排序,甚至可用来创建自定义排序算法。sortOn() 方法可用来对对象的索引数组进行排序,这些对象具有一个或多个可用作排序键的公共属性。
reverse() 方法不带参数,也不返回值,但可以将数组从当前顺序切换为相反顺序。
sort() 方法按照"默认排序顺序"重新安排数组中的元素。默认排序顺序具有以下特征:
排序区分大小写,也就是说大写字符优先于小写字符。例如,字母 D 优先于字母 b。
排序按照升序进行,也就是说低位字符代码(例如 A)优先于高位字符代码(例如 B)。
排序将相同的值互邻放置,并且不区分顺序。
排序基于字符串,也就是说,在比较元素之前,先将其转换为字符串(例如,10 优先于 3,因为相对于字符串 "3" 而言,字符串 "1" 具有低位字符代码)。
sort() 方法具有 options 参数,可通过该参数改变默认排序顺序的各个特征。options 是由 Array 类中的一组静态常量定义的,如以下列表所示:
Array.CASEINSENSITIVE:此选项可使排序不区分大小写。例如,小写字母 b 优先于大写字母 D。
Array.DESCENDING:用于颠倒默认的升序排序。例如,字母 B 优先于字母 A。
Array.UNIQUESORT:如果发现两个相同的值,此选项将导致排序中止。
Array.NUMERIC:这会导致排序按照数字顺序进行,比方说 3 优先于 10。
您也可以编写自定义排序函数,然后将其作为参数传递给 sort() 方法。
sortOn() 方法是为具有包含对象的元素的索引数组设计的。这些对象应至少具有一个可用作排序键的公共属性。如果将 sortOn() 方法用于任何其它类型的数组,则会产生意外结果。
sortOn() 方法定义两个参数 fieldName 和 options。必须将 fieldName 参数指定为字符串。Array.NUMERIC 参数用于确保按照数字顺序进行排序,而不是按照字母顺序。
通常,sort() 和 sortOn() 方法用来修改数组。如果要对数组排序而又不修改现有数组,请将 Array.RETURNINDEXEDARRAY 常量作为 options 参数的一部分进行传递。此选项将指示方法返回反映排序的新数组同时保留原始数组原封不动。方法返回的数组为由反映新排序顺序的索引号组成的简单数组,不包含原始数组的任何元素。
查询数组
Array 类中的其余四种方法 concat()、join()、slice() 和 toString() 用于查询数组中的信息,而不修改数组。concat() 和 slice() 方法返回新数组;而 join() 和 toString() 方法返回字符串。concat() 方法将新数组和元素列表作为参数,并将其与现有数组结合起来创建新数组。slice() 方法具有两个名为 startIndex 和 endIndex 的参数,并返回一个新数组,它包含从现有数组分离出来的元素副本。分离从 startIndex 处的元素开始,到 endIndex 处的前一个元素结束。值得强调的是,endIndex 处的元素不包括在返回值中。
可以使用 join() 和 toString() 方法查询数组,并将其内容作为字符串返回。如果 join() 方法没有使用参数,则这两个方法的行为相同,它们都返回包含数组中所有元素的列表(以逗号分隔)的字符串。与 toString() 方法不同,join() 方法接受名为 delimiter 的参数;可以使用此参数,选择要用作返回字符串中各个元素之间分隔符的符号。
对于 join() 方法,应注意的一个问题是,无论为主数组元素指定的分隔符是什么,为嵌套数组返回的值始终以逗号作为分隔符。
关联数组
关联数组有时候也称为"哈希"或"映射",它使用"键"而非数字索引来组织存储的值。关联数组中的每个键都是用于访问一个存储值的唯一字符串。关联数组为 Object 类的实例,也就是说每个键都与一个属性名称对应。关联数组是键和值对的无序集合。在代码中,不应期望关联数组的键按特定的顺序排列。
ActionScript 3.0 中引入了名为"字典"的高级关联数组。字典是 flash.utils 包中 Dictionary 类的实例,使用的键可以为任意数据类型,但通常为 Object 类的实例。换言之,字典的键不局限于 String 类型的值。
具有字符串键的关联数组
在 ActionScript 3.0 中有两种创建关联数组的方法。
第一种方法是使用 Object 构造函数,它的优点是可以使用对象文本初始化数组。Object 类的实例(也称作"通用对象")在功能上等同于关联数组。通用对象的每个属性名称都用作键,提供对存储的值的访问。
如果在声明数组时不需要初始化,可以使用 Object 构造函数创建数组。
使用对象文本或 Object 类构造函数创建数组后,可以使用括号运算符 ([]) 或点运算符 (.) 在数组中添加值。
如名为 aspect ratio 的键包含空格字符。也就是说,空格字符可以与括号运算符一起使用,但试图与点运算符一起使用时会生成一个错误。不建议在键名称中使用空格。
第二种关联数组创建方法是使用 Array 构造函数,然后使用括号运算符 ([]) 或点运算符 (.) 将键和值对添加到数组中。如果将关联数组声明为 Array 类型,则将无法使用对象文本初始化该数组。
使用 Array 构造函数创建关联数组没有什么优势。即使使用 Array 构造函数或 Array 数据类型,也不能将 Array 类的 Array.length 属性或任何方法用于关联数组。最好将 Array 构造函数用于创建索引数组。
具有对象键的关联数组
可以使用 Dictionary 类创建使用对象而非字符串作为键的关联数组。这样的数组有时候也称作字典、哈希或映射。可以使用属性访问运算符 ([]) 访问与每个键关联的值。
使用对象键循环访问
可以使用 for..in 循环或 for each..in 循环来循环访问 Dictionary 对象的内容。for..in 循环用来基于键进行循环访问;而 for each..in 循环用来基于与每个键关联的值进行循环访问。
可以使用 for..in 循环直接访问 Dictionary 对象的对象键。还可以使用属性访问运算符 ([]) 访问 Dictionary 对象的值。
可以使用 for each..in 循环直接访问 Dictionary 对象的值。
多维数组
多维数组将其它数组作为其元素。在多维数组中,可以使用任意组合的索引数组和关联数组。
两个索引数组
使用两个索引数组时,可以将结果呈现为表或电子表格。第一个数组的元素表示表的行,第二个数组的元素表示表的列。可以使用括号记号访问任意任务列表中的单个项。
具有索引数组的关联数组
克隆数组
Array 类不具有复制数组的内置方法。可以通过调用不带参数的 concat() 或 slice() 方法来创建数组的"浅副本"。在浅副本中,如果原始数组具有对象元素,则仅复制指向对象的引用而非对象本身。与原始数组一样,副本也指向相同的对象。对对象所做的任何更改都会在两个数组中反映出来。
在"深副本"中,将复制原始数组中的所有对象,从而使新数组和原始数组指向不同的对象。深度复制需要多行代码,通常需要创建函数。可以将此类函数作为全局实用程序函数或 Array 子类的方法来进行创建。
处理事件
事件处理基础知识
事件处理简介
在 ActionScript 3.0 中,每个事件都由一个事件对象表示。事件对象是 Event 类或其某个子类的实例。事件对象不但存储有关特定事件的信息,还包含便于操作事件对象的方法。
当 Flash Player 检测到事件时,它会创建一个事件对象以表示该特定事件。创建事件对象之后,Flash Player 即"调度"该事件对象,这意味着将该事件对象传递给作为事件目标的对象。作为所调度事件对象的目标的对象称为"事件目标"。但是,如果事件目标在显示列表中,则在显示列表层次结构中将事件对象向下传递,直到到达事件目标为止。在某些情况下,该事件对象随后会沿着相同路线在显示列表层次结构中向上"冒泡"回去。显示列表层次结构中的这种遍历行为称为"事件流"。
您可以使用事件侦听器"侦听"代码中的事件对象。"事件侦听器"是您编写的用于响应特定事件的函数或方法。要确保您的程序响应事件,必须将事件侦听器添加到事件目标,或添加到作为事件对象事件流的一部分的任何显示列表对象。
无论何时编写事件侦听器代码,该代码都会采用以下基本结构(以粗体显示的元素是占位符,您将针对具体情况对其进行填写):
function eventResponse(eventObject:EventType):void
{
// 此处是为响应事件而执行的动作。
}

eventTarget.addEventListener(EventType.EVENT_NAME, eventResponse);
此代码执行两个操作。首先,它定义一个函数,这是指定为响应事件而执行的动作的方法。接下来,调用源对象的 addEventListener() 方法,实际上就是为指定事件"订阅"该函数,以便当该事件发生时,执行该函数的动作。当事件实际发生时,事件目标将检查其注册为事件侦听器的所有函数和方法的列表。然后,它依次调用每个对象,以将事件对象作为参数进行传递。
重要概念和术语
默认行为 (Default behavior):某些事件包含通常与事件一起发生的行为(称为默认行为)。
调度 (Dispatch):通知事件侦听器发生了事件。
事件 (Event):对象可以通知其它对象它所发生的情况。
事件流 (Event flow):如果显示列表中的对象(屏幕上显示的对象)发生事件,则会向包含该对象的所有对象通知此事件,并依次通知其事件侦听器。此过程从舞台开始,并在显示列表中一直进行到发生事件的实际对象,然后再返回到舞台。此过程称为事件流。
事件对象 (Event object):此对象包含发生的特定事件的相关信息,当调度事件时,此信息将被发送到所有侦听器。
事件目标 (Event target):实际调度事件的对象。
侦听器 (Listener):对象或在对象中注册其自身的函数,用于指示发生特定事件时应通知它。
ActionScript 3.0 中的事件处理
ActionScript 3.0 引入了单一事件处理模型,以替代以前各语言版本中存在的众多不同的事件处理机制。该新事件模型基于文档对象模型 (DOM) 第 3 级事件规范。虽然 SWF 文件格式并不专门遵循文档对象模型标准,但显示列表和 DOM 结构之间存在的相似性足以使 DOM 事件模型的实现成为可能。显示列表中的对象类似于 DOM 层次结构中的节点,在本讨论中,术语"显示列表对象"和"节点"可互换使用。
Flash Player 实现的 DOM 事件模型包括一个名为"默认行为"的概念。"默认行为"是 Flash Player 作为特定事件的正常后果而执行的操作。
默认行为
开发人员通常负责编写响应事件的代码。但在某些情况下,行为通常与某一事件关联,使得 Flash Player 会自动执行该行为,除非开发人员添加了取消该行为的代码。由于 Flash Player 会自动表现该行为,因此这类行为称为默认行为。
并非所有默认行为都可以阻止。
许多类型的事件对象没有关联的默认行为。
事件流
只要发生事件,Flash Player 就会调度事件对象。如果事件目标不在显示列表中,则 Flash Player 将事件对象直接调度到事件目标。例如,Flash Player 将 progress 事件对象直接调度到 URLStream 对象。但是,如果事件目标在显示列表中,则 Flash Player 将事件对象调度到显示列表,事件对象将在显示列表中穿行,直到到达事件目标。
"事件流"说明事件对象如何在显示列表中穿行。显示列表以一种可以描述为树的层次结构形式进行组织。位于显示列表层次结构顶部的是舞台,它是一种特殊的显示对象容器,用作显示列表的根。舞台由 flash.display.Stage 类表示,且只能通过显示对象访问。每个显示对象都有一个名为 stage 的属性,该属性表示应用程序的舞台。
当 Flash Player 调度事件对象时,该事件对象进行一次从舞台到"目标节点"的往返行程。DOM 事件规范将目标节点定义为代表事件目标的节点。也就是说,目标节点是发生了事件的显示列表对象。例如,如果用户单击名为 child1 的显示列表对象,Flash Player 将使用 child1 作为目标节点来调度事件对象。
从概念上来说,事件流分为三部分。第一部分称为捕获阶段,该阶段包括从舞台到目标节点的父节点范围内的所有节点。第二部分称为目标阶段,该阶段仅包括目标节点。第三部分称为冒泡阶段。冒泡阶段包括从目标节点的父节点返回到舞台的行程中遇到的节点。
如果您将显示列表想像为一个垂直的层次结构,其中舞台位于顶层(如下图显示),那么这些阶段的名称就更容易理解了:

如果用户单击 Child1,Flash Player 将向事件流调度一个事件对象。如下面的图像所示,对象的行程从 Stage 开始,向下移动到 Parent,然后移动到 Child1,再"冒泡"返回到 Stage:在行程中重新经过 Parent,再返回到 Stage。

在该示例中,捕获阶段在首次向下行程中包括 Stage 和 Parent。目标阶段包括在 Child1 花费的时间。冒泡阶段包括在向上返回到根节点的行程中遇到的 Parent 和 Stage。
事件流使现在的事件处理系统比 ActionScript 程序员以前使用的事件处理系统功能更为强大。早期版本的 ActionScript 中没有事件流,这意味着事件侦听器只能添加到生成事件的对象。在 ActionScript 3.0 中,您不但可以将事件侦听器添加到目标节点,还可以将它们添加到事件流中的任何节点。
当用户界面组件包含多个对象时,沿事件流添加事件侦听器的功能十分有用。例如,按钮对象通常包含一个用作按钮标签的文本对象。如果无法将侦听器添加到事件流,您将必须将侦听器添加到按钮对象和文本对象,以确保您收到有关在按钮上任何位置发生的单击事件的通知。而事件流的存在则使您可以将一个事件侦听器放在按钮对象上,以处理文本对象上发生的单击事件或按钮对象上未被文本对象遮住的区域上发生的单击事件。
不过,并非每个事件对象都参与事件流的所有三个阶段。某些类型的事件(例如 enterFrame 和 init 类型的事件)会直接调度到目标节点,并不参与捕获阶段和冒泡阶段。其它事件可能以不在显示列表中的对象为目标,例如调度到 Socket 类的实例的事件。这些事件对象也将直接流至目标对象,而不参与捕获和冒泡阶段。
事件对象
在新的事件处理系统中,事件对象有两个主要用途。首先,事件对象通过将特定事件的信息存储在一组属性中,来代表实际事件。第二,事件对象包含一组方法,可用于操作事件对象和影响事件处理系统的行为。
为方便对这些属性和方法的访问,Flash Player API 定义了一个 Event 类,作为所有事件对象的基类。Event 类定义一组基本的适用于所有事件对象的属性和方法。
了解 Event 类属性
Event 类定义许多只读属性和常数,以提供有关事件对象的重要信息。以下内容尤其重要:
事件对象类型由常数表示,并存储在 Event.type 属性中。
事件的默认行为是否可以被阻止由布尔值表示,并存储在 Event.cancelable 属性中。
事件流信息包含在其余属性中。
事件对象类型
每个事件对象都有关联的事件类型。数据类型以字符串值的形式存储在 Event.type 属性中。知道事件对象的类型是非常有用的,这样您的代码就可以区分不同类型的事件。
大约有 20 多种事件类型与 Event 类自身关联并由 Event 类常数表示,其中某些数据类型显示在摘自 Event 类定义的以下代码中:
package flash.events
{
public class Event
{
// 类常数
public static const ACTIVATE:String = "activate";
public static const ADDED:String = "added";
// 为简便起见,省略了其余常数
}
}
这些常数提供了引用特定事件类型的简便方法。您应使用这些常数而不是它们所代表的字符串。如果您的代码中拼错了某个常数名称,编译器将捕获到该错误,但如果您改为使用字符串,则编译时可能不会出现拼写错误,这可能导致难以调试的意外行为。
默认行为信息
代码可通过访问 cancelable 属性来检查是否可以阻止任何指定事件对象的默认行为。cancelable 属性包含一个布尔值,用于指示是否可以阻止默认行为。您可以使用 preventDefault() 方法阻止或取消与少量事件关联的默认行为。
事件流信息
其余 Event 类属性包含有关事件对象及其与事件流的关系的重要信息,如以下列表所述:
bubbles 属性包含有关事件流中事件对象参与的部分的信息。
eventPhase 属性指示事件流中的当前阶段。
target 属性存储对事件目标的引用。
currentTarget 属性存储对当前正在处理事件对象的显示列表对象的引用。
bubbles 属性
如果事件对象参与事件流的冒泡阶段,则将该事件称为"冒泡",这指的是从目标节点将事件对象往回传递,经过目标节点的父节点,直到到达舞台。Event.bubbles 属性存储一个布尔值,用于指示事件对象是否参与冒泡阶段。由于冒泡的所有事件还参与捕获和目标阶段,因此这些事件参与事件流的所有三个阶段。如果值为 true,则事件对象参与所有三个阶段。如果值为 false,则事件对象不参与冒泡阶段。
eventPhase 属性
您可以通过调查任何事件对象的 eventPhase 属性来确定事件阶段。eventPhase 属性包含一个无符号整数值,该值代表三个事件流阶段中的一个阶段。Flash Player API 定义了单独的 EventPhase 类,该类包含三个对应于三个无符号整数值的常量,如以下摘录代码中所示:
package flash.events
{
public final class EventPhase
{
public static const CAPTURING_PHASE:uint = 1;
public static const AT_TARGET:uint = 2;
public static const BUBBLING_PHASE:uint = 3;
}
}
这些常数对应于 eventPhase 属性的三个有效值。使用这些常数可以使您的代码可读性更好。例如,如果要确保仅当事件目标在目标阶段中时才调用名为 myFunc() 的函数,您可以使用以下代码来测试此条件:
if (event.eventPhase == EventPhase.AT_TARGET)
{
myFunc();
}
target 属性
target 属性包含对作为事件目标的对象的引用。在某些情况下,这很简单,例如当麦克风变为活动状态时,事件对象的目标是 Microphone 对象。但是,如果目标在显示列表中,则必须考虑显示列表层次结构。例如,如果用户在包括重叠的显示列表对象的某一点输入一个鼠标单击,则 Flash Player 始终会选择距离舞台层次最深的对象作为事件目标。
对于复杂的 SWF 文件,特别是那些通常使用更小的子对象来修饰按钮的 SWF 文件,target 属性可能并不常用,因为它通常指向按钮的子对象,而不是按钮。在这些情况下,常见的做法是将事件侦听器添加到按钮并使用 currentTarget 属性,因为该属性指向按钮,而 target 属性可能指向按钮的子对象。
currentTarget 属性
currentTarget 属性包含对当前正在处理事件对象的对象的引用。
了解 Event 类方法
共有三种类别的 Event 类方法:
实用程序方法:可以创建事件对象的副本或将其转换为字符串
事件流方法:用于从事件流中删除事件对象
默认行为方法:可阻止默认行为或检查是否已阻止默认行为
Event 流实用程序方法
Event 类中有两个实用程序方法。clone() 方法用于创建事件对象的副本。toString() 方法用于生成事件对象属性的字符串表示形式以及它们的值。这两个方法都由事件模型系统在内部使用,但对开发人员公开以用于一般用途。
对于创建 Event 类的子类的高级开发人员来说,必须覆盖和实现两个实用程序方法的版本,以确保事件子类正常使用。
停止事件流
可以调用 Event.stopPropogation() 方法或 Event.stopImmediatePropogation() 方法来阻止在事件流中继续执行事件对象。这两种方法几乎相同,只有在是否允许执行当前节点的其它事件侦听器方面不同:
Event.stopPropogation() 方法可阻止事件对象移动到下一个节点,但只有在允许执行当前节点上的任何其它事件侦听器之后才起作用。
Event.stopImmediatePropogation() 方法也阻止事件对象移动到下一个节点,但不允许执行当前节点上的任何其它事件侦听器。
调用其中任何一个方法对是否发生与事件关联的默认行为没有影响。
取消默认事件行为
与取消默认行为有关的两个方法是 preventDefault() 方法和 isDefaultPrevented() 方法。调用 preventDefault() 方法可取消与事件关联的默认行为。要查看是否已针对事件对象调用了 preventDefault(),请调用 isDefaultPrevented() 方法,如果已经调用,该方法将返回值 true,否则返回值 false。
仅当可以取消事件的默认行为时,preventDefault() 方法才起作用。可通过参考该事件类型的 API 文档或使用 ActionScript 检查事件对象的 cancelable 属性来确定是否属于这种情况。
取消默认行为对事件对象通过事件流的进度没有影响。使用 Event 类的事件流方法可以从事件流中删除事件对象。
事件侦听器
事件侦听器也称为事件处理函数,是 Flash Player 为响应特定事件而执行的函数。添加事件侦听器的过程分为两步。首先,为 Flash Player 创建一个为响应事件而执行的函数或类方法。这有时称为侦听器函数或事件处理函数。然后,使用 addEventListener() 方法,在事件的目标或位于适当事件流上的任何显示列表对象中注册侦听器函数。
创建侦听器函数
创建侦听器函数是 ActionScript 3.0 事件模型与 DOM 事件模型不同的一个方面。在 DOM 事件模型中,事件侦听器和侦听器函数之间有一个明显的不同:即事件侦听器是实现 EventListener 接口的类的实例,而侦听器是该类的名为 handleEvent() 的方法。在 DOM 事件模型中,您注册的是包含侦听器函数的类实例,而不是实际的侦听器函数。
在 ActionScript 3.0 事件模型中,事件侦听器和侦听器函数之间没有区别。ActionScript 3.0 没有 EventListener 接口,侦听器函数可以在类外部定义,也可以定义为类的一部分。此外,无需将侦听器函数命名为 handleEvent() ─ 可以将它们命名为任何有效的标识符。在 ActionScript 3.0 中,您注册的是实际侦听器函数的名称。
管理事件侦听器
使用 IEventDispatcher 接口的方法来管理侦听器函数。IEventDispatcher 接口是 ActionScript 3.0 版本的 DOM 事件模型的 EventTarget 接口。虽然名称 IEventDispatcher 似乎暗示着其主要用途是发送(调度)事件对象,但该类的方法实际上更多用于注册、检查和删除事件侦听器。IEventDispatcher 接口定义五个方法,如以下代码中所示:
package flash.events
{
public interface IEventDispatcher
{
function addEventListener(eventName:String,
listener:Object,
useCapture:Boolean=false,
priority:Integer=0,
useWeakReference:Boolean=false):Boolean;

function removeEventListener(eventName:String,
listener:Object,
useCapture:Boolean=false):Boolean;

function dispatchEvent(eventObject:Event):Boolean;

function hasEventListener(eventName:String):Boolean;
function willTrigger(eventName:String):Boolean;
}
}
Flash Player API 使用 EventDispatcher 类来实现 IEventDispatcher 接口,该类用作可以是事件目标或事件流一部分的所有类的基类。例如,DisplayObject 类继承自 EventDispatcher 类。这意味着,显示列表中的所有对象都可以访问 IEventDispatcher 接口的方法。
添加事件侦听器
addEventListener() 方法是 IEventDispatcher 接口的主要函数。使用它来注册侦听器函数。两个必需的参数是 type 和 listener。type 参数用于指定事件的类型。listener 参数用于指定发生事件时将执行的侦听器函数。listener 参数可以是对函数或类方法的引用。
指定 listener 参数时,不要使用括号。
通过使用 addEventListener() 方法的 useCapture 参数,可以控制侦听器将处于活动状态的事件流阶段。如果 useCapture 设置为 true,侦听器将在事件流的捕获阶段成为活动状态。如果 useCapture 设置为 false,侦听器将在事件流的目标阶段和冒泡阶段处于活动状态。要在事件流的所有阶段侦听某一事件,您必须调用 addEventListener() 两次,第一次调用时将 useCapture 设置为 true,第二次调用时将 useCapture 设置为 false。
addEventListener() 方法的 priority 参数并不是 DOM Level 3 事件模型的正式部分。ActionScript 3.0 中包括它是为了在组织事件侦听器时提供更大的灵活性。调用 addEventListener() 时,可以将一个整数值作为 priority 参数传递,以设置该事件侦听器的优先级。默认值为 0,但您可以将它设置为负整数值或正整数值。将优先执行此数字较大的事件侦听器。对于具有相同优先级的事件侦听器,则按它们的添加顺序执行,因此将优先执行较早添加的侦听器。
可以使用 useWeakReference 参数来指定对侦听器函数的引用是弱引用还是正常引用。通过将此参数设置为 true,可避免侦听器函数在不再需要时仍然存在于内存中的情况。Flash Player 使用一项称为"垃圾回收"的技术从内存中清除不再使用的对象。如果不存在对某个对象的引用,则该对象被视为不再使用。垃圾回收器不考虑弱引用,这意味着如果侦听器函数仅具有指向它的弱引用,则符合垃圾回收条件。
该参数的一个重要后果与显示对象事件的处理有关。通常,您可能希望从显示列表中删除显示对象时,也将其从内存中删除。但是,如果其它对象已在 useWeakReference 参数设置为 false(默认值)时作为侦听器订阅该显示对象,该显示对象将继续存在于 Flash Player 的内存中,即使它已不再显示在屏幕中。要解决该问题,可以使所有侦听器在 useWeakReference 参数设置为 true 时订阅该显示对象,或者使用 removeEventListener() 方法从该显示对象中删除所有事件侦听器。
删除事件侦听器
可以使用 removeEventListener() 方法删除不再需要的事件侦听器。删除将不再使用的所有侦听器是个好办法。必需的参数包括 eventName 和 listener 参数,这与 addEventListener() 方法所需的参数相同。回想一下,您可以通过调用 addEventListener() 两次(第一次调用时将 useCapture 设置为 true,第二次调用时将其设置为 false),在所有事件阶段侦听事件。要删除这两个事件侦听器,您需要调用 removeEventListener() 两次,第一次调用时将 useCapture 设置为 true,第二次调用时将其设置为 false。
调度事件
高级程序员可以使用 dispatchEvent() 方法将自定义事件对象调度到事件流。该方法唯一接受的参数是对事件对象的引用,此事件对象必须是 Event 类的实例或子类。调度后,事件对象的 target 属性将设置为对其调用了 dispatchEvent() 的对象。
检查现有的事件侦听器
IEventDispatcher 接口的最后两个方法提供有关是否存在事件侦听器的有用信息。如果在特定显示列表对象上发现特定事件类型的事件侦听器,hasEventListener() 方法将返回 true。如果发现特定显示列表对象的侦听器,willTrigger() 方法也会返回 true。但 willTrigger() 不但检查该显示对象上的侦听器,还会检查该显示列表对象在事件流所有阶段中的所有父级上的侦听器。
没有侦听器的错误事件
ActionScript 3.0 中处理错误的主要机制是异常而不是事件,但对于异步操作(例如加载文件),异常处理不起作用。如果在这样的异步操作中发生错误,Flash Player 会调度一个错误事件对象。如果不为错误事件创建侦听器,Flash Player 的调试器版本将打开一个对话框,其中包含有关该错误的信息。
大多数错误事件基于 ErrorEvent 类,而且同样具有一个名为 text 的属性,它用于存储 Flash Player 显示的错误消息。两个异常是 StatusEvent 和 NetStatusEvent 类。这两个类都具有一个 level 属性(StatusEvent.level 和 NetStatusEvent.info.level)。当 level 属性的值为"error"时,这些事件类型被视为错误事件。
错误事件将不会导致 SWF 文件停止运行。它仅在浏览器插件和独立播放器的调试器版本上显示为对话框,在创作播放器的输出面板中显示为消息,在 Adobe Flex Builder 2 的日志文件中显示为条目,而在 Flash Player 的发行版中则根本不显示。
使用正则表达式
正则表达式描述用于查找和处理字符串中的匹配文本的模式。切记,不同的编程环境实现正则表达式的方式也不同。ActionScript 3.0 按照 ECMAScript 第 3 版语言规范 (ECMA-262) 中的定义实现正则表达式。
正则表达式基础知识
使用正则表达式简介
正则表达式描述字符模式。通常,正则表达式用于验证文本值是否符合特定模式,或者替换与特定模式匹配的部分文本值。
正则表达式文本是使用正斜杠 (/) 界定的。
重要概念和术语
转义字符 (Escape character):此字符指示应将后面的字符视为元字符,而不是字面字符。在正则表达式语法中,反斜杠字符 () 就是转义字符,因此反斜杠后跟另一个字符是一个特殊代码,而不仅仅是字符本身。
标志 (Flag):此字符指定有关应如何使用正则表达式模式的一些选项,如是否区分大写和小写字符。
元字符 (Metacharacter):在正则表达式模式中具有特殊含义的字符,它与从字面意义上在模式中表示该字符相对。
数量表示符 (Quantifier):一个或几个字符,指示应将模式部分重复多少次。
正则表达式 (Regular expression):用于定义字符模式的程序语句,它可用来确认其它字符串是否与该模式匹配,或者替换字符串的一部分。
正则表达式语法
建正则表达式实例
有两种方法可以创建正则表达式实例。一种方法是使用正斜杠字符 (/) 来界定正则表达式,另一种是使用 new 构造函数。
正斜杠界定正则表达式的方式与用引号界定字符串文本的方式相同。正斜杠内的正则表达式部分定义"模式"。正则表达式还可以在后一个界定斜杠后包含"标志"。这些标志也看作是正则表达式的一部分,但是它们独立于模式。
使用 new 构造函数时,使用两个字符串来定义正则表达式。第一个字符串定义模式,第二个字符串定义标志。
如果在使用正斜杠界定符定义的正则表达式中包含正斜杠,则必须在正斜杠前面加上反斜杠 () 转义字符。
要在使用 new 构造函数定义的正则表达式中包含引号,您必须在引号前面加上反斜杠 () 转义字符(就像定义任何 String 文本一样)。请勿在使用正斜杠界定符定义的正则表达式中对引号使用反斜杠转义字符。同样地,不要在使用 new 构造函数定义的正则表达式中对正斜杠使用转义字符。
此外,在使用 new 构造函数定义的正则表达式中,要使用以反斜杠 () 字符开头的元序列,请键入两个反斜杠字符。
字符、元字符和元序列
关于元字符
下表总结了可以在正则表达式中使用的元字符:
元字符
描述
^(尖号)
匹配字符串的开头。设置 m (multiline) 标志后,尖号还匹配行的开头。请注意,尖号用在字符类的开头时表示符号反转而非字符串的开头。
$(美元符号)
匹配字符串的结尾。设置 m (multiline) 标志后,$ 还匹配换行 (n) 字符前面的位置。
(反斜杠)
对特殊字符的特殊元字符含义进行转义。
此外,如果要在正则表达式文本中使用正斜杠字符,也要使用反斜杠字符。
.(点)
匹配任意单个字符。 只有设置 s (dotall) 标志时,点才匹配换行字符 (n)。
*(星号)
匹配前面重复零次或多次的项目。
+(加号)
匹配前面重复一次或多次的项目。
?(问号)
匹配前面重复零次或一次的项目。
( 和 )
在正则表达式中定义组。以下情况下使用组:
限制逻辑"或"字符 | 的范围:/(a|b|c)d/
定义数量表示符的范围:/(walla.){1,2}/
用在逆向引用中。例如,下面的正则表达式中的 1 匹配模式的第一个括号组中的匹配内容:
/(w*) is repeated: 1/
[ 和 ]
定义字符类,字符类定义单个字符可能的匹配:
/[aeiou]/ 匹配所指定字符中的任意一个。
在字符类中,使用连字符 (-) 指定字符的范围:
/[A-Z0-9]/ 匹配从 A 到 Z 的大写字母或 0 到 9 的数字。
在字符类中,插入反斜杠对 ] 和- 字符进行转义:
/[+-]d+/ 匹配一个或多个数字前面的 + 或 -。
在字符类中,以下字符(通常为元字符)被看作一般字符(非元字符),不需要反斜杠:
/[$£]/ 匹配 $ 或 £。。
|(竖线)
用于逻辑"或"操作,匹配左侧或右侧的部分:
/abc|xyz/ 匹配 abc 或 xyz。










关于元序列
元序列是在正则表达式模式中具有特殊含义的字符序列。下表说明了这些元序列:
元序列
描述
{n}{n,}和
{n,n}
指定前一项目的数值数量或数量范围:
/A{27}/ 匹配重复 27 次的字符 A。
/A{3,}/ 匹配重复 3 次或更多次的字符 A。
/A{3,5}/ 匹配重复 3 到 5 次的字符 A。
b
匹配单词字符和非单词字符之间的位置。如果字符串中的第一个或最后一个字符是单词字符,则也匹配字符串的开头或结尾。
B
匹配两个单词字符之间的位置。也匹配两个非单词字符之间的位置。
d
匹配十进制数字。
D
匹配除数字以外的任何字符。
f
匹配换页符。
n
匹配换行符。
r
匹配回车符。
s
匹配任何空白字符(空格、制表符、换行符或回车符)。
S
匹配除空白字符以外的任何字符。
t
匹配制表符。
unnnn
匹配字符代码由十六进制数字 nnnn 指定的 Unicode 字符。例如,u263a 是一个笑脸字符。
v
匹配垂直换页符。
w
匹配单词字符(A-Z、a-z、0-9 或 _)。请注意,w 不匹配非英文字符,如 e、O 或 A。
W
匹配除单词字符以外的任何字符。
xnn
匹配具有指定 ASCII 值(由十六进制数字 nn 定义)的字符。
字符类
可以使用字符类指定字符列表以匹配正则表达式中的一个位置。使用方括号([ 和 ])定义字符类。
字符类中的转义序列
通常在正则表达式中具有特殊含义的大多数元字符和元序列在字符类中"不具有"那些特殊含义。
下表中列出的三个字符功能与元字符相同,在字符类中具有特殊含义:
元字符
在字符类中的含义
]
定义字符类的结尾。
-
定义字符范围(请参阅字符类中字符的范围)。

定义元序列并撤销元字符的特殊含义。
对于要识别为字面字符(无特殊元字符含义)的任何字符,必须在该字符前面加反斜杠转义字符。
除能够保持特殊含义的元字符外,下列元序列在字符类中也具有元序列功能:
元序列
在字符类中的含义
n
匹配换行符。
r
匹配回车符。
t
匹配制表符。
unnnn
匹配具有指定 Unicode 代码点值(由十六进制数字 nnnn 定义)的字符。
xnn
匹配具有指定 ASCII 值(由十六进制数字 nn 定义)的字符。
其它正则表达式元序列和元字符在字符类中看作普通字符。
字符类中字符的范围
使用连字符指定字符的范围,例如 A-Z、a-z 或 0-9。这些字符必须在字符类中构成有效的范围。您还可以使用 xnn ASCII 字符代码通过 ASCII 值指定范围。
反转的字符类
如果在字符类的开头使用尖号 (^) 字符,则将反转该集合的意义,即未列出的任何字符都认为匹配。必须在字符类的"开头"键入尖号 (^) 字符以表示反转。否则,您只是将尖号字符添加到字符类的字符中。


数量表示符
使用数量表示符指定字符或序列在模式中的重复次数,如下所示:
数量表示符元字符
描述
*(星号)
匹配前面重复零次或多次的项目。
+(加号)
匹配前面重复一次或多次的项目。
?(问号)
匹配前面重复零次或一次的项目。
{n}
{n,}

{n,n}
指定前一项目的数值数量或数量范围:
/A{27}/ 匹配重复 27 次的字符 A。
/A{3,}/ 匹配重复 3 次或更多次的字符 A。
/A{3,5}/ 匹配重复 3 到 5 次的字符 A。
您可以将数量表示符应用到单个字符、字符类或组。
您可以在应用数量表示符的括号组内使用数量表示符。
默认情况下,正则表达式执行所谓"无限匹配"。正则表达式中的任何子模式(如 .*)都会尝试在字符串中匹配尽可能多的字符,然后再执行正则表达式的下一部分。在所有数量表示符后添加问号 (?) 以将其更改为所谓"惰性数量表示符"。使用惰性数量表示符 ? 匹配后跟数量最少(惰性)的字符。
有关数量表示符,请牢记以下几点:
数量表示符 {0} 和 {0,0} 不会从匹配中排除项目。
不要结合使用多个数量表示符,例如 /abc+*/ 中。
除非设置 s (dotall) 标志,否则点 (.) 不能跨越多行,即使后跟 * 数量表示符。
逻辑"或"
在正则表达式中使用 |(竖线)字符可使正则表达式引擎考虑其它匹配。
您可以使用括号定义组以限制逻辑"或"字符 | 的范围。

您可以使用括号在正则表达式中指定组,组是模式的子部分。您可以使用组实现以下操作:
将数量表示符应用到多个字符。
界定要应用逻辑"或"(通过使用 | 字符)的子模式。
捕获正则表达式中的子字符串匹配(例如,在正则表达式中使用 1 以匹配先前匹配的组,或类似地在 String 类的 replace() 方法中使用 $1)。
使用组捕获子字符串匹配
如果您在模式中定义标准括号组,则之后可以在正则表达式中引用它。这称为"逆向引用",并且此类型的组称为"捕获组"。
您可以通过键入 1,2,...,99 在正则表达式中指定最多 99 个此类逆向引用。
类似地,在 String 类的 replace() 方法中,可以使用 $1-$99 在替换字符串中插入捕获的组子字符串匹配。此外,如果使用捕获组,RegExp 类的 exec() 方法和 String 类的 match() 方法将返回与捕获组匹配的子字符串。
使用非捕获组和向前查找组
非捕获组是只用于分组的组,它不会被"收集",也不会匹配有限的逆向引用。可以使用 (?: 和 ) 来定义非捕获组。
一类特殊的非捕获组是"向前查找组",它包括两种类型:"正向前查找组"和"负向前查找组"。
使用 (?= 和 ) 定义正向前查找组,它指定组中的子模式位置必须匹配。但是,匹配正向前查找组的字符串部分可能匹配正则表达式中的剩余模式。
使用 (?! 和 ) 定义负向前查找组,它指定该组中的子模式位置必须不匹配。
使用命名组
命名组是正则表达式中给定命名标识符的一类组。使用 (?P<name> 和 ) 可定义命名组。
命名组不属于 ECMAScript 语言规范。它们是 ActionScript 3.0 中的新增功能。



标志和属性
下表列出了可以为正则表达式设置的五种标志。每种标志都可以作为正则表达式对象属性进行访问。
标志
属性
描述
g
global
匹配多个匹配。
i
ignoreCase
不区分大小写的匹配。应用于 A-Z 和 a-z 字符,但不能应用于扩展字符,如 E 和 e。
m
multiline
设置此标志后,^和$可以分别匹配行的开头和结尾。
s
dotall
设置此标志后,.(点)可以匹配换行符 (n)。
x
extended
允许扩展的正则表达式。您可以在正则表达式中键入空格,它将作为模式的一部分被忽略。这可使您更加清晰可读地键入正则表达式代码。
请注意这些属性都是只读属性。但是,您无法直接设置命名属性。
默认情况下,除非您在正则表达式声明中指定这些标志,否则不会设置,并且相应的属性也会设置为 false。
另外,还有其它两种正则表达式属性:
lastIndex 属性指定字符串中的索引位置以用于下次调用正则表达式的 exec() 或 test() 方法。
source 属性指定定义正则表达式的模式部分的字符串。
在m (multiline) 标志中,请注意,只有 n 字符表示行的结束。下列字符不表示行的结束:
回车 (r) 字符
Unicode 行分隔符 (u2028) 字符
Unicode 段分隔符 (u2029) 字符
对字符串使用正则表达式的方法
RegExp 类包含两个方法:exec() 和 test()。
除 RegExp 类的 exec() 和 test() 方法外,String 类还包含以下方法,使您可以在字符串中匹配正则表达式:match()、replace()、search() 和 splice()。
处理 XML
ActionScript 3.0 包含一组基于 ECMAScript for XML (E4X) 规范(ECMA-357 第 2 版)的类。这些类包含用于处理 XML 数据的强大且易用的功能。
XML 基础知识
处理 XML 简介
XML 是一种表示结构化信息的标准方法,以使计算机能够方便地使用此类信息,并且人们可以非常方便地编写和理解这些信息。XML 是 eXtensible Markup Language(可扩展标记语言)的缩写。
XML 提供了一种简便的标准方法对数据进行分类,以使其更易于读取、访问以及处理。XML 使用类似于 HTML 的树结构和标签结构。XML 数据也可能会比较复杂,其中包含嵌套在其它标签中的标签以及属性和其它结构组件。
XML 快速入门
XML 数据是以纯文本格式编写的,并使用特定语法将信息组织为结构化格式。通常,将一组 XML 数据称为"XML 文档"。在 XML 格式中,可通过分层结构将数据组织到"元素"(可以是单个数据项,也可以是其它元素的容器)中。每个 XML 文档将一个元素作为顶级项目或主项目;此根元素内可能会包含一条信息,但更可能会包含其它元素,而这些元素又包含其它元素,依此类推。
每个元素都是用一组"标签"来区分的,即包含在尖括号(小于号和大于号)中的元素名称。开始标签(指示元素的开头)包含元素名称:<title>结束标签(标记元素的结尾)在元素名称前面包含一个正斜杠:</title>如果元素不包含任何内容,则会将其编写为一个空元素(有时称为自结束元素)。在 XML 中,以下元素:<lastplayed/>与下面的元素完全相同:<lastplayed></lastplayed>。
除了在开始和结束标签之间包含的元素内容外,元素还可以包含在元素开始标签中定义的其它值(称为"属性")。
每个 XML 元素都包含内容,这可以是单个值、一个或多个 XML 元素或没有任何内容(对于空元素)。
用于处理 XML 的 ActionScript 类
ActionScript 3.0 包含一些用于处理 XML 结构化信息的类。下面列出了两个主类:
XML:表示单个 XML 元素,它可以是包含多个子元素的 XML 文档,也可以是文档中的单值元素。
XMLList:表示一组 XML 元素。当具有多个"同级"(在 XML 文档分层结构中的相同级别,并且包含在相同父级中)的 XML 元素时,将使用 XMLList 对象。
对于涉及 XML 命名空间的更高级用法,ActionScript 还包含 Namespace 和 QName 类。
除了用于处理 XML 的内置类外,ActionScript 3.0 还包含一些运算符,它们提供了用于访问和处理 XML 数据的特定功能。这种使用这些类和运算符来处理 XML 的方法称为 ECMAScript for XML (E4X),它是由 ECMA-357 第 2 版规范定义的。
重要概念和术语
元素 (Element):XML 文档中的单个项目,它被标识为开始标签和结束标签之间包含的内容(包括标签)。XML 元素可以包含文本数据或其它元素,也可以为空。
空元素 (Empty element):不包含任何子元素的 XML 元素。通常,将空元素编写为自结束标签(如 <element/>)。
文档 (Document):单个 XML 结构。XML 文档可以包含任意数量的元素(或者仅包含单个空元素);但是,XML 文档必须具有一个顶级元素,该元素包含文档中的所有其它元素。
节点 (Node):XML 元素的另一种称谓。
属性 (Attribute):与元素关联的命名值,它以 attributename="value" 格式写入到元素的开始标签中,而不是编写为嵌套在元素内的单独子元素。
用于处理 XML 的 E4X 方法
ECMAScript for XML 规范定义了一组用于处理 XML 数据的类和功能。这些类和功能统称为 E4X。ActionScript 3.0 包含以下 E4X 类:XML、XMLList、QName 和 Namespace。
E4X 包含了一些直观运算符(如点 (.))和属性标识符 (@) 运算符,用于访问 XML 中的属性 (property) 和属性 (attribute)。使用 @ 和 . 运算符不仅可以读取数据,还可以分配数据。
XML 对象
XML 对象可能表示 XML 元素、属性、注释、处理指令或文本元素。
XML 对象分为包含"简单内容"和包含"复杂内容"两类。有子节点的 XML 对象归入包含复杂内容的一类。如果 XML 对象是属性、注释、处理指令或文本节点之中的任何一个,我们就说它包含简单内容。
XML 属性
XML 类有五个静态属性:
ignoreComments 和 ignoreProcessingInstructions 属性确定分析 XML 对象时是否忽略注释或处理指令。
ignoreWhitespace 属性确定在只由空白字符分隔的元素标签和内嵌表达式中是否忽略空白字符。
prettyIndent 和 prettyPrinting 属性用于设置由 XML 类的 toString() 和 toXMLString() 方法返回的文本的格式。
XML 方法
以下方法用于处理 XML 对象的分层结构:
appendChild()、 child()、 childIndex()、 children()、descendants()、elements()、 insertChildAfter()、
insertChildBefore()、 parent()、 prependChild()。
以下方法用于处理 XML 对象属性 (attribute):
attribute()、 attributes() 。
以下方法用于处理 XML 对象属性 (property):
hasOwnProperty()、 propertyIsEnumerable()、 replace()、 setChildren()。
以下方法用于处理限定名和命名空间:
addNamespace()、 inScopeNamespaces()、 localName()、 name()、 namespace()、 namespaceDeclarations()、
removeNamespace()、 setLocalName()、 setName()、 setNamespace()。
以下方法用于处理和确定某些类型的 XML 内容:
comments()、 hasComplexContent()、 hasSimpleContent()、 nodeKind()、 processingInstructions()、
text()。
以下方法用于转换为字符串和设置 XML 对象的格式:
defaultSettings()、 setSettings()、 settings()、 normalize()、 toString()、 toXMLString()。
还有其它几个方法:
contains()、 copy()、 valueOf()、 length()。
XMLList 对象
XMLList 实例表示 XML 对象的任意集合。它可以包含完整的 XML 文档、XML 片断或 XML 查询结果。
以下方法用于处理 XMLList 对象的分层结构:
child()、 children()、 descendants()、 elements()、 parent()。
以下方法用于处理 XMLList 对象属性 (attribute):
attribute()、 attributes()。
以下方法用于处理 XMLList 属性 (property):
hasOwnProperty()、 propertyIsEnumerable()。
以下方法用于处理和确定某些类型的 XML 内容:
comments()、 hasComplexContent()、 hasSimpleContent()、 processingInstructions()、 text()。
以下方法用于转换为字符串和设置 XMLList 对象的格式:
normalize()、 toString()、 toXMLString()。
还有其它几个方法:
contains()、 copy()、 length()、 valueOf()。
对于只包含一个 XML 元素的 XMLList 对象,可以使用 XML 类的所有属性和方法,因为包含一个 XML 元素的 XMLList 被视为等同于 XML 对象。
组合和变换 XML 对象
使用 prependChild() 方法或 appendChild() 方法可在 XML 对象属性列表的开头或结尾添加属性。
使用 insertChildBefore() 方法或 insertChildAfter() 方法在指定属性之前或之后添加属性。
可以使用 = 运算符将属性 (property) 和属性 (attribute) 赋予 XML 对象,
如下所示:
var x:XML =
<employee>
<lastname>Smith</lastname>
</employee>
x.firstname = "Jean";
x.@id = "239";
这将对 XML 对象 x 进行如下设置:
<employee id="239">
<lastname>Smith</lastname>
<firstname>Jean</firstname>
</employee>

可以使用 + 和 += 运算符连接 XMLList 对象。
遍历 XML 结构
XML 的一个强大功能是它能够通过文本字符的线性字符串提供复杂的嵌套数据。将数据加载到 XML 对象时,ActionScript 会分析数据并将其分层结构加载到内存(如果 XML 数据格式有误,它会发送运行时错误)。
利用 XML 和 XMLList 对象的运算符和方法可以轻松遍历 XML 数据的结构。
使用点 (.) 运算符和后代存取器 (..) 运算符可以访问 XML 对象的子属性。
访问父节点和子节点
parent() 方法返回 XML 对象的父项。
可以使用子级列表的序数索引值访问特定的子对象。要访问特定的孙项,可为子项和孙项名称同时指定索引编号。可以使用 child() 方法导航到名称基于变量或表达式的子项。
访问属性
使用 @ 符号(属性标识符运算符)可以访问 XML 或 XMLList 对象的属性,可以一起使用 * 通配符和 @ 符号来访问 XML 或 XMLList 对象的所有属性,可以使用 attribute() 或 attributes() 方法访问 XML 或 XMLList 对象的特定属性或所有属性。
按属性或元素值过滤
可以使用括号运算符 -- ( 和 ) -- 过滤具有特定元素名称或属性值的元素。
使用 for..in 和 for each..in 语句
ActionScript 3.0 包含用于循环访问 XMLList 对象的 for..in 语句和 for each..in 语句。
使用 XML 命名空间
XML 对象(或文档)中的命名空间用于标识对象所包含的数据的类型。
XML 类包含用于处理命名空间的以下方法:addNamespace()、inScopeNamespaces()、localName()、name()、namespace()、namespaceDeclarations()、removeNamespace()、setLocalName()、setName() 和 setNamespace()。
default xml namespace 指令用于为 XML 对象指定默认的命名空间。
XML 类型转换
可以将 XML 对象和 XMLList 对象转换为字符串值。同样,也可以将字符串转换为 XML 对象和 XMLList 对象。还要记住,所有 XML 属性值、名称和文本值都是字符串。
将 XML 和 XMLList 对象转换为字符串
XML 和 XMLList 类都包含一个 toString() 方法和一个 toXMLString() 方法。toXMLString() 方法返回包含该 XML 对象的所有标签、属性、命名空间声明和内容的字符串。对于包含复杂内容(子元素)的 XML 对象,toString() 方法的作用与 toXMLString() 方法完全相同。对于包含简单内容的 XML 对象(只包含一个文本元素的对象),toString() 方法只返回该元素的文本内容.
如果使用 trace() 方法但不指定 toString() 或 toXMLString(),则默认情况下将使用 toString() 方法转换数据. 使用 trace() 方法调试代码时,通常都要使用 toXMLString() 方法,以便 trace() 方法输出更完整的数据。
将字符串转换为 XML 对象
可以使用 new XML() 构造函数从字符串创建 XML 对象,如果试图将表示无效 XML 或格式有误的 XML 的字符串转换为 XML,则会引发运行时错误.
显示编程
ActionScript 3.0 中的显示编程用于处理出现在 Adobe Flash Player 9 的舞台上的元素。
显示编程的基础知识
显示编程简介
使用 ActionScript 3.0 构建的每个应用程序都有一个由显示对象构成的层次结构,这个结构称为"显示列表"。显示列表包含应用程序中的所有可视元素。显示元素属于下列一个或多个组:
舞台
舞台是包括显示对象的基础容器。每个应用程序都有一个 Stage 对象,其中包含所有的屏幕显示对象。舞台是顶级容器,它位于显示列表层次结构的顶部:

每个 SWF 文件都有一个关联的 ActionScript 类,称为"SWF 文件的主类"。当 Flash Player 在 HTML 页中打开 SWF 文件时,Flash Player 将调用该类的构造函数,所创建的实例(始终是一种显示对象)将添加为 Stage 对象的子级。SWF 文件的主类始终扩展 Sprite 类.
可以通过任何 DisplayObject 实例的 stage 属性来访问舞台。
显示对象
在 ActionScript 3.0 中,在应用程序屏幕上出现的所有元素都属于"显示对象"类型。flash.display 包中包括的 DisplayObject 类是由许多其它类扩展的基类。这些不同的类表示一些不同类型的显示对象,如矢量形状、影片剪辑和文本字段等。
显示对象容器
显示对象容器是一些特殊类型的显示对象,这些显示对象除了有自己的可视表示形式之外,还可以包含也是显示对象的子对象。
DisplayObjectContainer 类是 DisplayObject 类的子类。DisplayObjectContainer 对象可以在其"子级列表"中包含多个显示对象。在讨论显示对象的上下文中,DisplayObjectContainer 对象又称为"显示对象容器"或简称为"容器"。
尽管所有可视显示对象都从 DisplayObject 类继承,但每类显示对象都是 DisplayObject 类的一个特定子类。例如,有 Shape 类或 Video 类的构造函数,但没有 DisplayObject 类的构造函数。
如前所述,舞台是显示对象容器。
重要概念和术语
Alpha:表示颜色中的透明度(更准确地说,是不透明度)的颜色值。
位图图形 (Bitmap graphic):在计算机中定义为彩色像素网格(行和列)的图形。
混合模式 (Blending mode):指定两个重叠图像的内容应如何进行交互。通常,一个图像上面的另一个不透明图像会遮盖住下面的图像,因此根本看不到该图像;但是,不同的混合模式会导致图像颜色以不同方式混合在一起,因此,生成的内容是两个图像的某种组合形式。
显示列表 (Display list):由 Flash Player 呈现为可见屏幕内容的显示对象的层次结构。舞台是显示列表的根,附加到舞台或其子级之一上的所有显示对象构成了显示列表(即使并未实际呈现该对象,例如,如果它位于舞台边界以外)。
显示对象 (Display object):在 Flash Player 中表示某种类型的可视内容的对象。显示列表中只能包含显示对象,所有显示对象类是 DisplayObject 类的子类。
显示对象容器 (Display object container):一种特殊类型的显示对象,除了(通常)具有其自己的可视表示形式以外,它还可以包含子显示对象。
SWF 文件的主类 (Main class of the SWF file):为 SWF 文件中最外面的显示对象定义行为的类,从概念上讲,它是 SWF 文件本身的类。例如,在 Flash 创作环境中创建的 SWF 具有一个"主时间轴",它包含所有其它的时间轴;SWF 文件的主类是指将主时间轴作为其实例的类。
蒙版 (Masking):一种将图像的某些部分隐藏起来(或者相反,只允许显示图像的某些部分)的技术。图像的隐藏部分将变为透明,因此,将显示其下面的内容。
舞台 (Stage):一个可视容器,它是 SWF 文件中的所有可视内容的基础或背景。
变形 (Transformation):对图形的可视特性进行的调整.
矢量图形 (Vector graphic):在计算机中定义为使用特定特性(如粗细、长度、大小、角度以及位置)绘制的线条和形状的图形。
核心显示类
ActionScript 3.0 的 flash.display 包中包括可在 Flash Player 中显示的可视对象的类。下图说明了这些核心显示对象类的子类关系。

该图说明了显示对象类的类继承。请注意,其中某些类,尤其是 StaticText、TextField 和 Video 类,不在 flash.display 包中,但它们仍然是从 DisplayObject 类继承的。
扩展 DisplayObject 类的所有类都继承该类的方法和属性。
可以实例化包含在 flash.display 包中的下列类的对象:
Bitmap ─ 使用 Bitmap 类可定义从外部文件加载或通过 ActionScript 呈现的位图对象。可以通过 Loader 类从外部文件加载位图。可以加载 GIF、JPG 或 PNG 文件。还可以创建包含自定义数据的 BitmapData 对象,然后创建使用该数据的 Bitmap 对象。可以使用 BitmapData 类的方法来更改位图,无论这些位图是加载的还是在 ActionScript 中创建的。
Loader ─ 使用 Loader 类可加载外部资源(SWF 文件或图形)。
Shape ─ 使用 Shape 类可创建矢量图形,如矩形、直线、圆等。
SimpleButton ─ SimpleButton 对象是 Flash 按钮元件的 ActionScript 表示形式。SimpleButton 实例有 3 个按钮状态:弹起、按下和指针经过。
Sprite ─ Sprite 对象可以包含它自己的图形,还可以包含子显示对象。(Sprite 类用于扩展 DisplayObjectContainer 类)。
MovieClip ─ MovieClip 对象是在 Flash 创作工具中创建的 ActionScript 形式的影片剪辑元件。实际上,MovieClip 与 Sprite 对象类似,不同的是它还有一个时间轴。
下列类不在 flash.display 包中,这些类是 DisplayObject 类的子类:
TextField 类包括在 flash.text 包中,它是用于文本显示和输入的显示对象。
Video 类包括在 flash.media 包中,它是用于显示视频文件的显示对象。
flash.display 包中的下列类用于扩展 DisplayObject 类,但您不能创建这些类的实例。这些类而是用作其它显示对象的父类,因此可将通用功能合并到一个类中。
AVM1Movie ─ AVM1Movie 类用于表示在 ActionScript 1.0 和 2.0 中创作的已加载 SWF 文件。
DisplayObjectContainer ─ Loader、Stage、Sprite 和 MovieClip 类每个都用于扩展了 DisplayObjectContainer 类。
InteractiveObject ─ InteractiveObject 是用于与鼠标和键盘交互的所有对象的基类。SimpleButton、TextField、Video、Loader、Sprite、Stage 和 MovieClip 对象是 InteractiveObject 类的所有子类。
MorphShape ─ 这些对象是在 Flash 创作工具中创建补间形状时创建的。无法使用 ActionScript 实例化这些对象,但可以从显示列表中访问它们。
Stage ─ Stage 类用于扩展 DisplayObjectContainer 类。有一个应用程序的 Stage 实例,该实例位于显示列表层次结构的顶部。要访问 Stage,请使用任何 DisplayObject 实例的 stage 属性。
此外,flash.text 包中的 StaticText 类也用于扩展 DisplayObject 类,但不能在代码中创建它的实例。只能在 Adobe Flash CS3 Professional 中创建静态文本字段。
处理显示对象
DisplayObject 类的属性和方法
所有显示对象都是 DisplayObject 类的子类,同样它们还会继承 DisplayObject 类的属性和方法。继承的属性是适用于所有显示对象的基本属性。
您不能使用 DisplayObject 类构造函数来创建 DisplayObject 实例。必须创建另一种对象(属于 DisplayObject 类的子类的对象,如 Sprite)才能使用 new 运算符来实例化对象。此外,如果要创建自定义显示对象类,还必须创建具有可用构造函数的其中一个显示对象子类的子类(如 Shape 类或 Sprite 类)。
在显示列表中添加显示对象
实例化显示对象时,在将显示对象实例添加到显示列表上的显示对象容器之前,显示对象不会出现屏幕上(即在舞台上)。
当在舞台上添加任何可视元素时,该元素会成为 Stage 对象的"子级"。应用程序中加载的第一个 SWF 文件(例如,HTML 页中嵌入的文件)会自动添加为 Stage 的子级。它可以是扩展 Sprite 类的任何类型的对象。
"不是"使用 ActionScript 创建的任何显示对象(例如,通过在 Adobe Flex Builder 2 中添加 MXML 标签或在 Flash 的舞台上放置某项而创建任何对象),都会添加到显示列表中。尽管没有通过 ActionScript 添加这些显示对象,但仍可通过 ActionScript 访问它们。
处理显示对象容器
如果从显示列表中删除某个 DisplayObjectContainer 对象,或者以其它某种方式移动该对象或对其进行变形处理,则会同时删除、移动 DisplayObjectContainer 中的每个显示对象或对其进行变形处理。
显示对象容器本身就是一种显示对象,它可以添加到其它显示对象容器中。
要使某一显示对象出现在显示列表中,必须将该显示对象添加到显示列表上的显示对象容器中。使用容器对象的 addChild() 方法或 addChildAt() 方法可执行此操作。
使用 addChildAt() 方法可将子级添加到显示对象容器的子级列表中的特定位置。子级列表中这些从 0 开始的索引位置与显示对象的分层(从前到后顺序)有关。要重新将对象定位到显示列表的顶部,只需重新将其添加到列表中。
可以使用 getChildAt() 方法来验证显示对象的图层顺序。getChildAt() 方法根据您向容器传递的索引编号返回容器的子对象。
如果从父容器的子级列表中删除了一个显示对象,则列表中位置较高的每一个元素在子索引中会分别下移一个位置。removeChild() 和 removeChildAt() 方法并不完全删除显示对象实例。这两种方法只是从容器的子级列表中删除显示对象实例。该实例仍可由另一个变量引用。(请使用 delete 运算符完全删除对象。)
由于显示对象只有一个父容器,因此只能在一个显示对象容器中添加显示对象的实例。如果将在第一个显示对象容器中包含的某一显示对象添加到另一个显示对象容器中,则会从第一个显示对象容器的子级列表中删除该显示对象。
DisplayObjectContainer 类还定义了用于处理子显示对象的几个方法,其中包括:
contains():确定显示对象是否是 DisplayObjectContainer 的子级。
getChildByName():按名称检索显示对象。
getChildIndex():返回显示对象的索引位置。
setChildIndex():更改子显示对象的位置。
swapChildren():交换两个显示对象的前后顺序。
swapChildrenAt():交换两个显示对象的前后顺序(由其索引值指定)。
遍历显示列表
正如所看到的,显示列表是一个树结构。树的顶部是舞台,它可以包含多个显示对象。那些本身就是显示对象容器的显示对象可以包含其它显示对象或显示对象容器。


DisplayObjectContainer 类包括通过显示对象容器的子级列表遍历显示列表的属性和方法。getChildAt() 方法返回显示列表中特定索引位置的子级。您也可以按名称访问子对象。每个显示对象都有一个名称属性,如果没有指定该属性,Flash Player 会指定一个默认值,如 "instance1"。使用 getChildByName() 方法来访问具有名称属性的子显示对象。与使用 getChildAt() 方法相比,使用 getChildByName() 方法会导致性能降低。
由于显示对象容器可以包含其它显示对象容器作为其显示列表中的子对象,因此您可将应用程序的完整显示列表作为树来遍历。
设置舞台属性
Stage 类用于覆盖 DisplayObject 类的大多数属性和方法。如果调用其中一个已覆盖的属性或方法,Flash Player 会引发异常。
如果显示对象与加载的第一个 SWF 文件不在同一个安全沙箱中,则 Stage 类的某些属性和方法不适用于这些显示对象。
控制回放帧速率
Stage 类的 framerate 属性用于设置加载到应用程序中的所有 SWF 文件的帧速率。
控制舞台缩放比例
当调整 Flash Player 屏幕的大小时,Flash Player 会自动调整舞台内容来加以补偿。Stage 类的 scaleMode 属性可确定如何调整舞台内容。此属性可以设置为四个不同值。
对于scaleMode 的三个值(StageScaleMode.EXACT_FIT、StageScaleMode.SHOW_ALL 和 StageScaleMode.NO_BORDER),Flash Player 将缩放舞台的内容以容纳在舞台边界内。三个选项在确定如何完成缩放时是不相同的。
StageScaleMode.EXACT_FIT 按比例缩放 SWF。
StageScaleMode.SHOW_ALL 确定是否显示边框(就像在标准电视上观看宽屏电影时显示的黑条)。
StageScaleMode.NO_BORDER 确定是否可以部分裁切内容。
或者,如果将 scaleMode 设置为 StageScaleMode.NO_SCALE,则当查看者调整 Flash Player 窗口大小时,舞台内容将保持定义的大小。仅在缩放模式中,Stage 类的 width 和 height 属性才可用于确定 Flash Player 窗口调整大小后的实际像素尺寸。(在其它缩放模式中,stageWidth 和 stageHeight 属性始终反映的是 SWF 的原始宽度和高度。)此外,当 scaleMode 设置为 StageScaleMode.NO_SCALE 并且调整了 SWF 文件大小时,将调度 Stage 类的 resize 事件,允许您进行相应地调整。
因此,将 scaleMode 设置为 StageScaleMode.NO_SCALE 可以更好地控制如何根据需要调整屏幕内容以适合窗口大小。
处理全屏模式
使用全屏模式可令 SWF 填充查看器的整个显示器,没有任何边框、菜单栏等。Stage 类的 displayState 属性用于切换 SWF 的全屏模式。可以将 displayState 属性设置为由 flash.display.StageDisplayState 类中的常量定义的其中一个值。要打开全屏模式,请将 displayState 设置为 StageDisplayState.FULL_SCREEN。
要退出全屏模式,请将 displayState 属性设置为 StageDisplayState.NORMAL。
此外,用户可以通过将焦点切换到其它窗口或使用以下组合键之一来选择退出全屏模式:Esc(所有平台)、Ctrl-W (Windows)、Command-W (Mac) 或 Alt-F4 (Windows)。
全屏模式的舞台缩放行为与正常模式下的相同;缩放比例由 Stage 类的 scaleMode 属性控制。通常,如果将 scaleMode 属性设置为 StageScaleMode.NO_SCALE,则 Stage 的 stageWidth 和 stageHeight 属性将发生更改,以反映由 SWF 占用的屏幕区域的大小(在本例中为整个屏幕)。
打开或关闭全屏模式时,可以使用 Stage 类的 fullScreen 事件来检测和响应。fullScreen 事件的事件对象是 flash.events.FullScreenEvent 类的实例,它包含指示是启用 (true) 还是禁用 (false) 全屏模式的 fullScreen 属性。
在 ActionScript 中处理全屏模式时,需要记住以下注意事项:
只能通过 ActionScript 响应鼠标单击(包括右键单击)或按键才能启动全屏模式。
对于有多个显示器的用户,SWF 内容将展开且只填充一个显示器。Flash Player 使用度量信息来确定哪
个显示器包含 SWF 的最大部分内容,然后使用该显示器提供全屏模式。
对于 HTML 页中嵌入的 SWF 文件,嵌入 Flash Player 的 HTML 代码必须包括名为 allowFullScreen 且值为 true 的 param 标签和 embed 属性,如下所示:
<object>
...
<param name="allowFullScreen" value="true" />
<embed ... allowfullscreen="true" />
</object>
如果要在网页中使用 JavaScript 来生成 SWF 嵌入标签,则必须更改 JavaScript 以添加 allowFullScreen param 标签和属性。例如,如果 HTML 页使用 AC_FL_RunContent() 函数(由 Flex Builder 和 Flash 生成的 HTML 页使用),则应在该函数调用中添加 allowFullScreen 参数,如下所示:
AC_FL_RunContent(
...
'allowFullScreen','true',
...
); //end AC code
这不适用于在独立 Flash Player 中运行的 SWF 文件。
在全屏模式下将禁用所有与键盘有关的 ActionScript,如 TextField 实例中的键盘事件和文本输入。用
于关闭全屏模式的键盘快捷键除外。
处理显示对象的事件
DisplayObject 类从 EventDispatcher 类继承。这意味着,每个显示对象都可完全参与到事件模型中。每个显示对象可使用其 addEventListener() 方法(继承自 EventDispatcher 类)来侦听特定的事件,但只有侦听对象是该事件的事件流的一部分时才能实现。
当 Flash Player 调度某个事件对象时,该事件对象会执行从舞台到发生事件的显示对象的往返行程。
处理显示对象事件时需要记住的一个重要问题是:从显示列表中删除显示对象时,事件侦听器的存在将会对是否从内存中自动删除显示对象(垃圾回收)产生影响。如果显示对象拥有订阅为其事件的侦听器的对象,即使从显示列表中删除了显示对象,也不会从内存中删除显示对象,因为显示对象仍然拥有到这些侦听器对象的引用。
选择 DisplayObject 子类
如果有多个可供选择的选项,处理显示对象时要作出的一个重要决策是:每个显示对象的用途是什么。以下原则可以帮助您作出决策。无论是需要类实例,还是选择要创建的类的基类,都可以应用这些建议:
如果不需要可作为其它显示对象的容器的对象(即只需要用作独立屏幕元素的对象),请根据使用目的选择 DisplayObject 或 InteractiveObject 两个子类中的一个:
用于显示位图图像的 Bitmap。
用于添加文本的 TextField。
用于显示视频的 Video。
用于绘制屏幕内容的"画布"的 Shape。特别是,如果要创建用于在屏幕上绘制形状的实例,而且该实例不是其它显示对象的容器,则使用 Shape 比使用 Sprite 或 MovieClip 有明显的性能优势。
用于 Flash 具体创作项的 MorphShape、StaticText 或 SimpleButton。(无法以编程方式创建这些类的实例,但可以通过创建这些数据类型的变量来引用使用 Flash 创作程序创建的项目。)
如果需要使用变量来引用主舞台,请使用 Stage 类作为其数据类型。
如果需要容器来加载外部 SWF 文件或图像文件,请使用 Loader 实例。加载的内容将作为 Loader 实例的子级添加到显示列表中。其数据类型将取决于加载内容的性质,如下所示:
加载的图像将是 Bitmap 实例。
使用 ActionScript 3.0 编写的已加载 SWF 文件将是 Sprite 或 MovieClip 实例(或这些类的子类的实例,由内容创建者指定)。
使用 ActionScript 1.0 或 ActionScript 2.0 编写的已加载 SWF 文件将是 AVM1Movie 实例。
如果需要将一个对象用作其它显示对象的容器(无论是否还要使用 ActionScript 在显示对象上进行绘制),请选择其中一个 DisplayObjectContainer 子类:
如果对象是只使用 ActionScript 创建的,或者如果对象作为只使用 ActionScript 创建和处理的自定义显示对象的基类,请选择 Sprite。
如果要通过创建变量来引用在 Flash 创作工具中创建的影片剪辑元件,请选择 MovieClip。
如果要创建的类与 Flash 库中的影片剪辑元件关联,请选择其中一个 DisplayObjectContainer 子类作为该类的基类:
如果关联的影片剪辑元件在多个帧上有内容,请选择 MovieClip
如果关联的影片剪辑元件仅在第一帧上有内容,请选择 Sprite
处理显示对象
改变位置
对任何显示对象进行的最基本操作是确定显示对象在屏幕上的位置。要设置显示对象的位置,请更改对象的 x 和 y 属性。
显示对象定位系统将舞台视为一个笛卡尔坐标系(带有水平 x 轴和垂直 y 轴的常见网格系统)。坐标系的原点(x 和 y 轴相交的 0,0 坐标)位于舞台的左上角。从原点开始,x 轴的值向右为正,向左为负,而 y 轴的值向下为正,向上为负(与典型的图形系统相反)。
默认情况下,当使用 ActionScript 创建显示对象时,x 和 y 属性均设置为 0,从而可将对象放在其父内容的左上角。
改变相对于舞台的位置
一定要记住 x 和 y 属性始终是指显示对象相对于其父显示对象坐标轴的 0,0 坐标的位置,这一点很重要。因此,对于包含在 Sprite 实例内的 Shape 实例(如圆),如果将 Shape 对象的 x 和 y 属性设置为 0,则会将圆放在 Sprite 的左上角,该位置不一定是舞台的左上角。要确定对象相对于全局舞台坐标的位置,可以使用任何显示对象的 globalToLocal() 方法将坐标从局部(舞台)坐标转换为本地(显示对象容器)坐标。同样,可以使用 DisplayObject 类的 localToGlobal() 方法将本地坐标转换为舞台坐标。
创建拖放交互组件
移动显示对象的一个常见理由是要创建拖放交互组件,这样当用户单击某个对象时,在松开鼠标按键之前,该对象会随着鼠标的移动而移动。在 ActionScript 中可以采用两种方法创建拖放交互组件。在每种情况下,都会使用两个鼠标事件:按下鼠标按键时,通知对象跟随鼠标光标;松开鼠标按键时,通知对象停止跟随鼠标光标。
第一方法使用 startDrag() 方法,它比较简单,但限制较多。按下鼠标按键时,将调用要拖动的显示对象的 startDrag() 方法。松开鼠标按键时,将调用 stopDrag() 方法。
这种方法有一个非常大的限制:每次只能使用 startDrag() 拖动一个项目。如果正在拖动一个显示对象,然后对另一个显示对象调用了 startDrag() 方法,则第一个显示对象会立即停止跟随鼠标。
由于每次只能使用 startDrag() 拖动一个对象,因此,可以对任何显示对象调用 stopDrag() 方法,这会停止当前正在拖动的任何对象。
如果需要拖动多个显示对象,或者为了避免多个对象可能会使用 startDrag() 而发生冲突,最好使用鼠标跟随方法来创建拖动效果。通过这种技术,当按下鼠标按键时,会将函数作为舞台的 mouseMove 事件的侦听器来订阅。然后,每次鼠标移动时都会调用此函数,它将使所拖动的对象跳到鼠标所在的 x,y 坐标。松开鼠标按键后,取消此函数作为侦听器的订阅,这意味着鼠标移动时不再调用该函数且对象停止跟随鼠标光标。
除了使显示对象跟随鼠标光标之外,拖放交互组件的共有部分还包括将拖动对象移到显示对象的前面,以使拖动对象好像浮动在所有其它对象之上。
最后,要增强效果,您可以在单击显示对象时(开始拖动显示对象时)对显示对象应用投影滤镜,然后在松开对象时删除投影。
平移和滚动显示对象
如果显示对象太大,不能在要显示它的区域中完全显示出来,则可以使用 scrollRect 属性定义显示对象的可查看区域。此外,通过更改 scrollRect 属性响应用户输入,可以使内容左右平移或上下滚动。
scrollRect 属性是 Rectangle 类的实例,Rectangle 类包括将矩形区域定义为单个对象所需的有关值。最初定义显示对象的可查看区域时,请创建一个新的 Rectangle 实例并为该实例分配显示对象的 scrollRect 属性。以后进行滚动或平移时,可以将 scrollRect 属性读入单独的 Rectangle 变量,然后更改所需的属性(例如,更改 Rectangle 实例的 x 属性进行平移,或更改 y 属性进行滚动)。然后将该 Rectangle 实例重新分配给 scrollRect 属性,将更改的值通知显示对象。
使用显示对象的 scrollRect 属性时,最好指定 Flash Player 应使用 cacheAsBitmap 属性将显示对象的内容作为位图来缓存。如果这样做了,每次滚动显示对象时,Flash Player 就不必重绘显示对象的整个内容,而可以改为使用缓存的位图将所需部分直接呈现到屏幕上。
处理大小和缩放对象
您可以采用两种方法来测量和处理显示对象的大小:使用尺寸属性(width 和 height)或缩放属性(scaleX 和 scaleY)。
每个显示对象都有 width 属性和 height 属性,它们最初设置为对象的大小,以像素为单位。您可以通过读取这些属性的值来确定显示对象的大小。还可以指定新值来更改对象的大小。
更改显示对象的 height 或 width 会导致缩放对象,这意味着对象内容经过伸展或挤压以适合新区域的大小。如果显示对象仅包含矢量形状,将按新缩放比例重绘这些形状,而品质不变。此时将缩放显示对象中的所有位图图形元素,而不是重绘。
当更改显示对象的 width 或 height 属性时,Flash Player 还会更新对象的 scaleX 和 scaleY 属性。这些属性表示显示对象与其原始大小相比的相对大小。scaleX 和 scaleY 属性使用小数(十进制)值来表示百分比。
如果更改一个正方形的 height 但不更改其 width,则其边长不再相同,它将是一个矩形而不是一个正方形。如果要更改显示对象的相对大小,则可以通过设置 scaleX 和 scaleY 属性的值来调整该对象的大小,另一种方法是设置 width 或 height 属性。
控制缩放时的扭曲
使用 9 切片缩放 (Scale-9) 来创建在其中控制如何缩放对象的显示对象。使用 9 切片缩放时,显示对象被分成 9 个单独的矩形(一个 3 x 3 的网格,就像一个“井”字)。矩形的大小不必一定相同,您可以指定放置网格线的位置。缩放显示对象时,四个角矩形中的任何内容(如按钮的圆角)不伸展也不压缩。上中矩形和下中矩形将进行水平缩放,但不进行垂直缩放,而左中矩形和右中矩形将进行垂直缩放,但不进行水平缩放。中心矩形既进行水平缩放又进行垂直缩放。


请记住,如果要创建显示对象且希望某些内容从不缩放,只需要通过放置 9 切片缩放网格的划分线来确保有关内容完全放在其中一个角矩形中即可。
在 ActionScript 中,如果为显示对象的 scale9Grid 属性设置一个值,就会打开对象的 9 切片缩放并定义对象的 Scale-9 网格中矩形的大小。可以使用 Rectangle 类的实例作为 scale9Grid 属性的值,如下所示:
myButton.scale9Grid = new Rectangle(32, 27, 71, 64);
Rectangle 构造函数的四个参数是 x 坐标、y 坐标、width 和 height。本示例中,矩形的左上角放在名为 myButton 的显示对象上的点 x:32,y:27 处。矩形宽 71 个像素,高 65 个像素(因此其右边位于显示对象上 x 坐标为 103 的位置,其下边位于显示对象上 y 坐标为 92 的位置)。


包含在由 Rectangle 实例定义的区域中的实际区域表示 Scale-9 网格的中心矩形。其它矩形是由 Flash Player 通过扩展 Rectangle 实例的各边计算出来的,如下所示:


在本例中,当按钮放大或缩小时,圆角不拉伸也不压缩,但其它区域将通过调整来适应缩放。


缓存显示对象
可以通过缓存指定的显示对象来提高 SWF 文件的性能。显示对象是一个"表面",实际上是位图版本的实例矢量数据,矢量数据是 SWF 文件中不需要有太多更改的一种数据。因此,打开缓存的实例不会随 SWF 文件的播放而不断地重绘,这样便可快速呈现 SWF 文件。
可以更新矢量数据,这时将重新创建表面。因此,缓存在表面中的矢量数据不需要在整个 SWF 文件中保持一样。
将显示对象的 cacheAsBitmap 属性设置为 true 会使显示对象缓存其自身的位图表示。Flash 为该实例创建一个 surface 对象,该对象是一个缓存的位图,而不是矢量数据。如果要更改显示对象的边界,则重新创建表面而不是调整其大小。表面可以嵌套在其它表面之内。子表面会将其位图复制到它的父表面上。
DisplayObject 类的 opaqueBackground 属性和 scrollRect 属性与使用 cacheAsBitmap 属性的位图缓存有关。尽管这三个属性彼此互相独立,但是,当对象缓存为位图时,opaqueBackground 和 scrollRect 属性的作用最佳,只有将 cacheAsBitmap 设置为 true 时,才能看到 opaqueBackground 和 scrollRect 属性带来的性能优势。
何时启用缓存
对显示对象启用缓存可创建表面,表面具有助于更快地呈现复杂的矢量动画等优点。
缓存数据的总体性能取决于实例矢量数据的复杂程度、要更改的数据量,以及是否设置了 opaqueBackground 属性。
何时使用位图缓存
在以下典型情况下,启用位图缓存可能会带来明显的好处。
复杂的背景图像:应用程序包含由矢量数据组成的细节丰富且背景复杂的图像。您可能会在背景上设计动画人物,这会降低动画的速度,因为背景需要持续地重新生成矢量数据。要提高性能,可以将背景显示对象的 opaqueBackground 属性设置为 true。背景将呈现为位图,可以迅速地重绘,所以动画的播放速度比较快。
滚动文本字段:应用程序在滚动文本字段中显示大量的文本。可以将文本字段放置在您设置为可滚动的具有滚动框(使用 scrollRect 属性)的显示对象中。这可以使指定的实例进行快速像素滚动。当用户滚动显示对象实例时,Flash 通过将滚动的像素向上移来生成新的看得见的区域,而不是重新生成整个文本字段。
窗口排列秩序:应用程序具有秩序复杂的重叠窗口。每个窗口都可以打开或关闭。如果将每个窗口标记为一个表面(将 cacheAsBitmap 属性设置为 true),则各个窗口将隔离开来进行缓存。用户可以拖动窗口使其互相重叠,每个窗口并不重新生成矢量内容。
Alpha 通道遮罩:当使用 Alpha 通道遮罩时,必须将 cacheAsBitmap 属性设置为 true。
所有这些情况下,启用位图缓存后都通过优化矢量图来提高应用程序的响应能力和互动性。
此外,只要对显示对象应用滤镜,Flash Player 就会将 cacheAsBitmap 自动设置为 true,即使已明确将其设置为 false 也是如此。如果清除了显示对象的所有滤镜,则 cacheAsBitmap 属性会返回最后设置的值。
何时避免使用位图缓存
滥用此功能对 SWF 文件可能会有负面影响。使用位图缓存时,请记住下面的准则:
不要过度使用表面(启用了缓存的显示对象)。每个表面使用的内存都比常规显示对象多,这意味着只在需要提高呈现性能时才启用表面。
避免放大缓存的表面。如果过度使用位图缓存,尤其是放大内容时,将使用大量的内存。
将表面用于通常为静态(非动画)的显示对象实例。可以拖动或移动实例,但实例内容不应为动画或者有太多的变化。
如果将表面和矢量数据混在一起,则会增加 Flash Player(有时还有计算机)需要处理的工作量。尽可能将表面归为一组。
启用位图缓存
要为显示对象启用位图缓存,请将它的 cacheAsBitmap 属性设置为 true:
mySprite.cacheAsBitmap = true;
将 cacheAsBitmap 属性设置为 true 后,您可能会注意到,显示对象的像素会自动与整个坐标对齐。测试 SWF 文件时,您还会注意到,在复杂矢量图像上执行的任何动画的呈现速度都快得多。
即便将 cacheAsBitmap 已设置为 true,如果出现以下一种或多种情况,也不会创建表面(缓存的位图):
位图高度或宽度超过 2880 个像素;位图分配不成功(由于内存不足而出现的错误)。
设置不透明背景颜色
可以为显示对象设置不透明背景。例如,如果 SWF 的背景中包含复杂的矢量图片,则可以将 opaqueBackground 属性设置为指定的颜色(通常与舞台颜色相同)。将颜色指定为一个数字(通常为十六进制的颜色值)。然后将背景视为一个位图,这有助于优化性能。
当将 cacheAsBitmap 设置为 true 并将 opaqueBackground 属性设置为指定的颜色时,opaqueBackground 属性可以使内部位图不透明而加快呈现速度。如果不将 cacheAsBitmap 设置为 true,opaqueBackground 属性将在显示对象的背景中添加一个不透明的矢量正方形形状。它不会自动创建位图。
应用混合模式
混合模式涉及将一个图像(基图像)的颜色与另一个图像(混合图像)的颜色进行组合来生成第三个图像,结果图像是实际在屏幕上显示的图像。图像中的每个像素值都与另一个图像的对应像素值一起处理的,以便为结果图像中的相同位置生成像素值。
每个显示对象都有 blendMode 属性,可以将其设置为下列混合模式之一。以下是在 BlendMode 类中定义的常量。此外,还可以使用 String 值(在括号中),这些值是常量的实际值。
BlendMode.ADD ("add"):通常用于创建两个图像之间的动画变亮模糊效果。
BlendMode.ALPHA ("alpha"):通常用于在背景上应用前景的透明度。
BlendMode.DARKEN ("darken"):通常用于重叠类型。
BlendMode.DIFFERENCE ("difference"):通常用于创建更多变动的颜色。
BlendMode.ERASE ("erase"):通常用于使用前景 Alpha 剪掉(擦除)背景的一部分。
BlendMode.HARDLIGHT ("hardlight"):通常用于创建阴影效果。
BlendMode.INVERT ("invert"):用于反转背景。
BlendMode.LAYER ("layer"):用于强制为特定显示对象的预构成创建临时缓冲区。
BlendMode.LIGHTEN ("lighten"):通常用于重叠类型。
BlendMode.MULTIPLY ("multiply"):通常用于创建阴影和深度效果。
BlendMode.NORMAL ("normal"):用于指定混合图像的像素值覆盖基本图像的像素值。
BlendMode.OVERLAY ("overlay"):通常用于创建阴影效果。
BlendMode.SCREEN ("screen"):通常用于创建亮点和镜头眩光。
BlendMode.SUBTRACT ("subtract"):通常用于创建两个图像之间的动画变暗模糊效果。
调整 DisplayObject 颜色
可以使用 ColorTransform 类的方法 (flash.geom.ColorTransform) 来调整显示对象的颜色。每个显示对象都有 transform 属性(它是 Transform 类的实例),还包含有关应用到显示对象的各种变形的信息。除了有关几何变形的信息之外,Transform 类还包括 colorTransform 属性,它是 ColorTransform 类的实例,并提供访问来对显示对象进行颜色调整。要访问显示对象的颜色转换信息,可以使用如下代码:
var colorInfo:ColorTransform = myDisplayObject.transform.colorTransform;
创建 ColorTransform 实例后,可以通过读取其属性值来查明已应用了哪些颜色转换,也可以通过设置这些值来更改显示对象的颜色。要在进行任何更改后更新显示对象,必须将 ColorTransform 实例重新分配给 transform.colorTransform 属性。
使用代码设置颜色值
ColorTransform 类的 color 属性可用于为显示对象分配具体的红、绿、蓝 (RGB) 颜色值。请注意,使用 color 属性更改显示对象的颜色时,将会完全更改整个对象的颜色,无论该对象以前是否有多种颜色。
使用代码更改颜色和亮度效果
假设显示对象有多种颜色(例如,数码照片),但是您不想完全重新调整对象的颜色,只想根据现有颜色来调整显示对象的颜色。这种情况下,ColorTransform 类包括一组可用于进行此类调整的乘数属性和偏移属性。乘数属性的名分别为 redMultiplier、greenMultiplier、blueMultiplier 和 alphaMultiplier,它们的作用像彩色照片滤镜(或彩色太阳镜)一样,可以增强或削弱显示对象上的某些颜色。偏移属性(redOffset、greenOffset、blueOffset 和 alphaOffset)可用于额外增加对象上某种颜色的值,或用于指定特定颜色可以具有的最小值。
在"属性"检查器上的"颜色"弹出菜单中选择"高级"时,这些乘数和偏移属性与 Flash 创作工具中影片剪辑元件可用的高级颜色设置相同。
旋转对象
使用 rotation 属性可以旋转显示对象。可以通过读取此值来了解是否旋转了某个对象,如果要旋转该对象,可以将此属性设置为一个数字(以度为单位),表示要应用于该对象的旋转量。
或者,还可以使用转换矩阵来旋转显示对象。
淡化对象
可以通过控制显示对象的透明度来使显示对象部分透明(或完全透明),也可以通过更改透明度来使对象淡入或淡出。DisplayObject 类的 alpha 属性用于定义显示对象的透明度(更确切地说是不透明度)。可以将 alpha 属性设置为介于 0 和 1 之间的任何值,其中 0 表示完全透明,1 表示完全不透明。
还可以使用通过 ColorTransform 类提供的颜色调整来更改显示对象的透明度。
遮罩显示对象
可以通过将一个显示对象用作遮罩来创建一个孔洞,透过该孔洞使另一个显示对象的内容可见。
定义遮罩
要指明一个显示对象将是另一个显示对象的遮罩,请将遮罩对象设置为被遮罩的显示对象的 mask 属性。
被遮罩的显示对象显示在用作遮罩的显示对象的全部不透明区域之内。
用作遮罩的显示对象可拖动、设置动画,并可动态调整大小,可以在单个遮罩内使用单独的形状。遮罩显示对象不必一定需要添加到显示列表中。但是,如果希望在缩放舞台时也缩放遮罩对象,或者如果希望支持用户与遮罩对象的交互(如用户控制的拖动和调整大小),则必须将遮罩对象添加到显示列表中。遮罩对象已添加到显示列表时,显示对象的实际 z 索引(从前到后顺序)并不重要。(除了显示为遮罩对象外,遮罩对象将不会出现在屏幕上。)如果遮罩对象是包含多个帧的一个 MovieClip 实例,则遮罩对象会沿其时间轴播放所有帧,如果没有用作遮罩对象,也会出现同样的情况。通过将 mask 属性设置为 null 可以删除遮罩。
不能使用一个遮罩对象来遮罩另一个遮罩对象。不能设置遮罩显示对象的 alpha 属性。只有填充可用于作为遮罩的显示对象中;笔触都会被忽略。
关于遮蔽设备字体
您可以使用显示对象遮罩用设备字体设置的文本。当使用显示对象遮罩用设备字体设置的文本时,遮罩的矩形边框会用作遮罩形状。也就是说,如果为设备字体文本创建了非矩形的显示对象遮罩,则 SWF 文件中显示的遮罩将是遮罩的矩形边框的形状,而不是遮罩本身的形状。
Alpha 通道遮罩
如果遮罩显示对象和被遮罩的显示对象都使用位图缓存,则支持 Alpha 通道遮罩。
对象动画
动画是使内容移动或者使内容随时间发生变化的过程。脚本动画是视频游戏的基础部分,通常用于将优美、有用的交互线索添加到其它应用程序中。
脚本动画的基本概念是变化一定要发生,而且变化一定要分时间逐步完成。使用常见的循环语句,可很容易在 ActionScript 中使内容重复。但是,在更新显示之前,循环将遍历其所有迭代。要创建脚本动画,需要编写 ActionScript,它随时间重复执行某个动作,每次运行时还更新屏幕。
例如,假设要创建一个简单的动画,如使球沿着屏幕运动。ActionScript 包括一个允许您跟踪时间和相应更新屏幕的简单机制,这意味着您可以编写代码,每次让球移动一点点,直到球到达目标为止。每次移动后,屏幕都会更新,从而使跨舞台的运动在查看器中可见。
从实际观点来看,让脚本动画与 SWF 文件的帧速率同步(换句话说,每次新帧显示时都设计一个动画变化)才有意义,因为这是 Flash Player 更新屏幕的速度。每个显示对象都有 enterFrame 事件,它根据 SWF 文件的帧速率来调度,即每帧一个事件。创建脚本动画的大多数开发人员都使用 enterFrame 事件作为一种方法来创建随时间重复的动作。可以编写代码以侦听 enterFrame 事件,每一帧都让动画球移动一定的量,当屏幕更新时(每一帧),将会在新位置重新绘制该球,从而产生了运动。
另一种随时间重复执行某个动作的方法是使用 Timer 类。每次过了指定的时间时,Timer 实例都会触发事件通知。可以编写通过处理 Timer 类的 timer 事件来执行动画的代码,将时间间隔设置为一个很小的间隔(几分之几秒)。
动态加载显示内容
可以将下列任何外部显示资源加载到 ActionScript 3.0 应用程序中:
在 ActionScript 3.0 中创作的 SWF 文件 ─ 此文件可以是 Sprite、MovieClip 或扩展 Sprite 的任何类。
图像文件 ─ 包括 JPG、PNG 和 GIF 文件。
AVM1 SWF 文件 ─ 在 ActionScript 1.0 或 2.0 中编写的 SWF 文件。
使用 Loader 类可以加载这些资源。
加载显示对象
Loader 对象用于将 SWF 文件和图形文件加载到应用程序中。Loader 类是 DisplayObjectContainer 类的子类。Loader 对象在其显示列表中只能包含一个子显示对象,该显示对象表示它加载的 SWF 或图形文件。加载 SWF 文件或图像后,即可将加载的显示对象移到另一个显示对象容器中。
监视加载进度
文件开始加载后,就创建了 LoaderInfo 对象。LoaderInfo 对象用于提供加载进度、加载者和被加载者的 URL、媒体的字节总数及媒体的标称高度和宽度等信息。LoaderInfo 对象还调度用于监视加载进度的事件。
下图说明了 LoaderInfo 对象的不同用途 ─ 用于 SWF 文件的主类的实例、用于 Loader 对象以及用于由 Loader 对象加载的对象:

可以将 LoaderInfo 对象作为 Loader 对象和加载的显示对象的属性进行访问。加载一开始,就可以通过 Loader 对象的 contentLoaderInfo 属性访问 LoaderInfo 对象。显示对象完成加载后,也可以将 LoaderInfo 对象作为加载的显示对象的属性通过显示对象的 loaderInfo 属性进行访问。已加载显示对象的 loaderInfo 属性是指与 Loader 对象的 contentLoaderInfo 属性相同的 LoaderInfo 对象。换句话说,LoaderInfo 对象是加载的对象与加载它的 Loader 对象之间(加载者和被加载者之间)的共享对象。
指定加载上下文
通过 Loader 类的 load() 或 loadBytes() 方法将外部文件加载到 Flash Player 中时,可以选择性地指定 context 参数。此参数是一个 LoaderContext 对象。
LoaderContext 类包括三个属性,用于定义如何使用加载的内容的上下文:
checkPolicyFile:仅当加载图像文件(不是 SWF 文件)时才会使用此属性。如果将此属性设置为 true,Loader 将检查跨域策略文件的原始服务器。只有内容的来源域不是包含 Loader 对象的 SWF 文件所在的域时才需要此属性。如果服务器授予 Loader 域权限,Loader 域中 SWF 文件的 ActionScript 就可以访问加载图像中的数据;换句话说,可以使用 BitmapData.draw() 命令访问加载的图像中的数据。
请注意,来自 Loader 对象所在域以外的其它域的 SWF 文件可以通过调用 Security.allowDomain() 来允许特定的域。
securityDomain:仅当加载 SWF 文件(不是图像)时才会使用此属性。如果 SWF 文件所在的域与包含 Loader 对象的文件所在的域不同,则指定此属性。指定此选项时,Flash Player 将检查跨域策略文件是否存在,如果存在,来自跨策略文件中允许的域的 SWF 文件可以对加载的 SWF 内容执行跨脚本操作。可以将 flash.system.SecurityDomain.currentDomain 指定为此参数。
applicationDomain:仅当加载使用 ActionScript 3.0 编写的 SWF 文件(不是图像或使用 ActionScript 1.0 或 2.0 编写的 SWF 文件)时才会使用此属性。加载文件时,通过将 applicationDomain 参数设置为 flash.system.ApplicationDomain.currentDomain,可以指定将该文件包括在与 Loader 对象相同的应用程序域中。通过将加载的 SWF 文件放在同一个应用程序域中,可以直接访问它的类。如果要加载的 SWF 文件中包含嵌入的媒体,这会很有帮助,您可以通过其关联的类名访问嵌入的媒体。
处理几何结构
flash.geom 包中包含用于定义几何对象(如,点、矩形和转换矩阵)的类。
几何学基础知识
处理几何学简介
flash.geom 包中包含用于定义几何对象(如,点、矩形和转换矩阵)的类。这些类本身并不一定提供功能,但它们用于定义在其它类中使用的对象的属性。
所有几何类都基于以下概念:将屏幕上的位置表示为二维平面。可以将屏幕看作是具有水平 (x) 轴和垂直 (y) 轴的平面图形。屏幕上的任何位置(或"点")可以表示为 x 和 y 值对,即该位置的"坐标"。
每个显示对象(包括舞台)具有其自己的"坐标空间";实质上,这是其用于标绘子显示对象、图画等位置的图形。通常,"原点"(x 和 y 轴相交的位置,其坐标为 0, 0)位于显示对象的左上角。尽管这始终适用于舞台,但并不一定适用于任何其它显示对象。正如在标准二维坐标系中一样,x 轴上的值越往右越大,越往左越小;对于原点左侧的位置,x 坐标为负值。但是,与传统的坐标系相反,在 ActionScript 中,屏幕 y 轴上的值越往下越大,越往上越小(原点上面的 y 坐标为负值)。由于舞台左上角是其坐标空间的原点,因此,舞台上的任何对象的 x 坐标大于 0 并小于舞台宽度,y 坐标大于 0 并小于舞台高度。
可以使用 Point 类实例来表示坐标空间中的各个点。您可以创建一个 Rectangle 实例来表示坐标空间中的矩形区域。对于高级用户,可以使用 Matrix 实例将多个或复杂变形应用于显示对象。通过使用显示对象的属性,可以将很多简单变形(如旋转、位置以及缩放变化)直接应用于该对象。
重要概念和术语
笛卡尔坐标 (Cartesian coordinate):通常,坐标采用一对数字的形式。两个数字分别是 x 坐标和 y 坐标。
坐标空间 (Coordinate space):显示对象中包含的坐标(其子元素所在的位置)的图形。
原点 (Origin):坐标空间中的一个点,x 轴和 y 轴在此位置相交。该点的坐标为 0, 0。
点 (Point):坐标空间中的一个位置。在 ActionScript 使用的二维坐标系中,点是按其 x 轴和 y 轴位置(点坐标)来定义的。
注册点 (Registration point):显示对象的坐标空间的原点(0, 0 坐标)。
缩放 (Scale):相对于原始大小的对象大小。用作动词时,对象缩放是指伸展或缩小对象以更改其大小。
平移 (Translate):将点的坐标从一个坐标空间更改为另一个坐标空间。
变形 (Transformation):对图形的可视特性进行的调整,如旋转对象、改变其缩放比例、倾斜或扭曲其形状或者改变其颜色。
X 轴 (X axis):ActionScript 使用的二维坐标系中的水平轴。
Y 轴 (Y axis):ActionScript 使用的二维坐标系中的垂直轴。
使用 Point 对象
Point 对象定义一对笛卡尔坐标。它表示二维坐标系中的某个位置。其中 x 表示水平轴,y 表示垂直轴。
要定义 Point 对象,请设置它的 x 和 y 属性。
确定两点之间的距离
可以使用 Point 类的 distance() 方法确定坐标空间两点之间的距离。
平移坐标空间
如果两个显示对象位于不同的显示对象容器中,则它们可能位于不同的坐标空间。您可以使用 DisplayObject 类的 localToGlobal() 方法将坐标平移到舞台中相同(全局)坐标空间。
按指定的角度和距离移动显示对象
您可以使用 Point 类的 polar() 方法将显示对象按特定角度移动特定距离。
Point 类的其它用法
您可以将 Point 对象用于以下方法和属性:

方法或属性
说明
DisplayObjectContainer
areInaccessibleObjectsUnderPoint() getObjectsUnderPoint()
用于返回显示对象容器中某个点下的对象的列表。
BitmapData
hitTest()
用于定义 BitmapData 对象中的像素以及要检查点击的点。
BitmapData
applyFilter() copyChannel() merge()
paletteMap() pixelDissolve() threshold()
用于定义那些定义操作的矩形的位置。
Matrix
deltaTransformPoint() transformPoint()
用于定义您要对其应用变形的点。
Rectangle
bottomRight size topLeft
用于定义这些属性。
使用 Rectangle 对象
Rectangle 对象定义一个矩形区域。Rectangle 对象有一个位置,该位置由其左上角的 x 和 y 坐标以及 width 属性和 height 属性定义。通过调用 Rectangle() 构造函数可以定义新 Rectangle 对象的这些属性。
调整 Rectangle 对象的大小和进行重新定位
您可以通过更改 Rectangle 对象的 x 和 y 属性直接重新定位该对象。这对 Rectangle 对象的宽度或高度没有任何影响。
如果更改 Rectangle 对象的 left 或 top 属性,也可以重新定位,并且该对象的 x 和 y 属性分别与 left 和 top 属性匹配。但是,Rectangle 对象的左下角位置不发生更改,所以调整了对象的大小。
如果更改 Rectangle 对象的 bottom 或 right 属性,该对象的左上角位置不发生更改,所以相应地调整了对象的大小。
可以使用 offset() 方法重新定位 Rectangle 对象,offsetPt() 方法工作方式类似,只不过它是将 Point 对象作为参数,而不是将 x 和 y 偏移量值作为参数。
还可以使用 inflate() 方法调整 Rectangle 对象的大小,该方法包含两个参数,dx 和 dy。dx 参数表示矩形的左边和右边距中心的像素数,而 dy 参数表示矩形的顶边和底边距中心的像素数。
inflatePt() 方法作方式类似,只不过它是将 Point 对象作为参数,而不是将 dx 和 dy 的值作为参数。
确定 Rectangle 对象的联合和交集
可以使用 union() 方法来确定由两个矩形的边界形成的矩形区域。
可以使用 intersection() 方法来确定由两个矩形重叠区域形成的矩形区域。
使用 intersects() 方法查明两个矩形是否相交。也可以使用 intersects() 方法查明显示对象是否在舞台的某个区域中。可以使用 intersects() 方法查明两个显示对象的边界矩形是否重叠。可以使用 DisplayObject 类的 getRect() 方法来包括显示对象笔触可添加到边界区域中的其它任何空间。
Rectangle 对象的其它用法
Rectangle 对象可用于以下方法和属性:

方法或属性
描述
BitmapData
applyFilter()、colorTransform()、copyChannel()、copyPixels()、draw()、fillRect()、generateFilterRect()、getColorBoundsRect()、getPixels()、merge()、paletteMap()、pixelDissolve()、setPixels() 和 threshold()
用作某些参数的类型以定义 BitmapData 对象的区域。
DisplayObject
getBounds()、getRect()、scrollRect、scale9Grid
用作属性的数据类型或返回的数据类型。
PrintJob
addPage()
用于定义 printArea 参数。
Sprite
startDrag()
用于定义 bounds 参数。
TextField
getCharBoundaries()
用作返回值类型。
Transform
pixelBounds
用作数据类型。


使用 Matrix 对象
Matrix 类表示一个转换矩阵,它确定如何将点从一个坐标空间映射到另一个坐标空间。可以对显示对象执行不同的图形转换,方法是设置 Matrix 对象的属性,将该 Matrix 对象应用于 Transform 对象的 matrix 属性,然后应用该 Transform 对象作为显示对象的 transform 属性。这些转换函数包括平移(x 和 y 重新定位)、旋转、缩放和倾斜。
定义 Matrix 对象
虽然可以通过直接调整 Matrix 对象的属性(a、b、c、d、tx 和 ty)来定义矩阵,但更简单的方法是使用 createBox() 方法。使用此方法提供的参数可以直接定义生成的矩阵的缩放、旋转和平移效果。
还可以使用 scale()、rotate() 和 translate() 方法调整 Matrix 对象的缩放、旋转和平移效果。请注意,这些方法合并了现有 Matrix 对象的值。
要将倾斜转换应用到 Matrix 对象,请调整该对象的 b 或 c 属性。调整 b 属性将矩阵垂直倾斜,并调整 c 属性将矩阵水平倾斜。
可以将矩阵转换应用到显示对象的 transform 属性。
使用绘图 API
绘图 API 使用基础知识
使用绘图 API 简介
绘图 API 是 ActionScript 中的一项内置功能的名称,您可以使用该功能来创建矢量图形(直线、曲线、形状、填充和渐变),并使用 ActionScript 在屏幕上显示它们。flash.display.Graphics 类提供了这一功能。您可以在任何 Shape、Sprite 或 MovieClip 实例中使用 ActionScript 进行绘制(使用其中的每个类中定义的 graphics 属性)。(实际上,每个类的 graphics 属性都是 Graphics 类的实例。)
重要概念和术语
以下参考列表包含将会在本章中遇到的重要术语:
锚点 (Anchor point):二次贝塞尔曲线的两个端点之一。
控制点 (Control point):该点定义了二次贝塞尔曲线的弯曲方向和弯曲量。弯曲的线绝不会到达控制点;但是,曲线就好像朝着控制点方向进行绘制的。
坐标空间 (Coordinate space):显示对象中包含的坐标(其子元素所在的位置)的图形。
填充 (Fill):形状内的实心部分,它包含一条用颜色填充的线条,或者整个形状都没有轮廓。
渐变 (Gradient):此颜色是指从一种颜色逐渐过渡到一种或多种其它颜色(相对于纯色而言)。
点 (Point):坐标空间中的一个位置。在 ActionScript 使用的二维坐标系中,点是按其 x 轴和 y 轴位置(点坐标)来定义的。
二次贝塞尔曲线 (Quadratic Bezier curve):一种由特定数学公式定义的曲线类型。在这种类型的曲线中,曲线形状是根据锚点(曲线端点)和控制点(定义曲线的弯曲方向和弯曲量)的位置计算的。
缩放 (Scale):相对于原始大小的对象大小。用作动词时,对象缩放是指伸展或缩小对象以更改其大小。
笔触 (Stroke):形状的轮廓部分,它包含一条用颜色填充的线条,或未填充的形状的多个线条。
平移 (Translate):将点的坐标从一个坐标空间更改为另一个坐标空间。
X 轴 (X axis):ActionScript 使用的二维坐标系中的水平轴。
Y 轴 (Y axis):ActionScript 使用的二维坐标系中的垂直轴。
了解 Graphics 类
每个 Shape、Sprite 和 MovieClip 对象都具有一个 graphics 属性,它是 Graphics 类的一个实例。Graphics 类包含用于绘制线条、填充和形状的属性和方法。如果要将显示对象仅用作内容绘制画布,则可以使用 Shape 实例。Shape 实例的性能优于其它用于绘制的显示对象,因为它不会产生 Sprite 和 MovieClip 类中的附加功能的开销。如果希望能够在显示对象上绘制图形内容,并且还希望该对象包含其它显示对象,则可以使用 Sprite 实例。
绘制直线和曲线
使用 Graphics 实例进行的所有绘制均基于包含直线和曲线的基本绘制。因此,必须使用一系列相同的步骤来执行所有 ActionScript 绘制:定义线条和填充样式、 设置初始绘制位置、 绘制直线、曲线和形状(可选择移动绘制点)、 如有必要,完成创建填充 。
定义线条和填充样式
要使用 Shape、Sprite 或 MovieClip 实例的 graphics 属性进行绘制,您必须先定义在绘制时使用的样式(线条大小和颜色、填充颜色)。就像使用 Adobe Flash CS3 Professional 或其它绘图应用程序中的绘制工具一样,使用 ActionScript 进行绘制时,可以使用笔触进行绘制,也可以不使用笔触;可以使用填充颜色进行绘制,也可以不使用填充颜色。您可以使用 lineStyle() 或 lineGradientStyle() 方法来指定笔触的外观。要创建纯色线条,请使用 lineStyle() 方法。调用此方法时,您指定的最常用的值是前三个参数:线条粗细、颜色以及 Alpha。Alpha 参数的默认值为 1.0 (100%),因此,如果需要完全不透明的线条,可以将该参数的值保持不变。lineStyle() 方法还接受两个用于像素提示和缩放模式的额外参数。
要创建渐变线条,请使用 lineGradientStyle() 方法。
如果要创建填充形状,请在开始绘制之前调用 beginFill()、beginGradientFill() 或 beginBitmapFill() 方法。其中的最基本方法 beginFill() 接受以下两个参数:填充颜色以及填充颜色的 Alpha 值(可选)。
调用任何填充方法时,将隐式地结束任何以前的填充,然后再开始新的填充。调用任何指定笔触样式的方法时,将替换以前的笔触,但不会改变以前指定的填充,反之亦然。
指定了线条样式和填充属性后,下一步是指示绘制的起始点。Graphics 实例具有一个绘制点,就像在一张纸上的钢笔尖一样。无论绘制点位于什么位置,它都是开始执行下一个绘制动作的位置。最初,Graphics 对象将它绘制时所在对象的坐标空间中的点 (0,0) 作为起始绘制点。要在其它点开始进行绘制,您可以先调用 moveTo() 方法,然后再调用绘制方法之一。这类似于将钢笔尖从纸上抬起,然后将其移到新位置。
确定绘制点后,可通过使用对绘制方法 lineTo()(用于绘制直线)和 curveTo()(用于绘制曲线)的一系列调用来进行绘制。在进行绘制时,可随时调用 moveTo() 方法,将绘制点移到新位置而不进行绘制。
在进行绘制时,如果已指定了填充颜色,可以指示 Adobe Flash Player 调用 endFill() 方法来结束填充。如果绘制的形状没有闭合(换句话说,在调用 endFill() 时,绘制点不在形状的起始点),调用 endFill() 方法时,Flash Player 将自动绘制一条直线以闭合形状,该直线从当前绘制点到最近一次 moveTo() 调用中指定的位置。如果已开始填充并且没有调用 endFill(),调用 beginFill()(或其它填充方法之一)时,将关闭当前填充并开始新的填充。
绘制直线
调用 lineTo() 方法时,Graphics 对象将绘制一条直线,该直线从当前绘制点到指定为方法调用中的两个参数的坐标,以便使用指定的线条样式进行绘制。
绘制曲线
curveTo() 方法可以绘制二次贝塞尔曲线。这将绘制一个连接两个点(称为锚点)的弧,同时向第三个点(称为控制点)弯曲。Graphics 对象使用当前绘制位置作为第一个锚点。调用 curveTo() 方法时,将传递以下四个参数:控制点的 x 和 y 坐标,后跟第二个锚点的 x 和 y 坐标。
使用内置方法绘制形状
为了便于绘制常见形状(如圆、椭圆、矩形以及带圆角的矩形),ActionScript 3.0 中提供了用于绘制这些常见形状的方法。它们是 Graphics 类的 drawCircle()、drawEllipse()、drawRect()、drawRoundRect() 和 drawRoundRectComplex() 方法。这些方法可用于替代 lineTo() 和 curveTo() 方法。但要注意,在调用这些方法之前,您仍需指定线条和填充样式。
在 Sprite 或 MovieClip 对象中,使用 graphics 属性创建的绘制内容始终出现在该对象包含的所有子级显示对象的后面。另外,graphics 属性内容不是单独的显示对象,因此,它不会出现在 Sprite 或 MovieClip 对象的子级列表中。
创建渐变线条和填充
graphics 对象也可以绘制渐变笔触和填充,而不是纯色笔触和填充。渐变笔触是使用 lineGradientStyle() 方法创建的;渐变填充是使用 beginGradientFill() 方法创建的。
这两种方法接受相同的参数。前四个参数是必需的,即类型、颜色、Alpha 以及比率。其余四个参数是可选的,但对于高级自定义非常有用。
第一个参数指定要创建的渐变类型。可接受的值是 GradientFill.LINEAR 或 GradientFill.RADIAL。
第二个参数指定要使用的颜色值的数组。在线性渐变中,将从左向右排列颜色。在放射状渐变中,将从内到外排列颜色。数组颜色的顺序表示在渐变中绘制颜色的顺序。
第三个参数指定前一个参数中相应颜色的 Alpha 透明度值。
第四个参数指定比率或每种颜色在渐变中的重要程度。可接受的值范围是 0-255。这些值并不表示任何宽度或高度,而是表示在渐变中的位置;0 表示渐变开始,255 表示渐变结束。比率数组必须按顺序增加,并且包含的条目数与第二个和第三个参数中指定的颜色和 Alpha 数组相同。
虽然第五个参数(转换矩阵)是可选的,但通常会使用该参数,因为它提供了一种简便且有效的方法来控制渐变外观。此参数接受 Matrix 实例。为渐变创建 Matrix 对象的最简单方法是使用 Matrix 类的 createGradientBox() 方法。
定义 Matrix 对象以用于渐变
可以使用 flash.display.Graphics 类的 beginGradientFill() 和 lineGradientStyle() 方法来定义在形状中使用的渐变。定义渐变时,需要提供一个矩阵作为这些方法的其中一个参数。
定义矩阵的最简单方法是使用 Matrix 类的 createGradientBox() 方法,该方法创建一个用于定义渐变的矩阵。可以使用传递给 createGradientBox() 方法的参数来定义渐变的缩放、旋转和位置。createGradientBox() 方法接受以下参数:
渐变框宽度:渐变扩展到的宽度(以像素为单位)
渐变框高度:渐变扩展到的高度(以像素为单位)
渐变框旋转:将应用于渐变的旋转角度(以弧度为单位)
水平平移:将渐变水平移动的距离(以像素为单位)
垂直平移:将渐变垂直移动的距离(以像素为单位)
请注意,渐变填充的宽度和高度是由渐变矩阵的宽度和高度决定的,而不是由使用 Graphics 对象绘制的宽度和高度决定的。使用 Graphics 对象进行绘制时,您绘制的内容位于渐变矩阵中的这些坐标处。即使使用 Graphics 对象的形状方法之一(如 drawRect()),渐变也不会将其自身伸展到绘制的形状的大小;必须在渐变矩阵本身中指定渐变的大小。
lineGradientStyle() 方法的工作方式与 beginGradientFill() 类似,所不同的是,除了定义渐变外,您还必须在绘制之前使用 lineStyle() 方法指定笔触粗细。
将 Math 类与绘制方法配合使用
Graphics 对象可以绘制圆和正方形,但也可以绘制更复杂的形状,尤其是在将绘制方法与 Math 类的属性和方法配合使用时。Math 类包含人们通常很感兴趣的数学常量,如 Math.PI(约等于 3.14159265...),此常量表示圆的周长与其直径的比率。它还包含三角函数的方法,其中包括 Math.sin()、Math.cos() 和 Math.tan() 等。使用这些方法和常量绘制形状可产生更动态的视觉效果,尤其是用于重复或递归时。
Math 类的很多方法都要求以弧度为单位来测量圆弧,而不是使用角度。
使用绘图 API 进行动画处理
使用绘图 API 创建内容的一个优点是,您并不限于将内容放置一次。可通过保留和修改用于绘制的变量来修改所绘制的内容。您可以通过更改变量和重绘(在一段帧上或使用计时器)来利用原有的动画。
过滤显示对象
ActionScript 3.0 包括 flash.filters 包,它包含一系列位图效果滤镜类,允许开发人员以编程方式对位图应用滤镜并显示对象,以达到图形处理应用程序中所具有的许多相同效果。
过滤显示对象的基础知识
过滤显示对象简介
ActionScript 3.0 包括九种可应用于任何显示对象或 BitmapData 实例的滤镜。滤镜的范围从基本滤镜(如投影和发光滤镜)到用于创建各种效果的复杂滤镜(如置换图滤镜和卷积滤镜)。
重要概念和术语
斜角:通过使两侧的像素变亮并使相对两侧的像素变暗所形成的一个边缘,它可以产生常用于凸起或凹进按钮和类似图形的三维边界效果。
卷积:通过使用各种比率将每个像素的值与其周围某些像素或全部像素的值合并以使图像中的像素发生扭曲。
置换:将图像中的像素移动到新位置。
矩阵:用于执行特定数学计算的网格数字,使用方法是对网格中的数字应用不同的值,然后合并结果。
建和应用滤镜
使用滤镜可以对位图和显示对象应用从投影到斜角和模糊等各种效果。由于将每个滤镜定义为一个类,因此应用滤镜涉及创建滤镜对象的实例,这与构造任何其它对象并没有区别。创建了滤镜对象的实例后,通过使用该对象的 filters 属性可以很容易地将此实例应用于显示对象;如果是 BitmapData 对象,可以使用 applyFilter() 方法。
创建新滤镜
若要创建新滤镜对象,只需调用所选的滤镜类的构造函数方法即可。
应用滤镜
构造滤镜对象后,可以将其应用于显示对象或 BitmapData 对象;应用滤镜的方式取决于您应用该滤镜的对象。
对显示对象应用滤镜
对显示对象应用滤镜效果时,可以通过 filters 属性应用这些效果。显示对象的 filters 属性是一个 Array 实例,其中的元素是应用于该显示对象的滤镜对象。若要对显示对象应用单个滤镜,请创建该滤镜实例,将其添加到 Array 实例,再将该 Array 对象分配给显示对象的 filters 属性。
如果要为该对象分配多个滤镜,只需在将 Array 实例分配给 filters 属性之前将所有滤镜添加到该实例即可。可以通过将多个对象作为参数传递给 Array 的构造函数,将多个对象添加到 Array。
在创建包含滤镜的数组时,您可以使用 new Array() 构造函数创建该数组(如前面示例所示),也可以使用 Array 文本语法将滤镜括在方括号 ([]) 中。
如果对显示对象应用多个滤镜,则会按顺序以累积方式应用这些滤镜。例如,如果滤镜数组有两个元素:先添加的斜角滤镜和后添加的投影滤镜,则投影滤镜既会应用于斜角滤镜,也会应用于显示对象。这是由于投影滤镜在滤镜数组中处于第二的位置。如果想要以非累积方式应用滤镜,则必须对显示对象的新副本应用每个滤镜。
删除显示对象中的滤镜
删除显示对象中的所有滤镜非常简单,只需为 filters 属性分配一个 null 值即可。
对 BitmapData 对象应用滤镜
对 BitmapData 对象应用滤镜需要使用 BitmapData 对象的 applyFilter() 方法。此方法不会修改原始的源图像;而是将对源图像应用滤镜的结果存储在调用 applyFilter() 方法的 BitmapData 实例中。
滤镜的工作原理
显示对象过滤是通过将原始对象的副本缓存为透明位图来工作的。
将滤镜应用于显示对象后,只要此对象具有有效的滤镜列表,Adobe Flash Player 就会将该对象缓存为位图。然后,将此位图用作所有后续应用的滤镜效果的原始图像。
每个显示对象通常包含两个位图:一个包含原始未过滤的源显示对象,另一个用于过滤后的最终图像。呈现时使用最终图像。只要显示对象不发生更改,最终图像就不需要更新。
使用滤镜的潜在问题
滤镜和位图缓存
若要对显示对象应用滤镜,必须启用该对象的位图缓存。在对 cacheAsBitmap 属性设置为 false 的显示对象应用滤镜时,Flash Player 会自动将该对象的 cacheAsBitmap 属性的值设置为 true。如果您以后删除了该显示对象中的所有滤镜,Flash Player 会将 cacheAsBitmap 属性重置为最后设置的值。
在运行时更改滤镜
如果已经对显示对象应用了一个或多个滤镜,则无法向 filters 属性数组添加其它滤镜。若要添加或更改应用的这组滤镜,需要创建整个滤镜数组的副本,然后对此(临时)数组进行修改。然后,将此数组重新分配给显示对象的 filters 属性,这样才能将滤镜应用于该对象。
滤镜和对象变形
在显示对象的边框矩形之外的任何过滤区域(例如投影)都不能视为可进行点击检测(确定实例是否与其它实例重叠或交叉)的表面。由于 DisplayObject 类的点击检测方法是基于矢量的,因此无法在位图结果上执行点击检测。例如,如果您对按钮实例应用斜角滤镜,则在该实例的斜角部分,点击检测不可用。
滤镜不支持缩放、旋转和倾斜;如果过滤的显示对象本身进行了缩放(如果 scaleX 和 scaleY 不是 100%),则滤镜效果将不随该实例缩放。这意味着,实例的原始形状将旋转、缩放或倾斜;而滤镜不随实例一起旋转、缩放或倾斜。
可以使用滤镜给实例添加动画,以形成理想的效果,或者嵌套实例并使用 BitmapData 类使滤镜动起来,以获得此效果。
滤镜和位图对象
对 BitmapData 对象应用滤镜时,cacheAsBitmap 属性会自动设置为 true。通过这种方式,滤镜实际上是应用于对象的副本而不是原始对象。
之后,会将此副本放在主显示(原始对象)上,尽量接近最近的像素。如果原始位图的边框发生更改,则会从头重新创建过滤的副本位图,而不进行伸展或扭曲。
如果清除了显示对象的所有滤镜,cacheAsBitmap 属性会重置为应用滤镜之前的值。
可用的显示滤镜
ActionScript 3.0 包括 9 个可用于显示对象和 BitmapData 对象的滤镜类:
斜角滤镜(BevelFilter 类)
模糊滤镜(BlurFilter 类)
投影滤镜(DropShadowFilter 类)
发光滤镜(GlowFilter 类)
渐变斜角滤镜(GradientBevelFilter 类)
渐变发光滤镜(GradientGlowFilter 类)
颜色矩阵滤镜(ColorMatrixFilter 类)
卷积滤镜(ConvolutionFilter 类)
置换图滤镜(DisplacementMapFilter 类)
前六个滤镜是简单滤镜,可用于创建一种特定效果,并可以对效果进行某种程度的自定义。可以使用 ActionScript 应用这六个滤镜,也可以在 Adobe Flash CS3 Professional 中使用"滤镜"面板将其应用于对象。因此,即使您要使用 ActionScript 应用滤镜,如果有 Flash 创作工具,也可以使用可视界面快速尝试不同的滤镜和设置,弄清楚如何创建需要的效果。
最后三个滤镜仅在 ActionScript 中可用。这些滤镜(颜色矩阵滤镜、卷积滤镜和置换图滤镜)所创建的效果具有极其灵活的形式;它们不仅仅可以进行优化以生成单一的效果,而且还具有强大的功能和灵活性。
不管是简单滤镜还是复杂滤镜,每个滤镜都可以使用其属性进行自定义。通常,您有两种方法用于设置滤镜属性。所有滤镜都允许通过向滤镜对象的构造函数传递参数值来设置属性。或者,不管您是否通过传递参数来设置滤镜属性,都可以在以后通过设置滤镜对象的属性值来调整滤镜。
斜角滤镜
BevelFilter 类允许您对过滤的对象添加三维斜面边缘。此滤镜可使对象的硬角或边缘具有硬角或边缘被凿削或呈斜面的效果。
BevelFilter 类属性允许您自定义斜角的外观。您可以设置加亮和阴影颜色、斜角边缘模糊、斜角角度和斜角边缘的位置,甚至可以创建挖空效果。
模糊滤镜
BlurFilter 类可使显示对象及其内容具有涂抹或模糊的效果。模糊效果可以用于产生对象不在焦点之内的视觉效果,也可以用于模拟快速运动,比如运动模糊。通过将模糊滤镜的 quality 属性设置为低,可以模拟轻轻离开焦点的镜头效果。将 quality 属性设置为高会产生类似高斯模糊的平滑模糊效果。
投影滤镜
投影给人一种目标对象上方有独立光源的印象。可以修改此光源的位置和强度,以产生各种不同的投影效果。
投影滤镜使用与模糊滤镜的算法相似的算法。主要区别是投影滤镜有更多的属性,您可以修改这些属性来模拟不同的光源属性(如 Alpha、颜色、偏移和亮度)。
投影滤镜还允许您对投影的样式应用自定义变形选项,包括内侧或外侧阴影和挖空(也称为剪切块)模式。
发光滤镜
GlowFilter 类对显示对象应用加亮效果,使显示对象看起来像是被下方的灯光照亮,可创造出一种柔和发光效果。
与投影滤镜类似,发光滤镜包括的属性可修改光源的距离、角度和颜色,以产生各种不同效果。GlowFilter 还有多个选项用于修改发光样式,包括内侧或外侧发光和挖空模式。
渐变斜角滤镜
GradientBevelFilter 类允许您对显示对象或 BitmapData 对象应用增强的斜角效果。在斜角上使用渐变颜色可以大大改善斜角的空间深度,使边缘产生一种更逼真的三维外观效果。
渐变发光滤镜
GradientGlowFilter 类允许您对显示对象或 BitmapData 对象应用增强的发光效果。该效果可使您更好地控制发光颜色,因而可产生一种更逼真的发光效果。另外,渐变发光滤镜还允许您对对象的内侧、外侧或上侧边缘应用渐变发光。
颜色矩阵滤镜
ColorMatrixFilter 类用于操作过滤对象的颜色和 Alpha 值。它允许您进行饱和度更改、色相旋转(将调色板从一个颜色范围移动到另一个颜色范围)、将亮度更改为 Alpha,以及生成其它颜色操作效果,方法是使用一个颜色通道中的值,并将这些值潜移默化地应用于其它通道。
从概念上来说,滤镜将逐一处理源图像中的像素,并将每个像素分为红、绿、蓝和 Alpha 组件。然后,用每个值乘以颜色矩阵中提供的值,将结果加在一起以确定该像素将显示在屏幕上的最终颜色值。滤镜的 matrix 属性是一个由 20 个数字组成的数组,用于计算最终颜色。
有关颜色矩阵滤镜的其它信息和示例,请参阅 Adobe 开发人员中心网站上提供的"Using Matrices for Transformations, Color Adjustments, and Convolution Effects in Flash"(在 Flash 中使用矩阵实现变形、颜色调整和卷积效果)文章。
卷积滤镜
ConvolutionFilter 类可用于对 BitmapData 对象或显示对象应用广泛的图像变形,如模糊、边缘检测、锐化、浮雕和斜角。
从概念上来说,卷积滤镜会逐一处理源图像中的每个像素,并使用像素和它周围的像素的值来确定该像素的最终颜色。指定为数值数组的矩阵可以指示每个特定邻近像素的值对最终结果值具有何种程度的影响。
最常用的矩阵类型是 3 x 3 矩阵。此矩阵包括九个值:
N N N
N P N
N N N
Flash Player 对特定像素应用卷积滤镜时,它会考虑像素本身的颜色值(本示例中的"P"),以及周围像素的值(本示例中的"N")。而通过设置矩阵中的值,可以指定特定像素在影响生成的图像方面所具有的优先级。
置换图滤镜
DisplacementMapFilter 类使用 BitmapData 对象(称为置换图图像)中的像素值在新对象上执行置换效果。通常,置换图图像与将要应用滤镜的实际显示对象或 BitmapData 实例不同。置换效果包括置换过滤的图像中的像素,也就是说,将这些像素移开原始位置一定距离。此滤镜可用于产生移位、扭曲或斑点效果。
应用于给定像素的置换位置和置换量由置换图图像的颜色值确定。使用滤镜时,除了指定置换图图像外,还要指定以下值,以便控制置换图图像中计算置换的方式:
映射点:过滤图像上的位置,在该点将应用置换滤镜的左上角。如果只想对图像的一部分应用滤镜,可以使用此值。
X 组件:影响像素的 x 位置的置换图图像的颜色通道。
Y 组件:影响像素的 y 位置的置换图图像的颜色通道。
X 缩放比例:指定 x 轴置换强度的乘数值。
Y 缩放比例:指定 y 轴置换强度的乘数值。
滤镜模式:确定在移开像素后形成的空白区域中,Flash Player 应执行什么操作。在 DisplacementMapFilterMode 类中定义为常量的选项可以显示原始像素(滤镜模式 IGNORE)、从图像的另一侧环绕像素(滤镜模式 WRAP,这是默认设置)、使用最近的移位像素(滤镜模式 CLAMP)或用颜色填充空间(滤镜模式 COLOR)。
处理影片剪辑
MovieClip 类是在 Adobe Flash CS3 Professional 中创建的动画和影片剪辑元件的核心类。它除具有显示对象的所有行为和功能外,还具有用于控制影片剪辑的时间轴的其它属性和方法。
影片剪辑基础知识
影片剪辑处理简介
只要在 Flash 中创建影片剪辑元件,Flash 就会将该元件添加到该 Flash 文档的库中。默认情况下,此元件会成为 MovieClip 类的一个实例,因此具有 MovieClip 类的属性和方法。
在将某个影片剪辑元件的实例放置在舞台上时,如果该影片剪辑具有多个帧,它会自动按其时间轴进行回放,除非使用 ActionScript 更改其回放。此时间轴使 MovieClip 类与其它类区别开来,允许您在 Flash 创作工具中通过补间动画或补间形状来创建动画。相反,对于作为 Sprite 类的实例的显示对象,您只需以编程方式更改该对象的值即可创建动画。
在 ActionScript 的早期版本中,MovieClip 类是舞台上所有实例的基类。在 ActionScript 3.0 中,影片剪辑只是可以在屏幕上显示的众多显示对象中的一个。如果使用显示对象时不需要时间轴,则使用 Shape 类或 Sprite 类替代 MovieClip 类可能会提高呈现性能。
重要概念和术语
AVM1 SWF:使用 ActionScript 1.0 或 ActionScript 2.0 创建的 SWF 文件,通常以 Flash Player 8 或更早版本为目标播放器。
AVM2 SWF:使用 ActionScript 3.0 for Adobe Flash Player 9 创建的 SWF 文件。
外部 SWF:单独从项目 SWF 文件创建的 SWF 文件,将加载到项目 SWF 文件中并在该 SWF 文件中回放。
帧:时间轴上划分时间的最小单位。与运动图像电影胶片一样,每个帧都类似于动画在特定时间的快照,当快速按顺序播放各个帧时,会产生动画的效果。
时间轴:构成影片剪辑动画序列的一系列帧的比喻形式。MovieClip 对象的时间轴等同于 Flash 创作工具中的时间轴。
播放头:一个标记,用于标识在给定时刻在时间轴中所处的位置(帧)。
处理 MovieClip 对象
在发布 SWF 文件时,Flash 会将舞台上的所有影片剪辑元件实例转换为 MovieClip 对象。通过在属性检查器的"实例名称"字段中指定影片剪辑元件的实例名称,您可以在 ActionScript 中使用该元件。在创建 SWF 文件时,Flash 会生成在舞台上创建该 MovieClip 实例的代码并使用该实例名称声明一个变量。如果您已经命名了嵌套在其它已命名影片剪辑内的影片剪辑,则这些子级影片剪辑将被视为父级影片剪辑的属性,您可以使用点语法访问该子级影片剪辑。
控制影片剪辑回放
Flash 利用时间轴来形象地表示动画或状态改变。任何使用时间轴的可视元素都必须为 MovieClip 对象或从 MovieClip 类扩展而来。尽管 ActionScript 可控制任何影片剪辑的停止、播放或转至时间轴上的另一点,但不能用于动态创建时间轴或在特定帧添加内容,这项工作仅能使用 Flash 创作工具来完成。
MovieClip 在播放时将以 SWF 文件的帧速率决定的速度沿着其时间轴推进。或者,您也可以通过在 ActionScript 中设置 Stage.frameRate 属性来覆盖此设置。
播放影片剪辑和停止回放
play() 和 stop() 方法允许对时间轴上的影片剪辑进行基本控制。
快进和后退
也可以使用 nextFrame() 和 prevFrame() 方法手动向前或向后沿时间轴移动播放头。调用这两种方法中的任一方法均会停止回放并分别使播放头向前或向后移动一帧。
在正常回放过程中,如果影片剪辑包含多个帧,播放时将会无限循环播放,也就是说在经过最后一帧后将返回到第 1 帧。使用 prevFrame() 或 nextFrame() 时,不会自动发生此行为(在播放头位于第 1 帧时调用 prevFrame() 不会将播放头移动到最后一帧)。
跳到不同帧和使用帧标签
向新帧发送影片剪辑非常简单。调用 gotoAndPlay() 或 gotoAndStop() 将使影片剪辑跳到指定为参数的帧编号。或者,您可以传递一个与帧标签名称匹配的字符串。可以为时间轴上的任何帧分配一个标签。为此,选择时间轴上的某一帧,然后在属性检查器的"帧标签"字段中输入一个名称。
当创建复杂的影片剪辑时,使用帧标签比使用帧编号具有明显优势。当动画中的帧、图层和补间的数量变得很大时,应考虑给重要的帧加上具有解释性说明的标签来表示影片剪辑中的行为转换(例如,"离开"、"行走"或"跑")。这可提高代码的可读性,同时使代码更加灵活,因为转到指定帧的 ActionScript 调用是指向单一参考("标签"而不是特定帧编号)的指针。如果您以后决定将动画的特定片段移动到不同的帧,则无需更改 ActionScript 代码,只要将这些帧的相同标签保持在新位置即可。
为便于在代码中表示帧标签,ActionScript 3.0 包括了 FrameLabel 类。此类的每个实例均代表一个帧标签,并具有一个 name 属性(表示在属性检查器中指定的帧标签的名称)和一个 frame 属性(表示该标签在时间轴上所处帧的帧编号)。
为了访问与影片剪辑实例相关联的 FrameLabel 实例,MovieClip 类包括了两个可直接返回 FrameLabel 对象的属性。currentLabels 属性返回一个包含影片剪辑整个时间轴上所有 FrameLabel 对象的数组。currentLabel 属性返回一个表示在时间轴上最近遇到的帧标签的 FrameLabel 对象。
处理场景
在 Flash 创作环境中,您可以使用场景来区分 SWF 文件播放时将要经过的一系列时间轴。使用 gotoAndPlay() 或 gotoAndStop() 方法的第二个参数,可以指定要向其发送播放头的场景。所有 FLA 文件开始时都只有初始场景,但您可以创建新的场景。
使用场景并非始终是最佳方法,因为场景有许多缺点。包含多个场景的 Flash 文档可能很难维护,尤其是在存在多个作者的环境中。多个场景也会使带宽效率降低,因为发布过程会将所有场景合并为一个时间轴。这样将使所有场景进行渐进式下载,即使从不会播放这些场景。因此,除非是组织冗长的基于多个时间轴的动画,否则通常不鼓励使用多个场景。
MovieClip 类的 scenes 属性返回表示 SWF 文件中所有场景的 Scene 对象的数组。currentScene 属性返回一个表示当前正在播放的场景的 Scene 对象。
Scene 类具有多个提供有关场景信息的属性。labels 属性返回表示该场景中帧标签的 FrameLabel 对象的数组。name 属性将以字符串形式返回场景名称。numFrames 属性返回一个表示场景中帧的总数的整数。
使用 ActionScript 创建 MovieClip 对象
在 Flash 中向屏幕中添加内容的一个方法是将资源从库中拖放到舞台上,但不是仅有这一种方法。对于复杂项目,经验丰富的开发人员通常更喜欢以编程方式创建影片剪辑。这种方法具有多个优点:代码更易于重用、编译时速度加快,以及仅可在 ActionScript 中进行的更复杂的修改。
ActionScript 3.0 的显示列表 API 简化了动态创建 MovieClip 对象的过程。直接实例化 MovieClip 实例的功能从向显示列表中添加该实例的过程中分离出来,从而更加灵活、简单,而不会牺牲控制性能。
在 ActionScript 3.0 中,当以编程方式创建影片剪辑(或任何其它显示对象)实例时,只有通过对显示对象容器调用 addChild() 或 addChildAt() 方法将该实例添加到显示列表中后,才能在屏幕上看到该实例。这允许您创建影片剪辑、设置其属性,甚至可以在向屏幕呈现该影片剪辑之前调用方法。
加载外部 SWF 文件
在 ActionScript 3.0 中,SWF 文件是使用 Loader 类来加载的。若要加载外部 SWF 文件,ActionScript 需要执行以下 4 个操作:
用文件的 URL 创建一个新的 URLRequest 对象。
创建一个新的 Loader 对象。
调用 Loader 对象的 load() 方法,并以参数形式传递 URLRequest 实例。
对显示对象容器(如 Flash 文档的主时间轴)调用 addChild() 方法,将 Loader 实例添加到显示列表中。
最后,代码如下所示:
var request:URLRequest = new URLRequest("http://www.[yourdomain].com/externalSwf.swf");
var loader:Loader = new Loader()
loader.load(request);
addChild(loader);
通过指定图像文件的 URL 而不是 SWF 文件的 URL,可以使用上述同样的代码加载外部图像文件,如 JPEG、GIF 或 PNG 图像。SWF 文件不同于图像文件,可能包含 ActionScript。因此,虽然加载 SWF 文件的过程可能与加载图像的过程完全相同,但如果您计划使用 ActionScript 以某种方式与外部 SWF 文件通信,则在加载该外部 SWF 文件时,执行加载的 SWF 文件和被加载的 SWF 文件必须位于同一个安全沙箱中。另外,如果外部 SWF 文件包含了与执行加载的 SWF 文件中的类共享同一命名空间的类,可能需要为被加载的 SWF 文件创建新的应用程序域才能避免命名空间冲突。
当成功加载外部 SWF 文件后,可通过 Loader.content 属性访问该文件。如果该外部 SWF 文件是针对 ActionScript 3.0 发布的,则加载的文件将为影片剪辑或 sprite,具体取决于所扩展的类。
处理文本
处理文本的基础知识
处理文本简介
在 Adobe Flash Player 中,若要在屏幕上显示文本,可以使用 TextField 类的实例。TextField 类是 Adobe Flex 框架和 Flash 创作环境中提供的其它基于文本的组件(如 TextArea 组件或 TextInput 组件)的基础。
文本字段内容可以在 SWF 文件中预先指定、从外部源(如文本文件或数据库)中加载或由用户在与应用程序交互时输入。在文本字段内,文本可以显示为呈现的 HTML 内容,并可在其中嵌入图像。一旦建立了文本字段的实例,您可以使用 flash.text 包中的类(例如 TextFormat 类和 StyleSheet 类)来控制文本的外观。flash.text 包几乎包含与在 ActionScript 中创建文本、管理文本及对文本进行格式设置有关的所有类。
可以用 TextFormat 对象定义格式设置并将此对象分配给文本字段,以此来设置文本格式。如果文本字段包含 HTML 文本,则可以对文本字段应用 StyleSheet 对象,以便将样式分配给文本字段内容的特定片段。TextFormat 对象或 StyleSheet 对象包含定义文本外观(例如颜色、大小和粗细)的属性。TextFormat 对象可以将属性分配给文本字段中的所有内容,也可以分配给某个范围的文本。
重要概念和术语
层叠样式表 (Cascading style sheet):对以 XML(或 HTML)格式构成的内容指定样式和格式设置的标准语法。
设备字体 (Device font):安装在用户计算机上的字体。
动态文本字段 (Dynamic text field):可以由 ActionScript 更改内容而不能由用户输入内容的文本字段。
嵌入字体 (Embedded font):字符轮廓数据存储在应用程序的 SWF 文件中的一种字体。
HTML 文本:使用 ActionScript 输入到文本字段中的文本内容,包括 HTML 格式标签和实际文本内容。
输入文本字段 (Input text field):其内容可通过用户输入也可以通过 ActionScript 进行更改的文本字段。
静态文本字段 (Static text field):在 Flash 创作工具中创建的文本字段,运行 SWF 文件时不能更改其内容。
文本行量度 (Text line metri):文本字段中文本内容不同部分的大小的量度,如文本的基线、字符顶部的高度、下行字符(某些小写字母延伸到基线以下的部分)的大小等等。
显示文本
尽管诸如 Adobe Flex Builder 和 Flash 等创作工具为显示文本提供了几种不同的选择(包括与文本相关的组件或文本工具),但以编程方式显示文本的主要途径还是通过文本字段。
文本类型
文本字段中文本的类型根据其来源进行划分:
动态文本
动态文本包含从外部源(例如文本文件、XML 文件以及远程 Web 服务)加载的内容。
输入文本
输入文本是指用户输入的任何文本或用户可以编辑的动态文本。可以设置样式表来设置输入文本的格式,或使用 flash.text.TextFormat 类为输入内容指定文本字段的属性。
静态文本
静态文本只能通过 Flash 创作工具来创建。您无法使用 ActionScript 3.0 创建静态文本实例。但是,可以使用 ActionScript 类(例如 StaticText 和 TextSnapshot)来操作现有的静态文本实例。
修改文本字段内容
可以通过将一个字符串赋予 flash.text.TextField.text 属性来定义动态文本。
或者,可以将一个远程变量的值赋予 text 属性。从远程源加载文本值有三种方式:
flash.net.URLLoader 和 flash.net.URLRequest 类可以从本地或远程位置为文本加载变量。
FlashVars 属性被嵌入到承载 SWF 文件的 HTML 页中,可以包含文本变量的值。
flash.net.SharedObject 类管理值的永久存储。
显示HTML文本
flash.text.TextField 类具有一个 htmlText 属性,可使用它将您的文本字符串标识为包含用于设置内容格式的 HTML 标签。如下例所示,必须将您的字符串值赋予 htmlText 属性(而不是 text 属性),以便 Flash Player 将文本呈现为 HTML.
一旦您使用 htmlText 属性指定了内容,就可以使用样式表或 textformat 标签来管理内容的格式设置。
在文本字段中使用图像
将内容显示为 HTML 文本的另一个好处是可以在文本字段中包括图像。可以使用 img 标签引用一个本地或远程图像,并使其显示在关联的文本字段内。img 标签支持 JPEG、GIF、PNG 和 SWF 文件。
在文本字段中滚动文本
在许多情况下,文本会比显示该文本的文本字段长。或者,某个输入字段允许用户输入比字段一次可显示的文本内容更多的文本。您可以使用 flash.text.TextField 类的与滚动相关的属性来管理过长的内容(垂直或水平方向)。
与滚动有关的属性包括 TextField.scrollV、TextField.scrollH、maxScrollV 和 maxScrollH。可使用这些属性来响应鼠标单击或按键等事件。
选择和操作文本
您可以选择动态文本或输入文本。由于 TextField 类的文本选择属性和方法使用索引位置来设置要操作的文本的范围,因此即使不知道内容,您也可以以编程方式选择动态文本或输入文本。
选择文本
默认情况下,flash.text.TextField.selectable 属性为 true,您可以使用 setSelection() 方法以编程方式选择文本。
捕获用户选择的文本
TextField 类的 selectionBeginIndex 和 selectionEndIndex 属性可用于捕获用户当前选择的内容,这两个属性为"只读"属性,因此不能设置为以编程方式选择文本。此外,输入文本字段也可以使用 caretIndex 属性。
捕获文本输入
默认情况下,文本字段的 type 属性设置为 dynamic。如果使用 TextFieldType 类将 type 属性设置为 input,则可以收集用户输入并保存该值以便在应用程序的其它部分使用。对于表单以及希望用户定义可用于程序中其它位置的文本值的任何应用程序而言,输入文本字段都十分有用。
限制文本输入
由于输入文本字段经常用于表单或应用程序中的对话框,因此您可能想要限制用户在文本字段中输入的字符的类型,或者甚至想将文本隐藏。可以设置 flash.text.TextField 类的 displayAsPassword 属性和 restrict 属性来控制用户输入。
displayAsPassword 属性只是在用户键入文本时将其隐藏(显示为一系列星号)。当 displayAsPassword 设置为 true 时,"剪切"和"复制"命令及其对应的键盘快捷键将不起作用。
restrict 属性则更为复杂一些,因为您需要指定允许用户在输入文本字段中键入哪些字符。可以允许特定字母、数字或字母、数字和字符的范围。ActionScript 3.0 使用连字符来定义范围,使用尖号来定义被排除的字符。
设置文本格式
以编程方式设置文本显示的格式设置有多种方式。可以直接在 TextField 实例中设置属性,例如,TextFIeld.thickness、TextField.textColor 和 TextField.textHeight 属性。也可以使用 htmlText 属性指定文本字段的内容,并使用受支持的 HTML 标签,如 b、i 和 u。但是您也可以将 TextFormat 对象应用于包含纯文本的文本字段,或将 StyleSheet 对象应用于包含 htmlText 属性的文本字段。使用 TextFormat 和 StyleSheet 对象可以对整个应用程序的文本外观提供最有力的控制和最佳的一致性。可以定义 TextFormat 或 StyleSheet 对象并将其应用于应用程序中的部分或所有文本字段。
指定文本格式
您可以使用 TextFormat 类设置多个不同的文本显示属性,并将它们应用于 TextField 对象的整个内容或一定范围的文本。
TextField.setTextFormat() 方法只影响已显示在文本字段中的文本。如果 TextField 中的内容发生更改,则应用程序可能需要重新调用 TextField.setTextFormat() 方法以便重新应用格式设置。您也可以设置 TextField 对象的 defaultTextFormat 属性来指定要用于用户输入文本的格式。
应用层叠样式表
文本字段可以包含纯文本或 HTML 格式的文本。纯文本存储在实例的 text 属性中,而 HTML 文本存储在 htmlText 属性中。
您可以使用 CSS 样式声明来定义可应用于多种不同文本字段的文本样式。CSS 样式声明可以在应用程序代码中进行创建,也可以在运行时从外部 CSS 文件中加载。
flash.text.StyleSheet 类用于处理 CSS 样式。StyleSheet 类可识别有限的 CSS 属性集合。
要使 CSS 样式生效,应在设置 htmlText 属性之前对 TextField 对象应用样式表。
根据设计,带有样式表的文本字段是不可编辑的。如果您有一个输入文本字段并为其分配一个样式表,则该文本字段将显示样式表的属性,但不允许用户在其中输入新的文本。而且,您也无法在分配有样式表的文本字段上使用以下 ActionScript API:
TextField.replaceText() 方法
TextField.replaceSelectedText() 方法
TextField.defaultTextFormat 属性
TextField.setTextFormat() 方法
如果某个文本字段已经分配了一个样式表,但后来将 TextField.styleSheet 属性设置为 null,则 TextField.text 和 TextField.htmlText 属性的内容会向它们的内容中添加标签和属性,以结合先前分配的样式表设定的格式。若要保留原始 htmlText 属性,应在将样式表设置为 null 之前将其保存在变量中。
加载外部 CSS 文件
用于设置格式的 CSS 方法的功能更加强大,您可以在运行时从外部文件加载 CSS 信息。当 CSS 数据位于应用程序本身以外时,您可以更改应用程序中的文本的可视样式,而不必更改 ActionScript 3.0 源代码。部署完应用程序后,可以通过更改外部 CSS 文件来更改应用程序的外观,而不必重新部署应用程序的 SWF 文件。
StyleSheet.parseCSS() 方法可将包含 CSS 数据的字符串转换为 StyleSheet 对象中的样式声明。
设置文本字段内文本范围的格式
flash.text.TextField 类的一个特别有用的方法是 setTextFormat() 方法。使用 setTextFormat(),您可以将特定属性分配给文本字段的部分内容以响应用户输入 。
高级文本呈现
ActionScript 3.0 在 flash.text 包中提供多个类来控制所显示文本的属性,包括嵌入字体、消除锯齿设置、alpha 通道控制及其它特定设置。
使用嵌入字体
您在应用程序中为 TextField 指定特定字体时,Flash Player 会查找具有相同名称的设备字体(位于用户计算机上的字体)。如果在用户系统上没有找到该字体,或者如果用户的字体版本与具有该名称的字体略有差异,则文本显示外观会与预想的情况差别很大。
若要确保用户看到完全正确的字体,您可以将该字体嵌入到应用程序的 SWF 文件中。嵌入字体有很多好处:
嵌入字体字符是消除锯齿的,特别是对于较大的文本,该字体可以使文本边缘看起来更平滑。
可以旋转使用嵌入字体的文本。
嵌入字体文本可以产生透明或半透明效果。
可以对嵌入字体使用字距调整的 CSS 样式。
使用嵌入字体的最大限制是嵌入字体会增加文件大小或应用程序的下载大小。
将声音文件嵌入到应用程序的 SWF 文件中的具体方法因开发环境而异。
嵌入字体后,可以确保 TextField 使用正确的嵌入字体:
将 TextField 的 embedFonts 属性设置为 true。
创建一个 TextFormat 对象,将其 fontFamily 属性设置为嵌入字体的名称,并对 TextField 应用 TextFormat 对象。指定嵌入字体时,fontFamily 属性应只包含一个名称;该名称不能是用逗号分隔的由多个字体名称构成的列表。
如果使用 CSS 样式为 TextField 或组件设置字体,请将 font-family CSS 属性设置为嵌入字体的名称。如果要指定一种嵌入字体,则 font-family 属性必须包含单一名称,而不能是多个名称的列表。
控制清晰度、粗细和消除锯齿
默认情况下,在文本调整大小、更改颜色或在不同背景上显示时,Flash Player 可以确定文本显示控件的设置(如清晰度、粗细和消除锯齿)。在某些情况下,如文本很小、很大或显示在各种特别的背景上时,您可能需要保持您自己对这些设置的控制。可以使用 flash.text.TextRenderer 类及其相关类(如 CSMSettings 类)来覆盖 Flash Player 设置。使用这些类可以精确控制嵌入文本的呈现品质。
flash.text.TextField.antiAliasType 属性必须具有 AntiAliasType.ADVANCED 值(该值为默认值),以供您设置清晰度、粗细或 gridFitType 属性,或供您使用 TextRenderer.setAdvancedAntiAliasingTable() 方法。
处理静态文本
静态文本只能在 Flash 创作工具中创建。不能使用 ActionScript 以编程方式对静态文本进行实例化。静态文本用于比较短小并且不会更改(而动态文本则会更改)的文本。可以将静态文本看作类似于在 Flash 创作工具中在舞台上绘制的圆或正方形的一种图形元素。由于静态文本比动态文本受到更多的限制,ActionScript 3.0 不支持使用 flash.text.StaticText 类读取静态文本属性值的能力。另外,您可以使用 flash.text.TextSnapshot 类从静态文本中读取值。
使用 StaticText 类访问静态文本字段
通常,可以在 Flash 创作工具的"动作"面板中使用 flash.text.StaticText 类来与放置在舞台上的静态文本实例进行交互。也可以在与包含静态文本的 SWF 文件进行交互的 ActionScript 文件中执行类似工作。但是这两种情况下都不能以编程方式对静态文本实例进行实例化。静态文本是在 Flash CS3 创作工具中创建的。
引用静态文本字段后,您可以在 ActionScript 3.0 中使用该字段的属性。
使用 TextSnapshot 类
如果要以编程方式使用现有静态文本实例,可以使用 flash.text.TextSnapshot 类来与 flash.display.DisplayObjectContainer 的 textSnapshot 属性配合工作。也就是说,通过 DisplayObjectContainer.textSnapshot 属性创建 TextSnapshot 实例。然后,可以将方法应用于该实例,以检索值或选择部分静态文本。
处理位图
处理位图的基本知识
处理位图简介
使用数字图像时,您可能会遇到两种主要的图形类型:位图和矢量。位图图形也称为光栅图形,由排列为矩形网格形式的小方块(像素)组成。矢量图形由以数学方式生成的几何形状(如直线、曲线和多边形)组成。
位图图像用图像的宽度和高度来定义,以像素为量度单位,每个像素包含的位数表示像素包含的颜色数。在使用 RGB 颜色模型的位图图像中,像素由三个字节组成:红、绿和蓝。每个字节包含一个 0 至 255 之间的值。将字节与像素合并时,它们可以产生与艺术混合绘画颜色相似的颜色。例如,一个包含红色字节值 255、绿色字节值 102 和蓝色字节值 0 的像素可以形成明快的橙色。
位图图像的品质由图像分辨率和颜色深度位值共同确定。分辨率 与图像中包含的像素数有关。像素数越大,分辨率越高,图像也就越精确。颜色深度 与像素可包含的信息量有关。例如,颜色深度值为每像素 16 位的图像无法显示颜色深度为 48 位的图像所具有颜色数。因此,48 位图像与 16 位图像相比,其阴影具有更高的平滑度。
由于位图图形跟分辨率有关,因此不能很好地进行缩放。当放大位图图像时,这一特性显得尤为突出。通常,放大位图有损其细节和品质。
位图文件格式
位图图像可分为几种常见的文件格式。这些格式使用不同类型的压缩算法减小文件大小,并基于图像的最终用途优化图像品质。Adobe Flash Player 支持的位图图像格式有 GIF、JPG 和 PNG。
GIF
图形交换格式 (GIF) 最初由 CompuServe 于 1987 年开发,作为一种传送 256 色(8 位颜色)图像的方式。此格式提供较小的文件大小,是基于 Web 的图像的理想格式。受此格式的调色板所限,GIF 图像通常不适用于照片,照片通常需要高度的阴影和颜色渐变。GIF 图像允许产生一位透明度,允许将颜色映射为清晰(或透明)。这可以使网页的背景颜色通过已映射透明度的图像显示出来。
JPEG
由联合图像专家组 (JPEG) 开发,JPEG(通常写成 JPG)图像格式使用有损压缩算法允许 24 位颜色深度具有很小的文件大小。有损压缩意味着每次保存图像,都会损失图像品质和数据,但会生成更小的文件大小。由于 JPEG 能够显示数百万计的颜色,因此它是照片的理想格式。控制应用于图像的压缩程度的功能使您能够控制图像品质和文件大小。
PNG
可移植网络图形 (PNG) 格式是作为受专利保护的 GIF 文件格式的开放源替代格式而开发的。PNG 最多支持 64 位颜色深度,允许使用最多 1600 万种颜色。由于 PNG 是一种比较新的格式,因此一些旧版本浏览器不支持 PNG 文件。与 JPG 不同,PNG 使用无损压缩,这意味着保存图像时不会丢失图像数据。PNG 文件还支持 Alpha 透明度,允许使用最多 256 级透明度。
透明位图和不透明位图
使用 GIF 或 PNG 格式的位图图像可以对每个像素添加一个额外字节(Alpha 通道)。此额外像素字节表示像素的透明度值。
GIF 图像允许使用一位透明度,这意味着您可以在 256 色调色板中指定一种透明的颜色。而 PNG 图像最多可以有 256 级透明度。当需要将图像或文本混合到背景中时,此功能特别有用。
ActionScript 3.0 在 BitmapData 类中复制了此额外透明度像素字节。与 PNG 透明度模型类似,BitmapDataChannel.ALPHA 常量最多提供 256 级透明度。
重要概念和术语
Alpha:颜色或图像中的透明度级别(更准确地说是指不透明度)。Alpha 量通常称为"Alpha 通道"值。
ARGB 颜色:一种配色方案,其中每个像素的颜色是红、绿和蓝色值的混合颜色,并将其透明度指定为一个 Alpha 值。
颜色通道:通常,将颜色表示为几种基本颜色的混合颜色,对于计算机图形来说,通常是红色、绿色和蓝色。每种基本颜色都视为一个颜色通道;每个颜色通道中的颜色量混合在一起可确定最终颜色。
颜色深度:也称为"位深度",指专门用于每个像素的计算机内存量,因而可以确定图像中可以显示的可能颜色数。
像素:位图图像中的最小信息单位,实际上就是颜色点。
分辨率:图像的像素尺寸,它决定图像中包含的精细细节的级别。分辨率通常表示为用像素数表示的宽度和高度。
RGB 颜色:一种配色方案,其中每个像素的颜色均表示为红、绿和蓝色值的混合颜色。
Bitmap 和 BitmapData 类
处理位图图像的主要 ActionScript 3.0 类是 Bitmap 类(用于在屏幕上显示位图图像)和 BitmapData 类(用于访问和操作位图的原始图像数据)。
了解 Bitmap 类
作为 DisplayObject 类的子类,Bitmap 类是用于显示位图图像的主要 ActionScript 3.0 类。这些图像可能已经通过 flash.display.Loader 类加载到 Flash 中,或已经使用 Bitmap() 构造函数动态创建。从外部源加载图像时,Bitmap 对象只能使用 GIF、JPEG 或 PNG 格式的图像。实例化后,可将 Bitmap 实例视为需要呈现在舞台上的 BitmapData 对象的包装。由于 Bitmap 实例是一个显示对象,因此可以使用显示对象的所有特性和功能来操作 Bitmap 实例。
像素贴紧和平滑
除了所有显示对象常见的功能外,Bitmap 类还提供了特定于位图图像的一些附加功能。
与 Flash 创作工具中的贴紧像素功能类似,Bitmap 类的 pixelSnapping 属性可确定 Bitmap 对象是否贴紧最近的像素。此属性接受 PixelSnapping 类中定义的三个常量之一:ALWAYS、AUTO 和 NEVER。
应用像素贴紧的语法为: myBitmap.pixelSnapping = PixelSnapping.ALWAYS;
通常,缩放位图图像时,图像会变得模糊或扭曲。若要帮助减少这种扭曲,请使用 BitmapData 类的 smoothing 属性。这是一个布尔值属性,设置为 true 时,缩放图像时,可使图像中的像素平滑或消除锯齿。它可使图像更加清晰、更加自然。
了解 BitmapData 类
BitmapData 类位于 flash.display 包中,它可以看作是加载的或动态创建的位图图像中包含的像素的照片快照。此快照用对象中的像素数据的数组表示。BitmapData 类还包含一系列内置方法,可用于创建和处理像素数据。
若要实例化 BitmapData 对象,请使用以下代码:
var myBitmap:BitmapData = new BitmapData(width:Number, height:Number, transparent:Boolean, fillColor:uinit);
width 和 height 参数指定位图的大小;二者的最大值都是 2880 像素。transparent 参数指定位图数据是 (true) 否 (false) 包括 Alpha 通道。fillColor 参数是一个 32 位颜色值,它指定背景颜色和透明度值(如果设置为 true)。
若要在屏幕上呈现新创建的 BitmapData 对象,请将此对象分配给或包装到 Bitmap 实例中。为此,可以作为 Bitmap 对象的构造函数的参数形式传递 BitmapData 对象,也可以将此对象分配给现有 Bitmap 实例的 bitmapData 属性。您还必须通过调用将包含该 Bitmap 实例的显示对象容器的 addChild() 或 addChildAt() 方法将该 Bitmap 实例添加到显示列表中。
处理像素
BitmapData 类包含一组用于处理像素数据值的方法。
处理单个像素
在像素级别更改位图图像的外观时,您首先需要获取要处理的区域中包含的像素的颜色值。使用 getPixel() 方法可读取这些像素值。
getPixel() 方法从作为参数传递的一组 x, y(像素)坐标中检索 RGB 值。如果您要处理的像素包括透明度(Alpha 通道)信息,则需要使用 getPixel32() 方法。此方法也可以检索 RGB 值,但与 getPixel() 不同,getPixel32() 返回的值包含表示所选像素的 Alpha 通道(透明度)值的附加数据。
或者,如果只想更改位图中包含的某个像素的颜色或透明度,则可以使用 setPixel() 或 setPixel32() 方法。若要设置像素的颜色,只需将 x, y 坐标和颜色值传递到这两种方法之一即可。
像素级别冲突检测
BitmapData.hitTest() 方法可以在位图数据和另一个对象或点之间执行像素级别冲突检测。
BitmapData.hitTest() 方法接受五个参数:
firstPoint (Point):此参数指在其上执行点击测试的第一个 BitmapData 的左上角的像素位置。
firstAlphaThreshold (uint):此参数指定对于此点击测试视为不透明的最高 Alpha 通道值。
secondObject (Object):此参数表示影响区域。secondObject 对象可以是 Rectangle、Point、Bitmap 或 BitmapData 对象。此对象表示在其上执行冲突检测的点击区域。
secondBitmapDataPoint (Point):此可选参数用于在第二个 BitmapData 对象中定义像素位置。只有当 secondObject 的值为 BitmapData 对象时,才使用此参数。默认值为 null。
secondAlphaThreshold (uint):此可选参数表示在第二个 BitmapData 对象中视为不透明的最高 Alpha 通道值。默认值为 1。只有当 secondObject 是一个 BitmapData 对象且两个 BitmapData 对象都透明时,才使用此参数。
在不透明图像上执行冲突检测时,请牢记,ActionScript 会将图像视为完全不透明的矩形(或边框)。或者,在透明的图像上执行像素级别点击测试时,需要两个图像都是透明的。除此之外,ActionScript 还使用 Alpha 阈值参数来确定像素在哪点开始从透明变为不透明。
复制位图数据
若要从一个图像向另一个图像中复制位图数据,可以使用多种方法:clone()、copyPixels()、copyChannel() 和 draw()。
正如名称的含义一样,clone() 方法允许您将位图数据从一个 BitmapData 对象克隆或采样到另一个对象。调用此方法时,此方法返回一个新的 BitmapData 对象,它是与被复制的原始实例完全一样的克隆。
copyPixels() 方法是一种从一个 BitmapData 对象向另一个对象复制像素的快速简便的方法。该方法会拍摄源图像的矩形快照(由 sourceRect 参数定义),并将其复制到另一个矩形区域(大小相等)。新"粘贴"的矩形位置在 destPoint 参数中定义。
copyChannel() 方法从源 BitmapData 对象中采集预定义的颜色通道值(Alpha、红、绿或蓝),并将此值复制到目标 BitmapData 对象的通道中。调用此方法不会影响目标 BitmapData 对象中的其它通道。
draw() 方法将源 sprite、影片剪辑或其它显示对象中的图形内容绘制或呈现在新位图上。使用 matrix、colorTransform、blendMode 和目标 clipRect 参数,可以修改新位图的呈现方式。此方法使用 Flash Player 矢量渲染器生成数据。
如果源对象在最初加载后应用了变形(颜色、矩阵等等),则不能将这些变形复制到新对象。如果想要将变形复制到新位图,则需要将 transform 属性的值从原始对象复制到使用新 BitmapData 对象的 Bitmap 对象的 transform 属性中。
使用杂点功能制作纹理
若要修改位图的外观,可以使用 noise() 方法或 perlinNoise() 方法对位图应用杂点效果。可以把杂点效果比作未调谐的电视屏幕的静态外观。
若要对位图应用杂点效果,请使用 noise() 方法。此方法对位图图像的指定区域中的像素应用随机颜色值。
此方法接受五个参数:
randomSeed (int):决定图案的随机种子数。不管名称具有什么样的含义,只要传递的数字相同,此数字就会生成相同的结果。为了获得真正的随机结果,请使用 Math.random() 方法为此参数传递随机数字。
low (uint):此参数指要为每个像素生成的最低值(0 至 255)。默认值为 0。将此参数设置为较低值会产生较暗的杂点图案,而将此参数设置为较高值会产生较亮的图案。
high (uint):此参数指要为每个像素生成的最高值(0 至 255)。默认值为 255。将此参数设置为较低值会产生较暗的杂点图案,而将此参数设置为较高值会产生较亮的图案。
channelOptions (uint):此参数指定将向位图对象的哪个颜色通道应用杂点图案。此数字可以是四个颜色通道 ARGB 值的任意组合。默认值是 7。
grayScale (Boolean):设置为 true 时,此参数对位图像素应用 randomSeed 值,可有效地褪去图像中的所有颜色。此参数不影响 Alpha 通道。默认值为 false。
如果想要创建更好的有机外观纹理,请使用 perlinNoise() 方法。perlinNoise() 方法可生成逼真、有机的纹理,是用于烟雾、云彩、水、火或爆炸的理想图案。
由于 perlinNoise() 方法是由算法生成的,因此它使用的内存比基于位图的纹理少。但还是会对处理器的使用有影响,特别是对于旧计算机,会降低 Flash 内容的处理速度,使屏幕重新绘制的速度比帧频慢。这主要是因为需要进行浮点计算,以便处理 Perlin 杂点算法。
此方法接受九个参数(前六个是必需参数):
baseX (Number):决定创建的图案的 x(大小)值。
baseY (Number):决定创建的图案的 y(大小)值。
numOctaves (uint):要组合以创建此杂点的 octave 函数或各个杂点函数的数目。octave 数目越大,创建的图像越精细,但这需要更多的处理时间。
randomSeed (int):随机种子数的功能与在 noise() 函数中的功能完全相同。为了获得真正的随机结果,请使用 Math.random() 方法为此参数传递随机数字。
stitch (Boolean):如果设置为 true,则此方法尝试缝合(或平滑)图像的过渡边缘以形成无缝的纹理,用于作为位图填充进行平铺。
fractalNoise (Boolean):此参数与此方法生成的渐变的边缘有关。如果设置为 true,则此方法生成的碎片杂点会对效果的边缘进行平滑处理。如果设置为 false,则将生成湍流。带有湍流的图像具有可见的不连续性渐变,可以使用它处理更接近锐化的视觉效果,例如,火焰或海浪。
channelOptions (uint):channelOptions 参数的功能与在 noise() 方法中的功能完全相同。它指定对哪个颜色通道(在位图上)应用杂点图案。此数字可以是四个颜色通道 ARGB 值的任意组合。默认值是 7。
grayScale (Boolean):grayScale 参数的功能与在 noise() 方法中的功能完全相同。如果设置为 true,则对位图像素应用 randomSeed 值,可有效地褪去图像中的所有颜色。默认值为 false。
offsets (Array):对应于每个 octave 的 x 和 y 偏移的点数组。通过处理偏移值,可以平滑滚动图像层。偏移数组中的每个点将影响一个特定的 octave 杂点函数。默认值为 null。
滚动位图
scroll() 方法可以复制屏幕上的位图,然后将它粘贴到由 (x, y) 参数指定的新偏移位置。如果位图的一部分恰巧在舞台以外,则会产生图像发生移位的效果。与计时器函数(或 enterFrame 事件)配合使用时,可以使图像呈现动画或滚动效果。
处理视频
视频基础知识
视频处理简介
Adobe Flash Player 的一个重要功能是可以使用 ActionScript,以操作其它可视内容(如图像、动画、文本等)的方式显示和操作视频信息。
在 Adobe Flash CS3 Professional 中创建 Flash 视频 (FLV) 文件时,您可以选择视频的外观,包括常用的回放控件。不过,您不一定要局限于可用的选项。使用 ActionScript 可以精确控制视频的加载、显示和回放,这意味着您可以创建自己的视频播放器外观,也可以按照所需的任何非传统方式使用视频。
在 ActionScript 中使用视频涉及多个类的联合使用:
Video 类:舞台上的实际视频内容框是 Video 类的一个实例。Video 类是一种显示对象,因此可以使用适用于其它显示对象的同样的技术(比如定位、应用变形、应用滤镜和混合模式等)进行操作。
NetStream 类:在加载将由 ActionScript 控制的视频文件时,将使用一个 NetStream 实例来表示该视频内容的源,在本例中为视频数据流。使用 NetStream 实例也涉及 NetConnection 对象的使用,该对象是到视频文件的连接,它好比是视频数据馈送的通道。
Camera 类:在通过连接到用户计算机的摄像头处理视频数据时,会使用一个 Camera 实例来表示视频内容的源,即用户的摄像头和它所提供的视频数据。
在加载外部视频时,您可以从标准 Web 服务器加载文件以便进行渐进式下载回放,也可以使用由专门的服务器(如 Adobe 的 Macromedia® Flash® Media Server)传送的视频流。
重要概念和术语
提示点:一个可以放在视频文件内特定时刻的标记,例如,可用作书签以便定位到该时刻或提供与该时刻相关联的其它数据。
编码:以一种格式接收视频数据并将其转换为另一种视频数据格式的过程;例如,接收高分辨率的源视频并将其转换为适合于 Internet 传送的格式。
帧:单一的一段视频信息;每一帧都类似于一个代表某一时刻的快照的静止图像。通过按顺序高速播放各个帧,可产生动画视觉效果。
关键帧:包含帧的完整信息的视频帧。关键帧后面的其它帧仅包含有关它们与关键帧之间的差异的信息,而不包含完整的帧信息。
元数据:有关视频文件的信息,可嵌入在视频文件中并可在加载视频时检索。
渐进式下载:当视频文件从标准 Web 服务器传送时,会使用渐进式下载来加载视频数据,这意味着会按顺序加载视频信息。其好处是不必等待整个文件下载完毕即可开始播放视频;不过,它会阻止您向前跳到视频中尚未加载的部分。
流式传输:渐进式下载的一种替代方法,使用流式传输(有时称为"实流")技术和一台专用视频服务器通过 Internet 传送视频。使用流式传输,用于查看视频的计算机不必一次下载整个视频。为了加快下载速度,在任何时刻,计算机均只需要整个视频信息的一部分。由于使用一台专用服务器来控制视频内容的传送,因此可以在任何时刻访问视频的任何部分,而无需等待其下载完毕后才能进行访问。
了解 Flash 视频 (FLV) 格式
FLV 文件格式包含用 Flash Player 编码以便于传送的音频和视频数据。例如,如果您有 QuickTime 或 Windows Media 视频文件,便可使用编码器(如 Flash Video Encoder 或 Sorenson™ Squeeze)将该文件转换为 FLV 文件。
可以通过将视频导入到 Flash 创作工具,然后导出为 FLV 文件来创建 FLV 文件。可以使用"FLV 导出"插件从受支持的视频编辑应用程序中导出 FLV 文件。
使用外部 FLV 文件可以提供使用导入的视频时不可用的某些功能:
无需降低回放速度就可以在 Flash 文档中使用较长的视频剪辑。可以使用缓存内存的方式来播放外部 FLV 文件,这意味着可以将大型文件分成若干个小片段存储,对其进行动态访问,这种方式比嵌入的视频文件所需的内存更少。
外部 FLV 文件可以和它所在的 Flash 文档具有不同的帧速率。例如,可以将 Flash 文档帧速率设置为 30 帧/秒 (fps),并将视频帧速率设置为 21 fps。与嵌入的视频相比,此项设置可使您更好地控制视频,确保视频顺畅地回放。此项设置还允许您在不改变现有 Flash 内容的前提下以不同的帧速率播放 FLV 文件。
利用外部 FLV 文件,Flash 文档回放就不必在视频文件进行加载时中断。导入的视频文件有时可能需要中断文档回放来执行某些功能,例如,访问 CD-ROM 驱动器。FLV 文件可以独立于 Flash 文档执行功能,因此不会中断回放。
对于外部 FLV 文件,为视频内容加字幕更加简单,这是因为您可以使用事件处理函数访问视频的元数据。
若要从 Web 服务器加载 FLV 文件,则可能需要向您的 Web 服务器注册文件扩展名和 MIME 类型;请查看您的 Web 服务器文档。FLV 文件的 MIME 类型是 video/x-flv。
了解 Video 类
使用 Video 类可以直接在应用程序中显示实时视频流,而无需将其嵌入 SWF 文件中。可以使用 Camera.getCamera() 方法捕获并播放实时视频。还可以使用 Video 类通过 HTTP 或在本地文件系统中回放 FLV 文件。在项目中使用 Video 有多种不同方法:
使用 NetConnection 和 NetStream 类动态加载 FLV 并在 Video 对象中显示视频。
从用户摄像头捕获输入。
使用 FLVPlayback 组件。
尽管 Video 类位于 flash.media 包中,但它继承自 flash.display.DisplayObject 类,因此,所有显示对象功能(如矩阵转换和滤镜)也适用于 Video 实例。
加载视频文件
使用 NetStream 和 NetConnection 类加载视频是一个多步骤过程:
第一步是创建一个 NetConnection 对象。如果连接到没有使用服务器(如 Adobe 的 Flash Media Server 2 或 Adobe Flex)的本地 FLV 文件,则使用 NetConnection 类可通过向 connect() 方法传递值 null,来从 HTTP 地址或本地驱动器播放流式 FLV 文件。
var nc:NetConnection = new NetConnection();
nc.connect(null);
第二步是创建一个 NetStream 对象(该对象将 NetConnection 对象作为参数)并指定要加载的 FLV 文件。以下代码片断将 NetStream 对象连接到指定的 NetConnection 实例,并加载 SWF 文件所在的目录中名为 video.flv 的 FLV:
var ns:NetStream = new NetStream(nc);
ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
ns.play("video.flv");
function asyncErrorHandler(event:AsyncErrorEvent):void
{
// 忽略错误
}
第三步是创建一个新的 Video 对象,并使用 Video 类的 attachNetStream() 方法附加以前创建的 NetStream 对象。然后可以使用 addChild() 方法将该视频对象添加到显示列表中,如以下代码片断所示:
var vid:Video = new Video();
vid.attachNetStream(ns);
addChild(vid);
输入上面的代码后,Flash Player 将尝试加载 SWF 文件所在目录中的 video.flv 视频文件。
控制视频回放
NetStream 类提供了四个用于控制视频回放的主要方法:
pause():暂停视频流的回放。如果视频已经暂停,则调用此方法将不会执行任何操作。
resume():恢复回放暂停的视频流。如果视频已在播放,则调用此方法将不会执行任何操作。
seek():搜寻最接近指定位置(从流的开始位置算起的偏移量,以秒为单位)的关键帧。
togglePause():暂停或恢复流的回放。
注意:没有 stop() 方法。为了停止视频流,必须暂停回放并找到视频流的开始位置。play() 方法不会恢复回放,它用于加载视频文件。
检测视频流的末尾
为了侦听视频流的开始和末尾,需要向 NetStream 实例添加一个事件侦听器以侦听 netStatus 事件。
您专门想要侦听的两段代码为"NetStream.Play.Start"和"NetStream.Play.Stop",它们会在视频回放到达开始和末尾时发出信号。
通过侦听 netStatus 事件 (NetStatusEvent.NET_STATUS),您可以生成一个视频播放器,它在当前视频完成播放后加载播放列表中的下一个视频。
流式传输视频文件
若要流式传输 Flash Media Server 中的文件,可以使用 NetConnection 和 NetStream 类连接到远程服务器实例并播放指定的流。要指定实时消息传递协议 (RTMP) 服务器,请向 NetConnection.connect() 方法传递所需的 RTMP URL(例如"rtmp://localhost/appName/appInstance"),而不传递 null。若要播放指定 Flash Media Server 中的指定实时流或录制流,请为 NetStream.play() 方法传递一个由 NetStream.publish() 发布的实时数据的标识名称,或一个要回放的录制文件名称。有关详细信息,请参阅 Flash Media Server 文档。
了解提示点
并非所有 FLV 文件都包含提示点。虽然有在现有 FLV 文件中嵌入提示点的工具,但提示点通常是在 FLV 编码过程中嵌入在 FLV 文件中的。
您可以对 Flash 视频使用几种不同类型的提示点。可以使用 ActionScript 与在创建 FLV 文件时嵌入到 FLV 文件中的提示点进行交互,也可以与用 ActionScript 创建的提示点进行交互。
导航提示点:您可以在编码 FLV 文件时,将导航提示点嵌入到 FLV 流和 FLV 元数据包中。使用导航提示点可以使用户搜索到文件的指定部分。
事件提示点:您可以在编码 FLV 文件时,将事件提示点嵌入到 FLV 流和 FLV 元数据包中。还可以编写代码来处理在 FLV 回放期间于指定点上触发的事件。
ActionScript 提示点:使用 ActionScript 代码创建的外部提示点。您可以编写代码来触发这些与视频回放有关的提示点。这些提示点的精确度要低于嵌入的提示点(最高时相差 1/10 秒),因为视频播放器单独跟踪这些提示点。
由于导航提示点会在指定的提示点位置创建一个关键帧,因此可以使用代码将视频播放器的播放头移动到该位置。您可以在 FLV 文件中设置一些特殊点,让用户搜索这些点。例如,视频可能会具有多个章节或段,在这种情况下您就可以在视频文件中嵌入导航提示点,以此方式来控制视频。
如果您计划创建一个应用程序,希望用户能在其中导航至提示点,则应在编码文件时创建并嵌入提示点,而不应使用 ActionScript 提示点。您应将提示点嵌入到 FLV 文件中,因为这些提示点需要更加精确的处理。有关在 FLV 文件中嵌入提示点的详细信息,请参阅《使用 Flash》中的"嵌入提示点"。
您可以通过编写 ActionScript 来访问提示点参数。提示点参数是从 onCuePoint 回调处理函数接收的事件对象的一部分。
若要在视频到达特定提示点时在代码中触发特定动作,请使用 NetStream.onCuePoint 事件处理函数。
为 onCuePoint 和 onMetaData 编写回调方法
当播放器到达特定提示点或收到特定元数据时,您可以在应用程序中触发动作。若要触发此类动作,请使用 onCuePoint 和 onMetaData 事件处理函数。必须为这些处理函数编写回调方法,否则,Flash Player 可能会引发错误。
将 NetStream 对象的 client 属性设置为一个 Object
通过将 client 属性设置为一个 Object 或设置为 NetStream 的一个子类,可以重新发送 onMetaData 和 onCuePoint 回调方法或彻底忽略这些方法。
将 NetStream 对象的 client 属性设置为 this
通过将 client 属性设置为 this,Flash Player 会在当前范围内查找 onMetaData() 和 onCuePoint() 方法。调用 onMetaData 或 onCuePoint 回调处理函数时,如果不存在处理该回调的方法时,不会生成错误。
使用提示点
以下示例使用一个简单的 for..in 循环来遍历 onCuePoint 回调处理函数的 infoObject 参数中的每个属性。
使用视频元数据
您可以使用 onMetaData 回调处理函数来查看 FLV 文件中的元数据信息。元数据包含 FLV 文件的相关信息,如持续时间、宽度、高度和帧速率。添加到 FLV 文件中的元数据信息取决于编码 FLV 文件时所使用的软件或添加元数据信息时所使用的软件。
如果您的视频没有音频,则与音频相关的元数据信息(如 audiodatarate)将返回 undefined,因为在编码期间没有将音频信息添加到元数据中。
onMetaData 的信息对象
下表显示视频元数据的可能值
参数
描述
audiocodecid
一个数字,指示已使用的音频编解码器(编码/解码技术)。
audiodatarate
一个数字,指示音频的编码速率,以每秒千字节为单位。
audiodelay
一个数字,指示原始 FLV 文件的"time 0"在 FLV 文件中保持多长时间。为了正确同步音频,视频内容需要有少量的延迟。
canSeekToEnd
一个布尔值,如果 FLV 文件是用最后一帧(它允许定位到渐进式下载影片剪辑的末尾)上的关键帧编码的,则该值为 true。如果 FLV 文件不是用最后一帧上的关键帧编码的,则该值为 false。
cuePoints
嵌入在 FLV 文件中的提示点对象组成的数组,每个提示点对应一个对象。如果 FLV 文件不包含任何提示点,则值是未定义的。每个对象都具有以下属性:
Type、 name、 time 、parameters 。
duration
一个数字,以秒为单位指定 FLV 文件的持续时间。
framerate
一个数字,表示 FLV 文件的帧速率。
height
一个数字,以像素为单位表示 FLV 文件的高度。
videocodecid
一个数字,表示用于对视频进行编码的编解码器版本。
videodatarate
一个数字,表示 FLV 文件的视频数据速率。
width
一个数字,以像素为单位表示 FLV 文件的宽度。
下表显示 videocodecid 参数的可能值:
videocodecid
编解码器名称
2
Sorenson H.263
3
屏幕视频(仅限 SWF 7 和更高版本)
4
VP6(仅限 SWF 8 和更高版本)
5
带有 Alpha 通道的 VP6 视频(仅限 SWF 8 和更高版本)
下表显示 audiocodecid 参数的可能值:
audiocodecid
编解码器名称
0
未压缩
1
ADPCM
2
mp3
5
Nellymoser 8kHz 单声
6
Nellymoser
捕获摄像头输入
除了外部视频文件外,附加到用户计算机上的摄像头也可以作为您使用 ActionScript 进行显示和操作的视频数据的来源。Camera 类是 ActionScript 中内置的机制,用于使用计算机摄像头。
了解 Camera 类
使用 Camera 对象可以连接到用户的本地摄像头并在本地广播视频(回放给用户),或将其广播到远程服务器(比如 Flash Media Server)。
使用 Camera 类可以访问有关用户摄像头的以下各种信息:
Flash Player 可以使用用户计算机上安装的哪些摄像头
是否安装了摄像头
是否允许 Flash Player 访问用户摄像头
哪个摄像头当前处于活动状态
正在捕获的视频的宽度和高度
Camera 类包括多个有用的方法和属性,通过这些方法和属性可以使用 Camera 对象。
在屏幕上显示摄像头内容
连接到摄像头所需的代码比使用 NetConnection 和 NetStream 类加载 FLV 的代码少。由于需要有用户许可才能让 Flash Player 连接到摄像头,并且只有在连接到摄像头后才能访问摄像头,因此,Camera 类的使用可能很快就会变得非常麻烦。
以下代码演示如何使用 Camera 类连接到用户的本地摄像头:
var cam:Camera = Camera.getCamera();
var vid:Video = new Video();
vid.attachCamera(cam);
addChild(vid);
Camera 类不具有构造函数方法。若要创建新的 Camera 实例,请使用静态 Camera.getCamera() 方法。
设计摄像头应用程序
在编写需要连接到用户摄像头的应用程序时,需要在代码中考虑以下事项:
检查用户当前是否安装了摄像头。
检查用户是否显式允许 Flash Player 访问其摄像头。出于安全原因,播放器会显示"Flash Player 设置"对话框,让用户选择允许还是拒绝对其摄像头的访问。这样可以防止 Flash Player 在未经用户许可的情况下连接到其摄像头并广播视频流。如果用户单击允许,则应用程序即可连接到用户的摄像头。如果用户单击拒绝,则应用程序将无法访问用户的摄像头。应用程序始终应适当地处理这两种情况。
连接到用户摄像头
连接到用户摄像头时,执行的第一步是通过创建一个类型为 Camera 的变量并将其初始化为静态 Camera.getCamera() 方法的返回值来创建一个新的 Camera 实例。
下一步是创建一个新的视频对象并向其附加 Camera 对象。
第三步是向显示列表中添加该视频对象。由于 Camera 类不会扩展 DisplayObject 类,它不能直接添加到显示列表中,因此需要执行第 2 步和第 3 步。若要显示摄像头捕获的视频,需要创建一个新的视频对象并调用 attachCamera() 方法。
以下代码演示这三个步骤:
var cam:Camera = Camera.getCamera();
var vid:Video = new Video();
vid.attachCamera(cam);
addChild(vid);
注意,如果用户未安装摄像头,Flash Player 将不显示任何内容。
验证是否已安装摄像头
在尝试对 Camera 实例使用任何方法或属性之前,您需要验证用户是否已安装了摄像头。检查用户是否已安装摄像头有两种方式:
检查静态 Camera.names 属性,该属性包含可用摄像头名称的数组。此数组通常具有一个或几个字符串,因为多数用户不太可能同时安装多个摄像头。以下代码演示如何检查 Camera.names 属性以查看用户是否具有可用的摄像头:
if (Camera.names.length > 0)
{
trace("用户未安装摄像头。");
}
else
{
var cam:Camera = Camera.getCamera(); //获取默认摄像头。
}

检查静态 Camera.getCamera() 方法的返回值。如果没有摄像头可用或未安装摄像头,则此方法将返回 null,否则返回对 Camera 对象的引用。以下代码演示如何检查 Camera.getCamera() 方法以查看用户是否具有可用的摄像头:
var cam:Camera = Camera.getCamera();
if (cam == null)
{
trace("用户未安装摄像头。");
}
else
{
trace("用户至少安装了 1 个摄像头。");
}

由于 Camera 类不会扩展 DisplayObject 类,因此不能通过使用 addChild() 方法将它直接添加到显示列表中。为了显示摄像头捕获的视频,您需要创建一个新的 Video 对象并对 Video 实例调用 attachCamera() 方法。
以下代码片断演示在存在摄像头的情况下如何附加摄像头;如果不存在摄像头,Flash Player 将不显示任何内容:
var cam:Camera = Camera.getCamera();
if (cam != null)
{
var vid:Video = new Video();
vid.attachCamera(cam);
addChild(vid);
}
检测摄像头的访问权限
在可以显示摄像头输出之前,用户必须显式允许 Flash Player 访问该摄像头。在调用 attachCamera() 方法后,Flash Player 会显示"Flash Player 设置"对话框,提示用户允许或拒绝 Flash Player 访问摄像头或麦克风。如果用户单击"允许"按钮,则会在舞台上的 Video 实例中显示摄像头输出。如果用户单击"拒绝"按钮,则 Flash Player 将无法连接到摄像头,且 Video 对象将不显示任何内容。
如果想要检测用户是否允许访问其摄像头,可以侦听摄像头的 status 事件 (StatusEvent.STATUS),如以下代码所示:
var cam:Camera = Camera.getCamera();
if (cam != null)
{
cam.addEventListener(StatusEvent.STATUS, statusHandler);
var vid:Video = new Video();
vid.attachCamera(cam);
addChild(vid);
}
function statusHandler(event:StatusEvent):void
{
// 当用户在"Flash Player 设置"对话框中单击
// "允许"或"拒绝"按钮时调度此事件。
trace(event.code); //"Camera.Muted"或"Camera.Unmuted"
}
一旦用户单击"允许"或"拒绝"后,即会调用 statusHandler() 函数。使用以下两种方法之一可以检测用户单击了哪个按钮:
statusHandler() 函数的 event 参数包含一个 code 属性,其中包含字符串"Camera.Muted"或"Camera.Unmuted"。如果值为"Camera.Muted",则说明用户单击了"拒绝"按钮,Flash Player 将无法访问该摄像头。在下面的代码片段中您会看到此情况的一个示例:
function statusHandler(event:StatusEvent):void
{
switch (event.code)
{
case "Camera.Muted":
trace("用户单击了"拒绝"。");
break;
case "Camera.Unmuted":
trace("用户单击了"接受"。");
break;
}
}

Camera 类包含一个名为 muted 的只读属性,它可以指明用户在 Flash Player 的"隐私"面板中是拒绝访问摄像头 (true) 还是允许访问摄像头 (false)。在下面的代码片段中您会看到此情况的一个示例:
function statusHandler(event:StatusEvent):void
{
if (cam.muted)
{
trace("用户单击了"拒绝"。");
}
else
{
trace("用户单击了"接受"。");
}
}

通过检查将要调度的 status 事件,您可以编写处理用户接受或拒绝访问摄像头的代码并进行相应的清理。例如,如果用户单击"拒绝"按钮,则您可以向用户显示一条消息,说明他们如果想要参加视频聊天的话,需要单击"允许";或者,您也可以确保删除显示列表中的 Video 对象以释放系统资源。
最优化视频品质
默认情况下,Video 类的新实例为 320 像素宽乘以 240 像素高。为了最优化视频品质,应始终确保视频对象与 Camera 对象返回的视频具有相同的尺寸。使用 Camera 类的 width 和 height 属性,您可以获取 Camera 对象的宽度和高度,然后将该视频对象的 width 和 height 属性设置为与 Camera 对象的尺寸相符,也可以将 Camera 对象的宽度和高度传递给 Video 类的构造函数方法,如以下代码片断所示:
var cam:Camera = Camera.getCamera();
if (cam != null)
{
var vid:Video = new Video(cam.width, cam.height);
vid.attachCamera(cam);
addChild(vid);
}


由于 getCamera() 方法返回对 Camera 对象的引用(在没有可用摄像头时返回 null),因此,即使用户拒绝访问其摄像头,您也可以访问 Camera 对象的方法和属性。这样可以使用摄像头的本机高度和宽度设置视频实例的尺寸。
var vid:Video;
var cam:Camera = Camera.getCamera();

if (cam == null)
{
trace("找不到可用的摄像头。");
}
else
{
trace("找到摄像头: " + cam.name);
cam.addEventListener(StatusEvent.STATUS, statusHandler);
vid = new Video();
vid.attachCamera(cam);
}
function statusHandler(event:StatusEvent):void
{
if (cam.muted)
{
trace("无法连接到活动摄像头。");
}
else
{
// 调整 Video 对象的大小,使之与摄像头设置相符,并
// 将该视频添加到显示列表中。
vid.width = cam.width;
vid.height = cam.height;
addChild(vid);
}
// 删除 status 事件侦听器。
cam.removeEventListener(StatusEvent.STATUS, statusHandler);
}

监视回放条件
Camera 类包含多个属性,这些属性允许您监视 Camera 对象的当前状态。
向服务器发送视频
如果您要生成涉及 Video 或 Camera 对象的更为复杂的应用程序,可以使用 Flash Media Server 提供的流媒体功能和开发环境组合来创建媒体应用程序并将它提供给广泛的目标用户。开发人员可以使用这一组合来创建应用程序,如 Video on Demand、实时 Web 事件广播和 mp3 流,以及视频博客、视频消息传送和多媒体聊天环境。
处理声音
声音处理基础知识
处理声音简介
就像计算机可以采用数字格式对图像进行编码、将它们存储在计算机上以及检索它们以便在屏幕上显示它们一样,计算机可以捕获并编码数字音频(声音信息的计算机表示形式)以及对其进行存储和检索,以通过连接到计算机上的扬声器进行回放。一种回放声音的方法是使用 Adobe Flash Player 和 ActionScript。
将声音数据转换为数字形式后,它具有各种不同的特性,如声音的音量以及它是立体声还是单声道声音。在 ActionScript 中回放声音时,您也可以调整这些特性;例如,使声音变得更大,或者使其像是来自某个方向。
在 ActionScript 中控制声音之前,您需要先将声音信息加载到 Flash Player 中。可以使用四种方法将音频数据加载到 Flash Player 中,以便通过 ActionScript 对其进行使用。您可以将外部声音文件(如 mp3 文件)加载到 SWF 中;在创建 SWF 文件时将声音信息直接嵌入到其中;使用连接到用户计算机上的麦克风来获取音频输入,以及访问从服务器流式传输的声音数据。
从外部声音文件加载声音数据时,您可以在仍加载其余声音数据的同时开始回放声音文件的开头部分。
虽然可以使用各种不同的声音文件格式对数字音频进行编码,但是 ActionScript 3.0 和 Flash Player 支持以 mp3 格式存储的声音文件。它们不能直接加载或播放其它格式的声音文件,如 WAV 或 AIFF。
在 ActionScript 中处理声音时,您可能会使用 flash.media 包中的某些类。通过使用 Sound 类,您可以加载声音文件并开始回放以获取对音频信息的访问。开始播放声音后,Flash Player 可为您提供对 SoundChannel 对象的访问。由于已加载的音频文件只能是您在用户计算机上播放的几种声音之一,因此,所播放的每种单独的声音使用其自己的 SoundChannel 对象;混合在一起的所有 SoundChannel 对象的组合输出是实际通过计算机扬声器播放的声音。可以使用此 SoundChannel 实例来控制声音的属性以及将其停止回放。最后,如果要控制组合音频,您可以通过 SoundMixer 类对混合输出进行控制。
重要概念和术语
波幅 (Amplitude):声音波形上的点与零或平衡线之间的距离。
比特率 (Bit rate):每秒为声音文件编码或流式传输的数据量。对于 mp3 文件,比特率通常是以每秒千位数 (kbps) 来表述的。较高的比特率通常意味着较高品质的声音波形。
缓冲 (Buffering):在回放之前接收和存储声音数据。
mp3:MPEG-1 Audio Layer 3 (mp3) 是一种常用的声音压缩格式。
声相 (Panning):将音频信号放在立体声声场中左声道和右声道之间。
峰值 (Peak):波形中的最高点。
采样率 (Sampling rate):定义在生成数字信号时每秒从模拟音频信号采集的样本数。标准光盘音频的采样率为 44.1 kHz 或每秒 44,100 个样本。
流式传输 (Streaming):此过程是指,在仍从服务器加载声音文件或视频文件的后面部分的同时播放该文件的前面部分。
音量 (Volume):声音的响度。
波形 (Waveform):声音信号波幅随时间变化的图形形状。
了解声音体系结构
应用程序可以从以下四种主要来源加载声音数据:
在运行时加载的外部声音文件
在应用程序的 SWF 文件中嵌入的声音资源
来自连接到用户系统上的麦克风的声音数据
从远程媒体服务器流式传输的声音数据,如 Flash Media Server
可以在回放之前完全加载声音数据,也可以进行流式传输,即在仍进行加载的同时回放这些数据。
ActionScript 3.0 和 Flash Player 支持以 mp3 格式存储的声音文件。它们不能直接加载或播放其它格式的声音文件,如 WAV 或 AIFF。
使用 Adobe Flash CS3 Professional,可以导入 WAV 或 AIFF 声音文件,然后将其以 mp3 格式嵌入应用程序的 SWF 文件中。Flash 创作工具还可压缩嵌入的声音文件以减小文件大小,但会降低声音的品质。
ActionScript 3.0 声音体系结构使用 flash.media 包中的以下类。




描述
flash.media.Sound
Sound 类处理声音加载、管理基本声音属性以及启动声音播放。
flash.media.SoundChannel
当应用程序播放 Sound 对象时,将创建一个新的 SoundChannel 对象来控制回放。SoundChannel 对象控制声音的左和右回放声道的音量。播放的每种声音具有其自己的 SoundChannel 对象。
flash.media.SoundLoaderContext
SoundLoaderContext 类指定在加载声音时使用的缓冲秒数,以及 Flash Player 在加载文件时是否从服务器中查找跨域策略文件。SoundLoaderContext 对象用作 Sound.load() 方法的参数。
flash.media.SoundMixer
SoundMixer 类控制与应用程序中的所有声音有关的回放和安全属性。实际上,可通过一个通用 SoundMixer 对象将多个声道混合在一起,因此,该 SoundMixer 对象中的属性值将影响当前播放的所有 SoundChannel 对象。
flash.media.SoundTransform
SoundTransform 类包含控制音量和声相的值。可以将 SoundTransform 对象应用于单个 SoundChannel 对象、全局 SoundMixer 对象或 Microphone 对象等。
flash.media.ID3Info
ID3Info 对象包含一些属性,它们表示通常存储在 mp3 声音文件中的 ID3 元数据信息。
flash.media.Microphone
Microphone 类表示连接到用户计算机上的麦克风或其它声音输入设备。可以将来自麦克风的音频输入传送到本地扬声器或发送到远程服务器。Microphone 对象控制其自己的声音流的增益、采样率以及其它特性。
加载和播放的每种声音需要其自己的 Sound 类和 SoundChannel 类的实例。然后,全局 SoundMixer 类在回放期间将来自多个 SoundChannel 实例的输出混合在一起。
Sound、SoundChannel 和 SoundMixer 类不能用于从麦克风或流媒体服务器(如 Flash Media Server)中获取的声音数据。
加载外部声音文件
Sound 类的每个实例可加载并触发特定声音资源的回放。应用程序无法重复使用 Sound 对象来加载多种声音。如果它要加载新的声音资源,则应创建一个新的 Sound 对象。Sound() 构造函数接受一个 URLRequest 对象作为其第一个参数。当提供 URLRequest 参数的值后,新的 Sound 对象将自动开始加载指定的声音资源。
除了最简单的情况下,应用程序都应关注声音的加载进度,并监视在加载期间出现的错误。。较为稳妥的作法是等待声音完全加载后,再让用户执行可能启动声音播放的动作。
Sound 对象将在声音加载过程中调度多种不同的事件。应用程序可以侦听这些事件以跟踪加载进度,并确保在播放之前完全加载声音。下表列出了可以由 Sound 对象调度的事件。
事件
描述
open (Event.OPEN)
就在声音加载操作开始之前进行调度。
progress (ProgressEvent.PROGRESS)
从文件或流接收数据时,在声音加载过程中定期进行调度。
id3 (Event.ID3)
当存在可用于 mp3 声音的 ID3 数据时进行调度。
complete (Event.COMPLETE)
在加载了所有声音资源后进行调度。
ioError (IOErrorEvent.IO_ERROR)
在以下情况下进行调度:找不到声音文件,或者在收到所有声音数据之前加载过程中断。
监视声音加载过程
声音文件可能很大,而需要花很长时间进行加载。尽管 Flash Player 允许应用程序甚至在完全加载声音之前播放声音,但您可能需要向用户指示已加载了多少声音数据以及已播放了多少声音。
Sound 类调度以下两个事件,它们可使声音加载进度显示变得相对比较简单:ProgressEvent.PROGRESS 和 Event.COMPLETE。
Sound 对象上也提供了相同的 bytesLoaded 和 bytesTotal 属性。
处理嵌入的声音
对于用作应用程序用户界面中的指示器的较小声音(如在单击按钮时播放的声音),使用嵌入的声音非常有用(而不是从外部文件加载声音)。
处理声音流文件
如果在仍加载声音文件或视频文件数据的同时回放该文件,则认为是流式传输。通常,将对从远程服务器加载的外部声音文件进行流式传输,以使用户不必等待加载完所有声音数据再收听声音。
SoundMixer.bufferTime 属性表示 Flash Player 在允许播放声音之前应收集多长时间的声音数据(以毫秒为单位)。也就是说,如果将 bufferTime 属性设置为 5000,在开始播放声音之前,Flash Player 将从声音文件中加载至少相当于 5000 毫秒的数据。SoundMixer.bufferTime 默认值为 1000。
通过在加载声音时显式地指定新的 bufferTime 值,应用程序可以覆盖单个声音的全局 SoundMixer.bufferTime 值。要覆盖默认缓冲时间,请先创建一个新的 SoundLoaderContext 类实例,设置其 bufferTime 属性,然后将其作为参数传递给 Sound.load() 方法。
当回放继续进行时,Flash Player 尝试将声音缓冲保持在相同大小或更大。如果声音数据的加载速度比回放快,回放将继续进行而不会中断。但是,如果数据加载速率由于网络限制而减慢,播放头可能会到达声音缓冲区的结尾。如果发生这种情况,将暂停回放,但会在加载更多声音数据后自动恢复回放。
要查明暂停回放是否是由于 Flash Player 正在等待加载数据,请使用 Sound.isBuffering 属性。
播放声音
播放加载的声音非常简便,您只需为 Sound 对象调用 Sound.play() 方法。
使用 ActionScript 3.0 回放声音时,您可以执行以下操作:
从特定起始位置播放声音
暂停声音并稍后从相同位置恢复回放
准确了解何时播放完声音
跟踪声音的回放进度
在播放声音的同时更改音量或声相
要在回放期间执行这些操作,请使用 SoundChannel、SoundMixer 和 SoundTransform 类。
SoundChannel 类控制一种声音的回放。可以将 SoundChannel.position 属性视为播放头,以指示所播放的声音数据中的当前位置。
当应用程序调用 Sound.play() 方法时,将创建一个新的 SoundChannel 类实例来控制回放。
通过将特定起始位置(以毫秒为单位)作为 Sound.play() 方法的 startTime 参数进行传递,应用程序可以从该位置播放声音。它也可以通过在 Sound.play() 方法的 loops 参数中传递一个数值,指定快速且连续地将声音重复播放固定的次数。
暂停和恢复播放声音
如果应用程序播放很长的声音(如歌曲或播客),您可能需要让用户暂停和恢复回放这些声音。实际上,无法在 ActionScript 中的回放期间暂停声音;而只能将其停止。但是,可以从任何位置开始播放声音。您可以记录声音停止时的位置,并随后从该位置开始重放声音。
在播放声音的同时,SoundChannel.position 属性指示当前播放到的声音文件位置。应用程序可以在停止播放声音之前存储位置值,如下所示:
var pausePosition:int = channel.position;
channel.stop();
要恢复播放声音,请传递以前存储的位置值,以便从声音以前停止的相同位置重新启动声音。
channel = snd.play(pausePosition);
监视回放
应用程序可能需要了解何时停止播放某种声音,以便开始播放另一种声音,或者清除在以前回放期间使用的某些资源。SoundChannel 类在其声音完成播放时将调度 Event.SOUND_COMPLETE 事件。
SoundChannel 类在回放期间不调度进度事件。要报告回放进度,应用程序可以设置其自己的计时机制并跟踪声音播放头的位置。
要计算已播放的声音百分比,您可以将 SoundChannel.position 属性值除以所播放的声音数据长度:
var playbackPercent:uint = 100 * (channel.position / snd.length);
但是,仅当在开始回放之前完全加载了声音数据,此代码才会报告精确的回放百分比。Sound.length 属性显示当前加载的声音数据的大小,而不是整个声音文件的最终大小。要跟踪仍在加载的声音流的回放进度,应用程序应估计完整声音文件的最终大小,并在其计算中使用该值。您可以使用 Sound 对象的 bytesLoaded 和 bytesTotal 属性来估计声音数据的最终长度,如下所示:
var estimatedLength:int =
Math.ceil(snd.length / (snd.bytesLoaded / snd.bytesTotal));
var playbackPercent:uint = 100 * (channel.position / estimatedLength);
停止声音流
在进行流式传输的声音(即,在播放的同时仍在加载声音)的回放过程中,有一个奇怪的现象。当应用程序对回放声音流的 SoundChannel 实例调用 SoundChannel.stop() 方法时,声音回放在一个帧处停止,随后在下一帧处从声音开头重新回放。发生这种情况是因为,声音加载过程仍在进行当中。要停止声音流加载和回放,请调用 Sound.close() 方法。
加载和播放声音时的安全注意事项
可以根据 Flash Player 安全模型来限制应用程序访问声音数据的功能。每种声音受两种不同的安全沙箱的限制:内容本身的沙箱("内容沙箱")以及加载和播放声音的应用程序或对象的沙箱("所有者沙箱")。
内容沙箱控制使用 id3 属性还是 SoundMixer.computeSpectrum() 方法从声音提取详细声音数据。它不会限制声音文件本身的加载或播放。
声音文件的原始域定义了内容沙箱的安全限制。一般来说,如果某个声音文件与加载该文件的应用程序或对象的 SWF 文件位于相同的域或文件夹中,则应用程序或对象具有该声音文件的完全访问权限。如果声音来自与应用程序不同的域,仍可以使用跨域策略文件将其加载到内容沙箱中。
应用程序可以将带有 checkPolicyFile 属性的 SoundLoaderContext 对象作为参数传递给 Sound.load() 方法。如果将 checkPolicyFile 属性设置为 true,则会通知 Flash Player 在从中加载声音的服务器上查找跨域策略文件。如果存在跨域策略文件,并且它为执行加载的 SWF 文件所在的域授予了访问权限,则该 SWF 文件可以加载声音文件、访问 Sound 对象的 id3 属性以及为加载的声音调用 SoundMixer.computeSpectrum() 方法。
所有者沙箱控制声音的本地回放。所有者沙箱是由开始播放声音的应用程序或对象定义的。
只要当前播放的所有 SoundChannel 对象中的声音符合以下条件,SoundMixer.stopAll() 方法就会将它们静音:
声音是由相同所有者沙箱中的对象启动的。
声音来自具有跨域策略文件(为调用 SoundMixer.stopAll() 方法的应用程序或对象所在的域授予访问权限)的源。
要查明 SoundMixer.stopAll() 方法是否确实停止了所有播放的声音,应用程序可以调用 SoundMixer.areSoundsInaccessible() 方法。如果该方法返回值 true,则当前所有者沙箱无法控制播放的某些声音,SoundMixer.stopAll() 方法不会将其停止。
SoundMixer.stopAll() 方法还会阻止播放头继续播放从外部文件加载的所有声音。但是,如果动画移动到一个新帧,FLA 文件中嵌入的声音以及使用 Flash 创作工具附加到时间轴中的帧上的声音可能会重新开始播放。
控制音量和声相
单个 SoundChannel 对象控制声音的左和右立体声声道。如果 mp3 声音是单声道声音,SoundChannel 对象的左和右立体声声道将包含完全相同的波形。
可通过使用 SoundChannel 对象的 leftPeak 和 rightPeak 属性来查明所播放的声音的每个立体声声道的波幅。这些属性显示声音波形本身的峰值波幅。它们并不表示实际回放音量。实际回放音量是声音波形的波幅以及 SoundChannel 对象和 SoundMixer 类中设置的音量值的函数。
在回放期间,可以使用 SoundChannel 对象的 pan 属性为左和右声道分别指定不同的音量级别。pan 属性可以具有范围从 -1 到 1 的值,其中,-1 表示左声道以最大音量播放,而右声道处于静音状态;1 表示右声道以最大音量播放,而左声道处于静音状态。介于 -1 和 1 之间的数值为左和右声道值设置一定比例的值,值 0 表示两个声道以均衡的中音量级别播放。
可以在播放声音的同时更改音量和声相,方法是:设置 SoundTransform 对象的 pan 或 volume 属性,然后将该对象作为 SoundChannel 对象的 soundTransform 属性进行应用。
也可以通过使用 SoundMixer 类的 soundTransform 属性,同时为所有声音设置全局音量和声相值。
也可以使用 SoundTransform 对象为 Microphone 对象、Sprite 对象和 SimpleButton 对象设置音量和声相值。
处理声音元数据
使用 mp3 格式的声音文件可以采用 ID3 标签格式来包含有关声音的其它数据。
并非每个 mp3 文件都包含 ID3 元数据。当 Sound 对象加载 mp3 声音文件时,如果该声音文件包含 ID3 元数据,它将调度 Event.ID3 事件。要防止出现运行时错误,应用程序应等待接收 Event.ID3 事件后,再访问加载的声音的 Sound.id3 属性。
访问原始声音数据
通过使用 SoundMixer.computeSpectrum() 方法,应用程序可以读取当前所播放的波形的原始声音数据。如果当前播放多个 SoundChannel 对象,SoundMixer.computeSpectrum() 方法将显示混合在一起的每个 SoundChannel 对象的组合声音数据。
声音数据是作为 ByteArray 对象(包含 512 个字节的数据)返回的,其中的每个字节包含一个介于 -1 和 1 之间的浮点值。这些值表示所播放的声音波形中的点的波幅。这些值是分为两个组(每组包含 256 个值)提供的,第一个组用于左立体声声道,第二个组用于右立体声声道。
如果将 FFTMode 参数设置为 true,SoundMixer.computeSpectrum() 方法将返回频谱数据,而非波形数据。频谱显示按声音频率(从最低频率到最高频率)排列的波幅。可以使用快速傅立叶变换 (FFT) 将波形数据转换为频谱数据。生成的频谱值范围介于 0 和约 1.414(2 的平方根)之间。
computeSpectrum() 方法也可以返回已在较低比特率重新采样的数据。通常,这会产生更平滑的波形数据或频率数据,但会以牺牲细节为代价。stretchFactor 参数控制 computeSpectrum() 方法数据的采样率。如果将 stretchFactor 参数设置为 0(默认值),则以采样率 44.1 kHz 采集声音数据样本。stretchFactor 参数值每连续增加 1,采样率就减小一半,因此,值 1 指定采样率 22.05 kHz,值 2 指定采样率 11.025 kHz,依此类推。当使用较高的 stretchFactor 值时,computeSpectrum() 方法仍会为每个立体声声道返回 256 个字节。
SoundMixer.computeSpectrum() 方法具有一些限制:
由于来自麦克风或 RTMP 流的声音数据不是通过全局 SoundMixer 对象传递的,因此,SoundMixer.computeSpectrum() 方法不会从这些源返回数据。
如果播放的一种或多种声音来自当前内容沙箱以外的源,安全限制将导致 SoundMixer.computeSpectrum() 方法引发错误。
捕获声音输入
应用程序可通过 Microphone 类连接到用户系统上的麦克风或其它声音输入设备,并将输入音频广播到该系统的扬声器,或者将音频数据发送到远程服务器,如 Flash Media Server。
访问麦克风
Microphone 类没有构造函数方法。相反,应使用静态 Microphone.getMicrophone() 方法来获取新的 Microphone 实例。
不使用参数调用 Microphone.getMicrophone() 方法时,将返回在用户系统上发现的第一个声音输入设备。系统可能连接了多个声音输入设备。应用程序可以使用 Microphone.names 属性来获取所有可用声音输入设备名称的数组。然后,它可以使用 index 参数(与数组中的设备名称的索引值相匹配)来调用 Microphone.getMicrophone() 方法。系统可能没有连接麦克风或其它声音输入设备。可以使用 Microphone.names 属性或 Microphone.getMicrophone() 方法来检查用户是否安装了声音输入设备。如果用户未安装声音输入设备,则 names 数组的长度为零,并且 getMicrophone() 方法返回值 null。
当应用程序调用 Microphone.getMicrophone() 方法时,Flash Player 将显示"Flash Player 设置"对话框,它提示用户允许或拒绝 Flash Player 对系统上的摄像头和麦克风的访问。在用户单击此对话框中的"允许"或"拒绝"按钮后,将调度 StatusEvent。该 StatusEvent 实例的 code 属性指示是允许还是拒绝对麦克风的访问。如果允许访问,StatusEvent.code 属性将包含"Microphone.Unmuted";如果拒绝访问,则包含"Microphone.Muted"。
当用户允许或拒绝对麦克风的访问时,Microphone.muted 属性将被分别设置为 true 或 false。但是,在调度 StatusEvent 之前,不会在 Microphone 实例上设置 muted 属性,因此,应用程序还应等待调度 StatusEvent.STATUS 事件后再检查 Microphone.muted 属性。
将麦克风音频传送到本地扬声器
可以使用参数值 true 调用 Microphone.setLoopback() 方法,以将来自麦克风的音频输入传送到本地系统扬声器。
如果将来自本地麦克风的声音传送到本地扬声器,则会存在创建音频回馈循环的风险,这可能会导致非常大的振鸣声,并且可能会损坏声音硬件。使用参数值 true 调用 Microphone.setUseEchoSuppression() 方法可降低发生音频回馈的风险,但不会完全消除该风险。Adobe 建议您始终在调用 Microphone.setLoopback(true) 之前调用 Microphone.setUseEchoSuppression(true),除非您确信用户使用耳机来回放声音,或者使用除扬声器以外的某种设备。
更改麦克风音频
应用程序可以使用两种方法更改来自麦克风的音频数据。第一,它可以更改输入声音的增益,这会有效地将输入值乘以指定的数值以创建更大或更小的声音。Microphone.gain 属性接受介于 0 和 100 之间的数值(含 0 和 100)。值 50 相当于乘数 1,它指定正常音量。值 0 相当于乘数 0,它可有效地将输入音频静音。大于 50 的值指定的音量高于正常音量。
应用程序也可以更改输入音频的采样率。较高的采样率可提高声音品质,但它们也会创建更密集的数据流(使用更多的资源进行传输和存储)。Microphone.rate 属性表示以千赫 (kHz) 为单位测量的音频采样率。默认采样率是 8 kHz。如果麦克风支持较高的采样率,您可以将 Microphone.rate 属性设置为高于 8 kHz 的值。
检测麦克风活动
为节省带宽和处理资源,Flash Player 将尝试检测何时麦克风不传输声音。当麦克风的活动级别处于静音级别阈值以下一段时间后,Flash Player 将停止传输音频输入,并调度一个简单的 ActivityEvent。
Microphone 类的以下三个属性用于监视和控制活动检测:
activityLevel 只读属性指示麦克风检测的音量,范围从 0 到 100。
silenceLevel 属性指定激活麦克风并调度 ActivityEvent.ACTIVITY 事件所需的音量。silenceLevel 属性也使用从 0 到 100 的范围,默认值为 10。
silenceTimeout 属性描述活动级别处于静音级别以下多长时间(以毫秒为单位)后,才会调度 ActivityEvent.ACTIVITY 事件以指示麦克风现在处于静音状态。silenceTimeout 默认值是 2000。
Microphone.silenceLevel 属性和 Microphone.silenceTimeout 属性都是只读的,但可以使用 Microphone.setSilenceLevel() 方法来更改它们的值。
在某些情况下,在检测到新活动时激活麦克风的过程可能会导致短暂的延迟。通过将麦克风始终保持活动状态,可以消除此类激活延迟。应用程序可以调用 Microphone.setSilenceLevel() 方法并将 silenceLevel 参数设置为零,以通知 Flash Player 将麦克风保持活动状态并持续收集音频数据,即使未检测到任何声音也是如此。反之,如果将 silenceLevel 参数设置为 100,则可以完全禁止激活麦克风。
向媒体服务器发送音频以及从中接收音频
将 ActionScript 与 Flash Media Server 等流媒体服务器配合使用时,可以使用额外的音频功能。
特别地,应用程序可以将 Microphone 对象附加到 NetStream 对象上,并将数据直接从用户麦克风传输到服务器。也可以将音频数据从服务器流式传输到 Flash 或 Flex 应用程序,并将其作为 MovieClip 的一部分或使用 Video 对象进行回放。
捕获用户输入
用户输入基础知识
捕获用户输入简介
用户交互(无论是通过键盘、鼠标、摄像头还是这些设备的组合)是交互性的基础。在 ActionScript 3.0 中,识别和响应用户交互主要涉及事件侦听。
InteractiveObject 类是 DisplayObject 类的一个子类,它提供了处理用户交互所需的事件和功能的通用结构。您无法直接创建 InteractiveObject 类的实例。而是由显示对象(如 SimpleButton、Sprite、TextField 和各种 Flash 和 Flex 组件)从此类中继承其用户交互模型,因而它们使用同一个通用结构。这意味着,您为处理从 InteractiveObject 派生的一个对象中的用户交互而编写的代码以及学会的方法适用于所有其它对象。
重要概念和术语
在继续阅读本章内容之前,一定要先熟悉以下重要用户交互术语:
字符代码 (Character code):表示当前字符集中的字符(与在键盘上所按的键关联)的数字代码。例如,尽管"D"和"d"是由美国英语键盘上的相同键创建的,但它们具有不同的字符代码。
上下文菜单 (Context menu):当用户右键单击或使用特定键盘-鼠标组合时显示的菜单。上下文菜单命令通常直接应用于已单击的内容。
焦点 (Focus):指示选定元素是活动元素,并且它是键盘或鼠标交互的目标。
键控代码 (Key code):对应于键盘上的实际键的数字代码。
捕获键盘输入
从 InteractiveObject 类继承交互模型的显示对象可以使用事件侦听器来响应键盘事件。例如,您可以将事件侦听器放在舞台上以侦听并响应键盘输入。
有些键(如 Ctrl 键)虽然没有字型表示形式,也能生成事件。
在上面的代码示例中,键盘事件侦听器捕获了整个舞台的键盘输入。也可以为舞台上的特定显示对象编写事件侦听器;当对象具有焦点时将触发该事件侦听器。
了解键控代码和字符代码
您可以访问键盘事件的 keyCode 和 charCode 属性,以确定按下了哪个键,然后触发其它动作。keyCode 属性为数值,与键盘上的某个键的值相对应。charCode 属性是该键在当前字符集中的数值。(默认字符集是 UTF-8,它支持 ASCII。)
键控代码值与字符值之间的主要区别是键控代码值表示键盘上的特定键(数字小键盘上的 1 与最上面一排键中的 1 不同,但生成"1"的键与生成"!"的键是相同的),字符值表示特定字符(R 与 r 字符是不同的)。
键与其键控代码之间的映射取决于设备和操作系统。因此,不应使用键映射来触发动作,而应使用 Keyboard 类提供的预定义常量值来引用相应的 keyCode 属性。例如,不要使用 Shift 的键映射,而应使用 Keyboard.SHIFT 常量。
了解 KeyboardEvent 的优先顺序
与其它事件一样,键盘事件序列是由显示对象层次结构决定的,而不是由在代码中分配 addEventListener() 方法的顺序决定的。
操作系统和 Web 浏览器在 Adobe Flash Player 之前处理键盘事件。
捕获鼠标输入
鼠标单击将创建鼠标事件,这些事件可用来触发交互式功能。您可以将事件侦听器添加到舞台上以侦听在 SWF 文件中任何位置发生的鼠标事件。也可以将事件侦听器添加到舞台上从 InteractiveObject 进行继承的对象(例如,Sprite 或 MovieClip)中;单击该对象时将触发这些侦听器。
与键盘事件一样,鼠标事件也会冒泡。
MouseEvent 对象还包含 altKey、ctrlKey 和 shiftKey 布尔属性。可以使用这些属性来检查在鼠标单击时是否还按下了 Alt、Ctrl 或 Shift 键。
自定义鼠标光标
可以将鼠标光标(鼠标指针)隐藏或交换为舞台上的任何显示对象。要隐藏鼠标光标,请调用 Mouse.hide() 方法。可通过以下方式来自定义光标:调用 Mouse.hide(),侦听舞台上是否发生 MouseEvent.MOUSE_MOVE 事件,以及将显示对象(自定义光标)的坐标设置为事件的 stageX 和 stageY 属性。
自定义上下文菜单
从 InteractiveObject 类进行继承的每个对象可以具有唯一的上下文菜单,用户在 SWF 文件内右键单击时将显示该菜单。默认情况下,菜单中包含几个命令,其中包括"前进"、"后退"、"打印"、"品质"和"缩放"。
除了"设置"和"关于"命令外,您可以从菜单中删除所有其它默认命令。如果将 Stage 属性 showDefaultContextMenu 设置为 false,则会从上下文菜单中删除这些命令。
要为特定显示对象创建自定义的上下文菜单,请创建 ContextMenu 类的一个新实例,调用 hideBuiltInItems() 方法,并将该实例分配给该 DisplayObject 实例的 contextMenu 属性。
管理焦点
交互式对象可以按编程方式或通过用户动作来获得焦点。在这两种情况下,设置焦点会将对象的 focus 属性更改为 true。另外,如果将 tabEnabled 属性设置为 true,用户可通过按 Tab 将焦点从一个对象传递到另一个对象。请注意,默认情况下,tabEnabled 值为 false,但以下情况除外:
对于 SimpleButton 对象,该值为 true。
对于输入文本字段,该值为 true。
对于 buttonMode 设置为 true 的 Sprite 或 MovieClip 对象,该值为 true。
在上述各种情况下,都可以为 FocusEvent.FOCUS_IN 或 FocusEvent.FOCUS_OUT 添加侦听器,以便在焦点更改时提供其它行为。这对文本字段和表单尤其有用,但也可以用于 sprite、影片剪辑或从 InteractiveObject 类进行继承的任何对象。
网络与通信
网络和通信基础知识
网络和通信简介
当构建更复杂的 ActionScript 应用程序时,通常需要与服务器端脚本进行通信,或者从外部 XML 文件或文本文件加载数据。flash.net 包中包含用于通过 Internet 收发数据的类;例如,从远程 URL 加载内容、与其它 Flash Player 实例进行通信以及连接到远程网站。
而在 ActionScript 3.0,可以使用 URLLoader 和 URLRequest 类加载外部文件。可随后使用特定类来访问数据,具体取决于加载的数据类型。例如,如果将远程内容的格式设置为名称-值对,则可以使用 URLVariables 类来分析服务器结果。或者,如果使用 URLLoader 和 URLRequest 类加载的文件是远程 XML 文档,则可以使用 XML 类的构造函数、XMLDocument 类的构造函数或 XMLDocument.parseXML() 方法来分析 XML 文档。这样,您便可以简化 ActionScript 代码,因为无论是使用 URLVariables、XML 还是某个其它类来分析和处理远程数据,用于加载外部文件的代码都是相同的。
flash.net 包中还包含用于其它类型的远程通信的类。这些类包括 FileReference 类(用于将文件上载到服务器以及从服务器下载文件)、Socket 和 XMLSocket 类(用于通过套接字连接直接与远程计算机进行通信)以及 NetConnection 和 NetStream 类(用于与特定于 Flash 的服务器资源(如 Flash Media Server 和 Flash Remoting 服务器)进行通信以及加载视频文件)。
最后,flash.net 包中包含用于用户本地计算机上通信的类。这些类包括 LocalConnection 类(用于在一台计算机上运行的两个或多个 SWF 文件之间的通信)和 SharedObject 类(用于将数据存储在用户的计算机上,并在以后返回到应用程序时检索这些数据)。
重要概念和术语
外部数据 (External data):此类数据以某些形式存储在 SWF 文件外部,并在需要时加载到 SWF 文件中。可以将此数据存储在直接加载的文件中,将其存储在数据库中,或者以其它形式进行存储,以便通过调用在服务器上运行的脚本或程序对其进行检索。
URL 编码变量 (URL-encoded variable):URL 编码格式提供了一种在单个文本字符串中表示多个变量(变量名和值对)的方法。各个变量采用 name=value 格式。每个变量(即每个名称-值对)之间用"and"符隔开,如下所示:variable1=value1&variable2=value2。这样,便可以将不限数量的变量作为一条消息进行发送。
MIME 类型 (MIME type):用于在 Internet 通信中标识给定文件类型的标准代码。任何给定文件类型都具有用于对其进行标识的特定代码。发送文件或消息时,计算机(如 Web 服务器或用户的 Flash Player 实例)将指定要发送的文件类型。
HTTP:超文本传输协议,这是一种标准格式,用于传送通过 Internet 发送的网页和其它各种类型的内容。
请求方法 (Request method):当程序(如 Flash Player)或 Web 浏览器将消息(称为 HTTP 请求)发送到 Web 服务器时,可以使用以下两种方法之一将发送的任何数据嵌入到请求中:这两种方法是两个"请求方法",即 GET 和 POST。在服务器端,接收请求的程序需要查看相应的请求部分以查找数据,因此,用于从 ActionScript 发送数据的请求方法应与用于在服务器上读取该数据的请求方法相匹配。
套接字连接 (Socket connection):用于两台计算机之间的通信的永久连接。
上载 (Upload):将文件发送到另一台计算机。
下载 (Download):从另一台计算机检索文件。
处理外部数据
ActionScript 3.0 包含用于从外部源加载数据的机制。这些源可以是静态内容(如文本文件),也可以是动态内容(如从数据库检索数据的 Web 脚本)。可以使用多种不同的方法来设置数据的格式,并且 ActionScript 提供了用于解码和访问数据的功能。也可以在检索数据的过程中将数据发送到外部服务器。
使用 URLLoader 类和 URLVariables 类
ActionScript 3.0 使用 URLLoader 和 URLVariables 类来加载外部数据。URLLoader 类以文本、二进制数据或 URL 编码变量的形式从 URL 下载数据。URLLoader 类用于下载文本文件、XML 或其它要用于数据驱动的动态 ActionScript 应用程序中的信息。URLLoader 类使用 ActionScript 3.0 高级事件处理模型,使用该模型可以侦听诸如 complete、httpStatus、ioError、open、progress 和 securityError 等事件。
与早期版本 ActionScript 中的 XML 和 LoadVars 类非常相似,URLLoader URL 的数据在下载完成之前不可用。尽管如果文件加载速度太快,可能不会调度 ProgressEvent.PROGRESS 事件,但您可以通过侦听要调度的 flash.events.ProgressEvent.PROGRESS 事件来监视下载进度(已加载的字节数和总字节数)。成功下载文件后,将调度 flash.events.Event.COMPLETE 事件。加载的数据将从 UTF-8 或 UTF-16 编码被解码为字符串。
如果没有为 URLRequest.contentType 设置值,则以 application/x-www-form-urlencoded 的形式发送值。
URLLoader.load() 方法(以及 URLLoader 类的构造函数,可选)使用一个参数,即 request,该参数是一个 URLRequest 实例。URLRequest 实例包含单个 HTTP 请求的所有信息,如目标 URL、请求方法(GET 或 POST)、附加标头信息以及 MIME 类型(例如,当上载 XML 内容时)。
可以使用三种方式指定要在 URL 请求中传递的参数:
在 URLVariables 构造函数中
在 URLVariables.decode() 方法中
作为 URLVariables 对象本身中的特定属性
当定义 URLVariables 构造函数或 URLVariables.decode() 方法中的变量时,需要确保对"and"符进行 URL 编码,因为它具有特殊含义并作为分隔符使用。例如,由于与号作为参数的分隔符使用,当传递与号时,需要将与号从 & 更改为 %26 来对与号进行 URL 编码。
从外部文档加载数据
当使用 ActionScript 3.0 生成动态应用程序时,最好从外部文件或服务器端脚本加载数据。这样,您不必编辑或重新编译 ActionScript 文件,即可生成动态应用程序。
默认情况下,如果您未定义请求方法,Flash Player 将使用 HTTP GET 方法加载内容。如果要使用 POST 方法发送数据,则需要使用静态常量 URLRequestMethod.POST 将 request.method 属性设置为 POST。
不要将保留字或语言构造作为外部数据文件中的变量名称,因为这样做会使代码的读取和调试变得更困难。
加载数据后,将调度 Event.COMPLETE 事件,现可以在 URLLoader 的 data 属性中使用外部文档的内容。
如果远程文档包含名称-值对,您可以通过传入加载文件的内容,使用 URLVariables 类来分析数据。
外部文件中的每个名称-值对都创建为 URLVariables 对象中的一个属性。
如果从外部文本文件加载数值数据,则需要使用顶级函数(如 int()、uint() 或 Number())将这些值转换为数值。
无需将远程文件的内容作为字符串加载和新建 URLVariables 对象,您可以将 URLLoader.dataFormat 属性设置为在 URLLoaderDataFormat 类中找到的静态属性之一。URLLoader.dataFormat 属性的三个可能值如下:
URLLoaderDataFormat.BINARY ─ URLLoader.data 属性将包含 ByteArray 对象中存储的二进制数据。
URLLoaderDataFormat.TEXT ─ URLLoader.data 属性将包含 String 对象中的文本。
URLLoaderDataFormat.VARIABLES ─ URLLoader.data 属性将包含 URLVariables 对象中存储的 URL 编码的变量。
URLLoader.dataFormat 的默认值为 URLLoaderDataFormat.TEXT。
从外部文件加载 XML 与加载 URLVariables 相同。可以创建 URLRequest 和 URLLoader 实例,然后使用它们下载远程 XML 文档。文件完全下载后,调度 Event.COMPLETE 事件,并将外部文件的内容转换为可使用 XML 方法和属性分析的 XML 实例。
与外部脚本进行通信
除了加载外部数据文件,还可以使用 URLVariables 类将变量发送到服务器端脚本并处理服务器的响应。
连接到其它 Flash Player 实例
通过使用 LocalConnection 类,可以在不同的 Flash Player 实例(例如 HTML 容器、嵌入或独立播放器中的 SWF)之间进行通信。这样,您便可以构建在 Flash Player 实例之间共享数据(例如在 Web 浏览器中运行或嵌入在桌面应用程序中的 SWF 文件)的各种不同的应用程序。
LocalConnection 类
LocalConnection 类用于开发 SWF 文件,这些文件无需使用 fscommand() 方法或 JavaScript 即可向其它 SWF 文件发送指令。LocalConnection 对象只能在同一客户端计算机上运行的 SWF 文件间进行通信,但是它们可以在不同的应用程序中运行。
可以使用 LocalConnection 对象在使用不同 ActionScript 版本的 SWF 之间进行通信:
ActionScript 3.0 LocalConnection 对象可以与使用 ActionScript 1.0 或 2.0 创建的 LocalConnection 对象进行通信。
ActionScript 1.0 或 2.0 LocalConnection 对象可以与使用 ActionScript 3.0 创建的 LocalConnection 对象进行通信。
Flash Player 可自动处理不同版本 LocalConnection 对象间的通信。
最简便的 LocalConnection 对象使用方法是只允许位于同一个域中的 LocalConnection 对象之间进行通信。这样,您就不必担心安全方面的问题。
可以使用 LocalConnection 对象在一个 SWF 文件中收发数据,但是 Adobe 不建议这样做。相反,您应使用共享对象。
可以使用三种方式将回调方法添加到 LocalConnection 对象中:
使 LocalConnection 类成为子类,并添加方法。
将 LocalConnection.client 属性设置为实现方法的对象。
创建扩展 LocalConnection 的动态类,并动态附加方法。
添加回调方法的第一种方式是扩展 LocalConnection 类。您在自定义类中定义方法,而不是将它们动态添加到 LocalConnection 实例中。
添加回调方法的第二种方式是使用 LocalConnection.client 属性。这包括创建自定义类和将新实例分配给 client 属性,LocalConnection.client 属性指示应调用的对象回调方法。
添加回调方法的第三种方式是创建动态类并动态附加该方法,这与在早期版本的 ActionScript 中使用 LocalConnection 类非常相似,不建议使用上面这种添加回调方法的方式,因为该代码不是非常易于移植。
在两个 Flash Player 实例之间发送消息
可以使用 LocalConnection 类在 Flash Player 的不同实例之间进行通信。例如,可以在网页上有多个 Flash Player 实例,或者让 Flash Player 实例从弹出窗口中的 Flash Player 实例检索数据。
下面的代码定义一个本地连接对象,该对象作为服务器使用,接受来自其它 Flash Player 实例的传入调用:
package
{
import flash.net.LocalConnection;
import flash.display.Sprite;
public class ServerLC extends Sprite
{
public function ServerLC()
{
var lc:LocalConnection = new LocalConnection();
lc.client = new CustomClient1();
try
{
lc.connect("conn1");
}
catch (error:Error)
{
trace("error:: already connected");
}
}
}
}
此代码先创建一个名为 lc 的 LocalConnection 对象,然后将 client 属性设置为自定义类 CustomClient1。当另一个 Flash Player 实例调用此本地连接实例中的某方法时,Flash Player 在 CustomClient1 类中查找该方法。
当 Flash Player 实例连接到此 SWF 文件并尝试调用指定本地连接的任何方法时,系统会将请求发送到 client 属性指定的类(该属性被设置为 CustomClient1 类):
package
{
import flash.events.*;
import flash.system.fscommand;
import flash.utils.Timer;
public class CustomClient1 extends Object
{
public function doMessage(value:String = ""):void
{
trace(value);
}
public function doQuit():void
{
trace("quitting in 5 seconds");
this.close();
var quitTimer:Timer = new Timer(5000, 1);
quitTimer.addEventListener(TimerEvent.TIMER, closeHandler);
}
public function closeHandler(event:TimerEvent):void
{
fscommand("quit");
}
}
}
要创建 LocalConnection 服务器,请调用 LocalConnection.connect() 方法并提供唯一的连接名称。如果已存在具有指定名称的连接,则会生成 ArgumentError 错误,指出由于已经连接了该对象,连接尝试失败。
下面的片断说明如何使用名称 conn1 创建新的套接字连接:
try
{
connection.connect("conn1");
}
catch (error:ArgumentError)
{
trace("Error! Server already existsn");
}
在早期版本的 ActionScript 中,如果连接名称已被使用,LocalConnection.connect() 方法则会返回一个布尔值。在 ActionScript 3.0 中,如果该名称已被使用,则生成错误。
从辅助 SWF 文件连接到主 SWF 文件需要在发送方 LocalConnection 对象中新建 LocalConnection 对象,然后使用连接名称和要执行的方法名称来调用 LocalConnection.send() 方法。例如,要连接到早先创建的 LocalConnection 对象,可以使用下面的代码:
sendingConnection.send("conn1", "doQuit");
此代码使用连接名称 conn1 连接到现有 LocalConnection 对象,并调用远程 SWF 文件中的 doQuit() 方法。如果想要将参数发送到远程 SWF 文件,可以在 send() 方法中的方法名称后指定附加参数,如下面的片断所示:
sendingConnection.send("conn1", "doMessage", "Hello world");
连接到不同域中的 SWF 文档
要只允许从特定域进行通信,可以调用 LocalConnection 类的 allowDomain() 或 allowInsecureDomain() 方法,并传递包含允许访问此 LocalConnection 对象的一个或多个域的列表。
在早期版本的 ActionScript 中,LocalConnection.allowDomain() 和 LocalConnection.allowInsecureDomain() 是必须由开发人员实现的、且必须返回布尔值的回调方法。在 ActionScript 3.0 中,LocalConnection.allowDomain() 和 LocalConnection.allowInsecureDomain() 都是内置方法,开发人员可以像调用 Security.allowDomain() 和 Security.allowInsecureDomain() 那样调用这两个内置方法,传递要允许的一个或多个域的名称。
可以向 LocalConnection.allowDomain() 和 LocalConnection.allowInsecureDomain() 方法传递两个特殊值:* 和 localhost。星号值 (*) 表示允许从所有域访问。字符串 localhost 允许从本地安装的 SWF 文件调用 SWF 文件。
Flash Player 8 对本地 SWF 文件引入了安全限制。可以访问 Internet 的 SWF 文件还不能访问本地文件系统。如果指定 localhost,则任何本地 SWF 文件都可以访问 SWF 文件。如果 LocalConnection.send() 方法试图从调用代码没有访问权限的安全沙箱与 SWF 文件进行通信,则会调度 securityError 事件 (SecurityErrorEvent.SECURITY_ERROR)。要解决此错误,可以在接收方的 LocalConnection.allowDomain() 方法中指定调用方的域。
如果仅在同一个域中的 SWF 文件之间实现通信,可以指定一个不以下划线 (_) 开头且不指定域名的 connectionName 参数(例如 myDomain:connectionName)。在 LocalConnection.connect(connectionName) 命令中使用相同的字符串。
如果要实现不同域中的 SWF 文件之间的通信,可以指定一个以下划线开头的 connectionName 参数。指定下划线使具有接收方 LocalConnection 对象的 SWF 文件更易于在域之间移植。下面是两种可能的情形:
如果 connectionName 字符串不以下划线开头,则 Flash Player 会添加一个包含超级域名称和一个冒号的前缀(例如 myDomain:connectionName)。虽然这可以确保您的连接不会与其它域中具有同一名称的连接冲突,但任何发送方 LocalConnection 对象都必须指定此超级域(例如 myDomain:connectionName)。如果将具有接收方 LocalConnection 对象的 SWF 文件移动到另一个域中,Flash Player 会更改前缀,以反映新的超级域(例如 anotherDomain:connectionName)。必须手动编辑所有发送方 LocalConnection 对象,以指向新超级域。
如果 connectionName 字符串以下划线开头(例如 _connectionName),Flash Player 不会向该字符串添加前缀。这意味着接收方和发送方 LocalConnection 对象都将使用相同的 connectionName 字符串。如果接收方对象使用 LocalConnection.allowDomain() 来指定可以接受来自任何域的连接,则可以将具有接收方 LocalConnection 对象的 SWF 文件移动到另一个域,而无需更改任何发送方 LocalConnection 对象。
套接字连接
在 ActionScript 3.0 中,可以使用两种不同类型的套接字连接:XML 套接字连接和二进制套接字连接。使用 XML 套接字,可以连接到远程服务器并创建服务器连接,该连接在显式关闭之前一直保持打开。这样,无需不断打开新服务器连接,就可以在服务器与客户端之间交换 XML 数据。使用 XML 套接字服务器的另一个好处是用户不需要显式请求数据。您无需请求即可从服务器发送数据,并且可以将数据发送到连接到 XML 套接字服务器的每个客户端。
二进制套接字连接与 XML 套接字类似,不同之处是客户端与服务器不需要专门交换 XML 数据包,连接可以将数据作为二进制信息传输。这样,您就可以连接到各种各样的服务,包括邮件服务器(POP3、SMTP 和 IMAP)和新闻服务器 (NNTP)。
Socket 类
ActionScript 3.0 中引入的 Socket 类使 ActionScript 可以建立套接字连接并读取和写入原始二进制数据。它与 XMLSocket 类相似,但没有指定接收和传输数据的格式。使用二进制协议的服务器互操作时,Socket 类与非常有用。使用二进制套接字连接,可以编写允许用一些不同的 Internet 协议(例如 POP3、SMTP、IMAP 和 NNTP)进行交互的代码。反过来,这又会使 Flash Player 能够连接到邮件和新闻服务器。
Flash Player 可通过使用服务器的二进制协议直接与该服务器连接。某些服务器使用 big-endian 字节顺序,某些服务器则使用 little-endian 字节顺序。Internet 上的大多数服务器使用 big-endian 字节顺序,因为"网络字节顺序"为 big-endian。little-endian 字节顺序很常用,因为 Intel x86® 体系结构使用该字节顺序。您应使用与收发数据的服务器的字节顺序相匹配的 endian 字节顺序。默认情况下,IDataInput 和 IDataOutput 接口执行的所有操作和实现这些接口的类(ByteArray、Socket 和 URLStream)都以 big-endian 格式编码;即,最高有效字节位于前面。这样做是为了匹配 Java 和官方网络字节顺序。要更改是使用 big-endian 还是使用 little-endian 字节顺序,可以将 endian 属性设置为 Endian.BIG_ENDIAN 或 Endian.LITTLE_ENDIAN。
Socket 类继承 IDataInput 和 IDataOutput 接口(位于 flash.utils 包中)实现的所有方法,应使用这些方法从 Socket 读取数据和向其中写入数据。
XMLSocket 类
ActionScript 提供了一个内置的 XMLSocket 类,使用它可以打开与服务器的持续连接。这种打开的连接消除了反应时间问题,它通常用于实时的应用程序,例如聊天应用程序或多人游戏。传统的基于 HTTP 的聊天解决方案频繁轮询服务器,并使用 HTTP 请求来下载新的消息。与此相对照,XMLSocket 聊天解决方案保持与服务器的开放连接,这一连接允许服务器即时发送传入的消息,而无需客户端发出请求。
要创建套接字连接,必须创建服务器端应用程序来等待套接字连接请求,然后向 SWF 文件发送响应。可以使用 Java、Python 或 Perl 程语言来编写这种类型的服务器端应用程序。要使用 XMLSocket 类,服务器计算机必须运行可识别 XMLSocket 类使用的协议的守护程序。下面的列表说明了该协议:
通过全双工 TCP/IP 流套接字连接发送 XML 消息。
每个 XML 消息都是一个完整的 XML 文档,以一个零 (0) 字节结束。
通过一个 XMLSocket 连接发送和接收的 XML 消息的数量没有限制。
XMLSocket 类不能自动穿过防火墙,因为 XMLSocket 没有 HTTP 隧道功能(这与实时消息传递协议 (RTMP) 不同)。如果您需要使用 HTTP 隧道,应考虑改用 Flash Remoting 或 Flash Media Server(支持 RTMP)。
XMLSocket 对象连接到服务器的方式和位置受以下限制:
XMLSocket.connect() 方法只能连接到端口号大于或等于 1024 的 TCP 端口。这种限制的一个后果是,与 XMLSocket 对象进行通信的服务器守护程序也必须分配到端口号大于或等于 1024 的端口。端口号小于 1024 的端口通常用于系统服务(如 FTP (21)、Telnet (23)、SMTP (25)、HTTP (80) 和 POP3 (110)),因此,出于安全方面的考虑,禁止 XMLSocket 对象使用这些端口。这种端口号方面的限制可以减少不恰当地访问和滥用这些资源的可能性。
XMLSocket.connect() 方法只能连接到 SWF 文件所在域中的计算机。这一限制不适用于在本地磁盘外运行的 SWF 文件。(此限制与 URLLoader.load() 的安全规则相同。)要连接到在 SWF 所在域之外的其它域中运行的服务器守护程序,可以在该服务器上创建一个允许从特定域进行访问的安全策略文件。
将服务器设置为与 XMLSocket 对象进行通信可能会遇到一些困难。如果您的应用程序不需要进行实时交互,请使用 URLLoader 类,而不要使用 XMLSocket 类。
可以使用 XMLSocket 类的 XMLSocket.connect() 和 XMLSocket.send() 方法,通过套接字连接与服务器之间传输 XML。XMLSocket.connect() 方法与 Web 服务器端口建立套接字连接。XMLSocket.send() 方法将 XML 对象传递给套接字连接中指定的服务器。
调用 XMLSocket.connect() 方法时,Flash Player 打开与服务器的 TCP/IP 连接,并使该连接保持打开状态,直到发生以下任一事件:
XMLSocket 类的 XMLSocket.close() 方法被调用。
对 XMLSocket 对象的引用不再存在。
Flash Player 退出。
连接中断(例如,调制解调器断开连接)。
存储本地数据
共享对象(有时称为"Flash cookie")是一个数据文件,您访问的站点可能会在您的计算机上创建该文件。共享对象通常用于增强您的 Web 浏览体验 ─ 例如,使用它可以个性化经常访问的网站的外观。共享对象本身不能对您计算机上的数据进行任何操作,也不能使用这些数据进行任何操作。更重要的是,共享对象永远不能访问或记住您的电子邮件地址或其它个人信息 ─ 除非您愿意提供这样的信息。
可以使用静态的 SharedObject.getLocal() 或 SharedObject.getRemote() 方法来创建新的共享对象实例。getLocal() 方法尝试加载仅对当前客户端可用的本地永久共享对象,而 getRemote() 方法则尝试加载可借助服务器(例如 Flash Media Server)跨多个客户端共享的远程共享对象。如果本地或远程共享对象不存在,则 getLocal() 和 getRemote() 方法将创建一个新的 SharedObject 实例。
可以使用 SharedObject.size 属性来确定共享对象是否已存在。
为了将共享对象保存到用户的硬盘驱动器,您必须显式地调用 SharedObject.flush() 方法。使用 flush() 方法将共享对象写入用户的硬盘时,应仔细检查用户是否已使用 Flash Player 设置管理器显式禁用了本地存储。
显示共享对象的内容
值存储在共享对象中的 data 属性中。
创建安全 SharedObject
当使用 getLocal() 或 getRemote() 创建本地或远程 SharedObject 时,有一个名为 secure 的可选参数,该参数确定对此共享对象的访问是否限于通过 HTTPS 连接传递的 SWF 文件。如果此参数设置为 true 且 SWF 文件是通过 HTTPS 传递的,Flash Player 将新建一个安全共享对象,或者获取对现有安全共享对象的引用。此安全共享对象只能由通过 HTTPS 传递的 SWF 文件来读取或写入,SWF 文件将调用 SharedObject.getLocal() 并将 secure 参数设置为 true。如果此参数设置为 false 且 SWF 文件是通过 HTTPS 传递的,Flash Player 将新建一个共享对象,或者获取对现有共享对象的引用。
还可由通过非 HTTPS 连接传递的 SWF 文件对此共享对象进行读取或写入。如果 SWF 文件是通过非 HTTPS 连接传递的,并且您尝试将此参数设置为 true,将无法创建新的共享对象(或访问以前创建的安全共享对象),并会引发错误,并且共享对象设置为 null。
处理文件上载和下载
可通过使用 FileReference 类,在客户端和服务器之间添加上载和下载文件的功能。将通过一个对话框(例如,Windows 操作系统中的"打开"对话框)提示用户选择要上载的文件或用于下载的位置。
使用 ActionScript 创建的每个 FileReference 对象都引用用户硬盘上的一个文件。该对象的属性包含有关文件大小、类型、名称、创建日期和修改日期的信息。
仅 Mac OS 支持 creator 属性。所有其它平台都会返回 null。
可以通过两种方式创建 FileReference 类的实例。可以使用 new 运算符,如以下代码所示:
import flash.net.FileReference;
var myFileReference:FileReference = new FileReference();
或者,可以调用 FileReferenceList.browse() 方法,该方法在用户的系统中打开一个对话框,提示用户选择一个或多个要上载的文件,如果用户成功选择了一个或多个文件,将创建一个由 FileReference 对象组成的数组。每个 FileReference 对象表示用户在对话框中选择的一个文件。FileReference 对象在 FileReference 属性(例如 name、size 或 modificationDate)中不包含任何数据,直到发生下列任一情况:
已调用 FileReference.browse() 方法或 FileReferenceList.browse() 方法,并且用户已经从文件选取器中选择了文件。
已调用 FileReference.download() 方法,并且用户已经从文件选取器中选择了文件。
当执行下载时,在下载完成前只填充 FileReference.name 属性。下载完文件后,所有属性都将可用。在执行对 FileReference.browse()、FileReferenceList.browse() 或 FileReference.download() 方法的调用时,大多数播放器将继续 SWF 文件播放。
FileReference 类
使用 FileReference 类可以在用户的计算机和服务器之间上载和下载文件。操作系统对话框会提示用户选择要上载的文件或用于下载的位置。每个 FileReference 对象都引用用户磁盘上的一个文件并且具有一些属性,这些属性包含有关文件大小、类型、名称、创建日期、修改日期以及创建者的信息。
FileReference 实例的创建方法有两种:
使用 new 运算符和 FileReference 构造函数,如下所示:
var myFileReference:FileReference = new FileReference();
调用 FileReferenceList.browse(),从而创建 FileReference 对象数组。
对于上载和下载操作,SWF 文件只能访问自己的域(包括由跨域策略文件指定的任何域)内的文件。如果启动上载或下载的 SWF 与文件服务器不在同一个域中,则需要将策略文件放到文件服务器上。
一次只能执行一个 browse() 或 download() 操作,因为在任何时刻只能打开一个对话框。
处理文件上载的服务器脚本应收到包含下列元素的 HTTP POST 请求:
Content-Type,其值为 multipart/form-data。
Content-Disposition,其 name 属性设置为"Filedata",filename 属性设置为原始文件的名称。您可以通过在 FileReference.upload() 方法中传递 uploadDataFieldName 参数的值来指定自定义 name 属性。
文件的二进制内容。
将文件上载到服务器
要将文件上载到服务器,需要首先调用 browse() 方法,以允许用户选择一个或多个文件。接下来,当调用 FileReference.upload() 方法时,所选的文件将传输到服务器。如果用户使用 FileReferenceList.browse() 方法选择了多个文件,Flash Player 将创建一个称为 FileReferenceList.fileList 的所选文件数组。可随后使用 FileReference.upload() 方法分别上载每个文件。
使用 FileReference.browse() 方法时,您只能上载单个文件。要允许用户上载多个文件,您必须使用 FileReferenceList.browse() 方法。
虽然开发人员可以通过使用 FileFilter 类并将文件过滤器实例数组传递给 browse() 方法来指定一个或多个自定义文件类型过滤器,但是默认情况下,系统文件选取器对话框允许用户从本地计算机选取任何文件类型。
您可以使用 URLRequest.method 和 URLRequest.data 属性,通过 FileReference.upload() 方法将数据发送到服务器,以使用 POST 或 GET 方法发送变量。
当您尝试使用 FileReference.upload() 方法上载文件时,可能调度下列任何事件:
Event.OPEN:上载操作开始时调度。
ProgressEvent.PROGRESS:文件上载操作期间定期调度。
Event.COMPLETE:文件上载操作成功完成时调度。
SecurityErrorEvent.SECURITY_ERROR:由于安全侵犯导致上载失败时调度。
HTTPStatusEvent.HTTP_STATUS:由于 HTTP 错误导致上载失败时调度。
IOErrorEvent.IO_ERROR:由于下列任何原因导致上载失败时调度:
当 Flash Player 正在读取、写入或传输文件时发生输入/输出错误。
SWF 尝试将文件上载到要求身份验证(如用户名和密码)的服务器。在上载期间,Flash Player 不为用户提供输入密码的方法。
url 参数包含无效协议。FileReference.upload() 方法必须使用 HTTP 或 HTTPS。
Flash Player 不对需要身份验证的服务器提供完全支持。只有使用浏览器插件或 Microsoft ActiveX® 控件在浏览器中运行的 SWF 文件才可以提供一个对话框,来提示用户输入用户名和密码以进行身份验证,并且用户只有在通过身份验证后才能下载。对于使用插件或 ActiveX 控件进行的上载操作,或者使用独立或外部播放器进行的上载/下载操作,文件传输会失败。
在 ActionScript 3.0 中,可以使用 URLRequest 对象将变量传递到远程脚本,该对象允许您使用 POST 或 GET 方法传递数据;因此,可以更轻松和更清晰地传递较大数据集。为了指定是使用 GET 还是使用 POST 请求方法来传递变量,可以将 URLRequest.method 属性相应设置为 URLRequestMethod.GET 或 URLRequestMethod.POST。
在 ActionScript 3.0 中,还可以通过向 upload() 方法提供第二个参数来覆盖默认 Filedata 上载文件字段名称,如上面的示例所示(该示例使用 Custom1 替换默认值 Filedata)。
默认情况下,Flash Player 不尝试发送测试上载,虽然您可以通过将值 true 作为第三个参数传递给 upload() 方法来覆盖此行为。测试上载的目的是检查实际文件上载是否会成功,如果需要服务器身份,还会检查服务器身份验证是否会成功。
目前,只在基于 Windows 的 Flash Player 上进行测试上载。
从服务器下载文件
您可以让用户使用 FileReference.download() 方法从服务器下载文件,该方法使用两个参数:request 和 defaultFileName。第一个参数是 URLRequest 对象,该对象包含要下载的文件的 URL。第二个参数是可选的,它允许您指定出现在下载文件对话框中的默认文件名。如果省略第二个参数 defaultFileName,则使用指定 URL 中的文件名。
FileReferenceList 类
使用 FileReferenceList 类,用户可以选择一个或多个要上载到服务器端脚本的文件。文件上载是由 FileReference.upload() 方法处理的,必须对用户选择的每个文件调用此方法。
客户端系统环境
客户端系统环境基础知识
客户端系统环境简介
在构建更高级的 ActionScript 应用程序时,您可能会发现需要了解有关用户操作系统的详细信息(和访问操作系统功能)。客户端系统环境是 flash.system 包中的类集合,可通过这些类来访问系统级功能,例如:
确定执行 SWF 时所在的应用程序和安全域
确定用户的 Flash Player 的功能,如屏幕大小(分辨率);以及确定某项功能是否可用,如 mp3 音频
使用 IME 建立多语言站点
与 Flash Player 的容器(可能是 HTML 页或容器应用程序)进行交互
将信息保存到用户的剪贴板中
flash.system 包还包括 IMEConversionMode 和 SecurityPanel 类。这两个类分别包含与 IME 和 Security 类一起使用的静态常数。
重要概念和术语
操作系统 (Operating system):计算机上运行的主程序(其它所有应用程序均运行在其中),如 Microsoft Windows、Mac OS X 或 Linux®。
剪贴板 (Clipboard):用于保存复制或剪切的文本或项目的操作系统容器,可从中将项目粘贴到应用程序中。
应用程序域 (Application domain):用于将不同 SWF 文件中使用的类分开的机制,以便在 SWF 文件包含具有相同名称的不同类时,这些类不会彼此覆盖。
IME(input method editor,输入法编辑器):用于通过标准键盘输入复杂字符或符号的程序(或操作系统工具)。
客户端系统:在编程术语中,"客户端"是指在单独计算机上运行并由单个用户使用的应用程序部分(或整个应用程序)。"客户端系统"是指用户计算机上的基础操作系统。
使用 System 类
System 类包含的一些方法和属性可让您与用户的操作系统进行交互,并检索 Adobe Flash Player 的当前内存使用数据。System 类的方法和属性还可用来侦听 imeComposition 事件,指示 Flash Player 使用用户的当前代码页加载外部文本文件或按 Unicode 进行加载,或者设置用户剪贴板的内容。
在运行时获取有关用户系统的数据
通过检查 System.totalMemory 属性,可以确定 Flash Player 当前所用的内存数量(以字节为单位)。该属性可让您监视内存使用情况,并根据内存级别的更改方式优化应用程序。
System.ime 属性是对当前安装的输入法编辑器 (IME) 的引用。该属性允许使用 addEventListener() 方法来侦听 imeComposition 事件 (flash.events.IMEEvent.IME_COMPOSITION)。
System 类中的第三个属性是 useCodePage。如果将 useCodePage 设置为 true,Flash Player 将使用运行播放器的操作系统的传统代码页来加载外部文本文件。如果将此属性设置为 false,则 Flash Player 按 Unicode 解释外部文件。
如果将 System.useCodePage 设置为 true,请记住,运行播放器的操作系统的传统代码页中必须包括在外部文本文件中使用的字符,这样才能显示文本。例如,如果您加载了一个包含中文字符的外部文本文件,则这些字符不能显示在使用英文 Windows 代码页的系统上,因为该代码页不包括中文字符。
要确保所有平台上的用户都能查看 SWF 文件中使用的外部文本文件,应将所有外部文本文件按 Unicode 进行编码,并将 System.useCodePage 设置保留为默认设置 false。这样,Flash Player 就会将文本解释为 Unicode。
将文本保存到剪贴板
System 类包含一个名为 setClipboard() 的方法,它允许 Flash Player 使用指定的字符串来设置用户剪贴板的内容。出于安全方面的考虑,不存在 Security.getClipboard() 方法,因为此类方法可能允许恶意站点访问最近复制到用户剪贴板中的数据。
使用 Capabilities 类
开发人员可通过 Capabilities 类来确定正在运行 SWF 文件的环境。使用 Capabilities 类的各种属性,可以查明用户系统的分辨率、用户的系统是否支持辅助功能软件、用户操作系统的语言以及当前安装的 Flash Player 的版本。
通过检查 Capabilities 类的属性,可以自定义应用程序,使其与特定用户环境更好地配合使用。例如,通过检查 Capabilities.screenResolutionX 和 Capabilities.screenResolutionY 属性,可以确定用户系统所使用的显示分辨率以及决定最合适的视频大小。或者,在尝试加载外部 mp3 文件之前,可以检查 Capabilities.hasMP3 属性以查看用户系统是否支持 mp3 回放。
使用 ApplicationDomain 类
ApplicationDomain 类的用途是存储 ActionScript 3.0 定义表。SWF 文件中的所有代码被定义为存在于应用程序域中。 可以使用应用程序域划分位于同一个安全域中的类。这允许同一个类存在多个定义,并且还允许子级重用父级定义。
在使用 Loader 类 API 加载用 ActionScript 3.0 编写的外部 SWF 文件时,可以使用应用程序域。(请注意,在加载图像或用 ActionScript 1.0 或 ActionScript 2.0 编写的 SWF 文件时不能使用应用程序域。)包含在已加载类中的所有 ActionScript 3.0 定义都存储在应用程序域中。加载 SWF 文件时,通过将 LoaderContext 对象的 applicationDomain 参数设置为 ApplicationDomain.currentDomain,可以指定文件包含在 Loader 对象所在的相同应用程序域中。通过将加载的 SWF 文件放在同一个应用程序域中,可以直接访问它的类。如果加载的 SWF 文件包含嵌入的媒体(可通过其关联的类名称访问),或者您要访问加载的 SWF 文件的方法,则这种方式会很有用。
使用应用程序域时,还要记住以下几点:
SWF 文件中的所有代码被定义为存在于应用程序域中。 主应用程序在“当前域”中运行。“系统域”中包含所有应用程序域(包括当前域),也就是,它包含所有 Flash Player 类。
所有应用程序域(除系统域外)都有关联的父域。主应用程序的应用程序域的父域是系统域。已加载的类仅在其父级中没有相关定义时才进行定义。不能用较新的定义覆盖已加载类的定义。
下图显示了某个应用程序在单个域 (domain1.com) 中加载多个 SWF 文件的内容。根据加载内容的不同,可以使用不同的应用程序域。紧跟的文本说明用于为应用程序中的每个 SWF 文件设置适当应用程序域的逻辑。


主应用程序文件为 application1.swf。它包含从其它 SWF 文件加载内容的 Loader 对象。在此方案下,当前域为 Application domain 1。用法 A、用法 B 和用法 C 说明了为应用程序中的每个 SWF 文件设置适当应用程序域的不同方法。
用法 A:通过创建系统域的子级划分子级 SWF 文件。在示意图中,Application domain 2 创建为系统域的子级。application2.swf 文件在 Application domain 2 中加载,因此其类定义从 application1.swf 中定义的类中划分出来。
此方法的一个用处是使旧版应用程序能够动态加载相同应用程序的更新版本,而不会发生冲突。之所以不发生冲突,是因为尽管使用的是同样的类名称,但它们划分到不同的应用程序域中。
以下代码将创建作为系统域子级的应用程序域:
request.url = "application2.swf";
request.applicationDomain = new ApplicationDomain();
用法 B:在当前类定义中添加新的类定义。module1.swf 的应用程序域设置为当前域 (Application domain 1)。这可让您将新的类定义添加到应用程序的当前一组类定义中。这可用于主应用程序的运行时共享库。加载的 SWF 被视为远程共享库 (RSL)。使用此方法可以在应用程序启动之前使用预加载器加载 RSL。
以下代码将某应用程序域设置为当前域:
request.url = "module1.swf";
request.applicationDomain = ApplicationDomain.currentDomain;
用法 C:通过创建当前域的新子域,使用父级的类定义。module3.swf 的应用程序域是当前域的子级,并且子级使用所有类的父级的版本。此方法的一个用处可能是作为一个使用主应用程序的类型的多屏幕丰富 Internet 应用程序 (RIA) 模块,该模块作为主应用程序的子级加载。如果能够确保所有类始终更新为向后兼容,并且正在加载的应用程序始终比其加载的软件的版本新,则子级将使用父级版本。如果可以确保不继续拥有对子级 SWF 的引用,则拥有了新的应用程序域还使您能够卸载所有的类定义以便于垃圾回收。
此方法使加载的模块可以共享加载者的 singleton 对象和静态类成员。
以下代码将创建当前域的新子域:
request.url = "module3.swf";
request.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
使用 IME 类
通过使用 IME 类,您可以在 Flash Player 中运用操作系统的 IME。
使用 ActionScript 可以确定以下内容:
用户的计算机上是否安装了 IME (Capabilities.hasIME)
用户计算机上是否启用了 IME (IME.enabled)
当前 IME 使用的转换模式 (IME.conversionMode)
可以使用特定 IME 上下文关联输入文本字段。在输入字段之间切换时,还可以在平假名(日文)、全角数字、半角数字、直接输入等之间切换 IME。
利用 IME,用户可键入多字节语言(例如中文、日文和韩文)的非 ASCII 文本字符。
如果用户计算机上 IME 未处于活动状态,则调用 IME 方法或属性(除 Capabilities.hasIME 之外)将失败。一旦手动激活 IME,随后对 IME 方法和属性的 ActionScript 调用即会正常运行。
查看是否已安装并启用了 IME
调用任何 IME 方法或属性之前,应始终检查用户计算机上当前是否已安装并启用 IME。先使用 Capabilities.hasIME 属性检查用户是否安装了 IME。如果将该属性设置为 true,则代码使用 IME.enabled 属性检查用户当前是否已启用了 IME。
确定当前启用的是哪种 IME 转换模式
通过将 IME.conversionMode 属性与 IMEConversionMode 类中的每个常量进行比较,检查当前 IME 使用的是哪种转换模式。
设置 IME 转换模式
更改用户的 IME 的转换模式时,您需要确保将代码封装在 try..catch 块中,因为使用 conversionMode 属性设置转换模式时,如果 IME 不能设置转换模式,则可能会引发错误。
侦听 IME 合成事件
设置合成字符串时会调度 IME 合成事件。例如,如果用户启用了 IME 并键入日文字符串,在用户选择合成字符串时,即会调度 IMEEvent.IME_COMPOSITION 事件。为了侦听 IMEEvent.IME_COMPOSITION 事件,您需要在 System 类的静态 ime 属性中添加一个事件侦听器 (flash.system.System.ime.addEventListener(...))。
Flash Player 安全性
Flash Player 安全性概述
Flash Player 安全性大部分基于加载的 SWF 文件、媒体和其它资源的原始域。来自特定 Internet 域(例如 www.loach.net.cn的 SWF 文件始终可以访问该域的所有数据。这些资源放置在相同的安全分组中,该分组称为"安全沙箱"。
SWF 文件可以加载 SWF 文件、位图、音频、文本文件以及自身域中的任何其它资源。此外,只要同一域中的两个 SWF 文件都是使用 ActionScript3.0 编写的,则始终可以在这两个文件之间执行跨脚本访问操作。"跨脚本访问"是指一个 SWF 文件能够使用 ActionScript 访问另一个 SWF 文件中的属性、方法和对象。对于使用 ActionScript 3.0 编写的 SWF 文件与使用 ActionScript 早期版本编写的 SWF 文件,它们之间不支持跨脚本访问;但是,可以通过使用 LocalConnection 类在这些文件之间进行通信。
默认情况下,以下基本安全性规则始终适用:
位于相同安全沙箱中的资源始终可以互相访问。
远程沙箱中的 SWF 文件始终不能访问本地文件和数据。
Flash Player 将以下地址视为单个域,并为每个地址设置单独的安全沙箱:
http://example.com
http://www.loach.net.cn
http://store.example.com
https://www.loach.net.cn
http://192.0.34.166
即使某个指定域(例如 http://example.com)映射到特定 IP 地址(例如 http://192.0.34.166),Flash Player 也会为它们设置单独的安全沙箱。
开发人员可以使用两种基本方法为 SWF 文件授予访问权限,使之能够访问除该 SWF 文件所属沙箱之外的其它沙箱中的资源:
Security.allowDomain() 方法
跨域策略文件
默认情况下,SWF 文件不能对其它域中的 ActionScript 3.0 SWF 文件执行跨脚本访问操作,也不能加载其它域中的数据。可以通过在加载的 SWF 文件中调用 Security.allowDomain() 方法来授予这种权限。
在 Flash Player 安全模型中,加载内容 与访问或加载数据 之间存在区别:
加载内容 ─"内容"被定义为媒体,包括 Flash Player 可以播放的可视化媒体、音频、视频或包含显示媒体的 SWF 文件。"数据"被定义为只有 ActionScript 代码才能访问的内容。可以使用 Loader、Sound 和 NetStream 等这样一些类来加载内容。
访问数据内容或加载数据 ─ 可以通过两种方式来访问数据:一种是从加载的媒体内容中提取数据,一种是从外部文件(例如 XML 文件)中直接加载数据。可以通过使用 Bitmap 对象、BitmapData.draw() 方法、Sound.id3 属性或者 SoundMixer.computeSpectrum() 方法从加载的媒体中提取数据,使用诸如 URLStream、URLLoader、Socket 和 XMLSocket 等类来加载数据。
Flash Player 安全模型针对加载内容和访问数据定义了不同的规则。通常,加载内容的限制要比访问数据的限制少一些。
通常,可以从任意位置加载内容(SWF 文件、位图、MP3 文件和视频),但是如果内容来自执行加载的 SWF 文件所在的域之外的域,则会将内容划分到单独的安全沙箱中。
下面是加载内容的一些限制:
默认情况下,本地 SWF 文件(从非网络地址加载的文件,例如用户硬盘上的文件)会被分类到只能与本地文件系统内容交互的沙箱中。这些文件无法从网络加载内容。
实时消息传递协议 (RTMP) 服务器可以限制对内容的访问。
如果加载的媒体为图像、音频或视频,则其安全沙箱之外的 SWF 文件无法访问该媒体的数据(如像素数据和声音数据),除非该 SWF 文件的域已包含在该媒体原始域的跨域策略文件中。
加载数据的其它格式包括文本文件或 XML 文件,这些文件可使用 URLLoader 对象来加载。同样,这种情况下要访问其它安全沙箱中的任何数据,必须通过原始域中的跨域策略文件来授予权限。
权限控制概述
Flash Player 客户端运行时安全模型是围绕 SWF 文件、本地数据和 Internet URL 等这些对象资源设计而成的模型。"资源持有者"是指拥有或使用这些资源的各方。资源持有者可以对其自己的资源进行控制(安全设置),每种资源有四个持有者。Flash Player 对这些控制严格采用一种权利层次结构,如下图所示:

安全控制层次结构
该图说明,如果管理员限制对资源的访问,则任何其他持有者都不能覆盖该限制。
管理用户控制
计算机的管理用户(使用管理权限登录的用户)可以应用能影响计算机所有用户的 Flash Player 安全设置。在非企业环境(例如家庭计算机)中,通常只有一个用户,该用户也拥有管理访问权限。即使是在企业环境中,单个用户也可以拥有计算机管理权限。
管理用户控制有两种类型:
mms.cfg 文件
"全局 Flash Player 信任"目录
mms.cfg 文件
在 Mac OS X 系统上,mms.cfg 文件位于 /Library/Application Support/Macromedia 中。在 Microsoft Windows 系统上,该文件位于系统目录的 Macromedia Flash Player 文件夹中(例如,在 Windows XP 默认安装中为 C:windowssystem32macromedflashmms.cfg)。
Flash Player 启动时将从此文件中读取其安全设置,然后使用这些设置限制功能。
mms.cfg 文件包括管理员用于执行以下任务的设置:
数据加载 ─ 限制读取本地 SWF 文件、禁止文件下载和上载以及对永久共享对象设置存储限制。
隐私控制 ─ 禁止麦克风和摄像头访问、禁止 SWF 文件播放无窗口内容,以及禁止与浏览器窗口中显示的 URL 不匹配的域中的 SWF 文件访问永久共享对象。
Flash Player 更新 ─ 设置检查 Flash Player 更新版本的时间间隔、指定检查 Flash Player 更新信息所使用的 URL、指定从其中下载 Flash Player 更新版本的 URL 以及完全禁用 Flash Player 的自动更新。
旧版文件支持 ─ 指定是否应将早期版本的 SWF 文件放置在受信任的本地沙箱中。
本地文件安全性 ─ 指定是否可以将本地文件放置在受信任的本地沙箱中。
全屏模式 ─ 禁用全屏模式。
SWF 文件可通过调用 Capabilities.avHardwareDisable 和 Capabilities.localFileReadDisable 属性来访问已禁用功能的某些信息。但是,mms.cfg 文件中的大部分设置无法通过 ActionScript 进行查询。
为对计算机强制执行与应用程序无关的安全和隐私策略,只能由系统管理员修改 mms.cfg 文件。mms.cfg 文件不能用于安装应用程序。虽然使用管理权限运行的安装程序可以修改 mms.cfg 文件的内容,但是 Adobe 将此类使用视为违反用户的信任,并且劝告安装程序的创建者决不要修改 mms.cfg 文件。
"全局 Flash Player 信任"目录
管理用户和安装应用程序可以将指定的本地 SWF 文件注册为受信任。这些 SWF 文件会被分配到受信任的本地沙箱。它们可以与任何其它 SWF 文件进行交互,也可以从任意位置(远程或本地)加载数据。文件在"全局 Flash Player 信任"目录中被指定为受信任,该目录与 mms.cfg 文件的所在目录相同,位置(特定于当前用户)如下:
Windows:systemMacromedFlashFlashPlayerTrust
(例如,C:windowssystem32MacromedFlashFlashPlayerTrust)
"Flash Player 信任"目录可以包含任意数目的文本文件,每个文件均列出受信任的路径,一个路径占一行。每个路径可以是单个的 SWF 文件、HTML 文件,也可以是目录。注释行以 # 号开头。例如,包含以下文本的 Flash Player 信任配置文件表示将向指定目录及所有子目录中的所有文件授予受信任状态:
# Trust files in the following directories:
C:Documents and SettingsAll UsersDocumentsSampleApp
信任配置文件中列出的路径应始终是本地路径或 SMB 网络路径。信任配置文件中的任何 HTTP 路径均会被忽略;只能信任本地文件。
为避免发生冲突,应为每个信任配置文件指定一个与安装应用程序相应的文件名,并且使用 .cfg 文件扩展名。
由于开发人员通过安装应用程序分发本地运行的 SWF 文件,因此可以让安装应用程序向"全局 Flash Player 信任"目录添加一个配置文件,为要分发的文件授予完全访问权限。安装应用程序必须由拥有管理权限的用户来运行。与 mms.cfg 文件不同,包含"全局 Flash Player 信任"目录是为了让安装应用程序授予信任权限。管理用户和安装应用程序都可以使用"全局 Flash Player 信任"目录指定受信任的本地应用程序。
此外,还有适用于单个用户的"Flash Player 信任"目录。
用户控制
Flash Player 提供三种不同的用户级别权限设置机制:"设置 UI"、"设置管理器"和"用户 Flash Player 信任"目录。
设置 UI 和设置管理器
"设置 UI"是一种用于配置特定域设置的快速交互机制。"设置管理器"显示一个更详细的界面,并提供全局更改功能,全局更改可影响对许多域或所有域拥有的权限。另外,当 SWF 文件请求新的权限,要求有关安全或隐私的运行时决策时,程序会显示一些对话框,用户可以在这些对话框中调整某些 Flash Player 设置。
"设置管理器"和"设置 UI"提供以下安全相关选项:
摄像头和麦克风设置 ─ 用户可以控制 Flash Player 对计算机上的摄像头和麦克风的访问。用户可以允许或拒绝对所有站点或特定站点的访问。如果用户没有为所有站点或特定站点指定设置,则当 SWF 文件试图访问摄像头或麦克风时,程序会显示一个对话框,让用户选择是否允许 SWF 文件访问该设备。用户也可以指定要使用的摄像头或麦克风,还可以设置麦克风的敏感度。
共享对象存储设置 ─ 用户可以选择域能够用来存储永久共享对象的磁盘空间量。用户可以对任意数量的特定域进行这些设置,还可以为新域指定默认设置。默认限制是 100 KB 磁盘空间。有关永久共享对象的详细信息,请参阅《ActionScript 3.0 语言和组件参考》中的 SharedObject 类。
在 mms.cfg 文件中所做的任何设置均不会反映在"设置管理器"中。
"用户 Flash Player 信任"目录
用户和安装应用程序可以将指定的本地 SWF 文件注册为受信任。这些 SWF 文件会被分配到受信任的本地沙箱。它们可以与任何其它 SWF 文件进行交互,也可以从任意位置(远程或本地)加载数据。用户在"用户 Flash Player 信任"目录中将文件指定为受信任,该目录与 Flash 共享对象存储区域的所在目录相同,位置(特定于当前用户)如下:
Windows:app dataMacromediaFlash Player#SecurityFlashPlayerTrust
(例如,C:Documents and SettingsJohnDApplication DataMacromediaFlash Player#SecurityFlashPlayerTrust)
这些设置只会影响当前用户,不会影响登录到计算机的其他用户。如果没有管理权限的用户在属于他们自己的系统中安装了某个应用程序,则"用户 Flash Player 信任"目录允许安装程序将该应用程序注册为该用户的受信任程序。
由于开发人员通过安装应用程序分发本地运行的 SWF 文件,因此可以让安装应用程序向"用户 Flash Player 信任"目录添加一个配置文件,为要分发的文件授予完全访问权限。即使在这种情况下,也将"用户 Flash Player 信任"目录文件视为用户控制,原因是用户操作(安装)启动了它。
此外,还有一个"全局 Flash Player 信任"目录,管理用户或安装程序可使用该目录为所有计算机用户注册应用程序。
Web 站点控制(跨域策略文件)
要使来自某个 Web 服务器的数据可用于来自其它域的 SWF 文件,可以在服务器上创建一个跨域策略文件。"跨域策略文件"是一个 XML 文件,它为服务器提供了一种方式,以指示该服务器的数据和文档可用于从某些域或所有域提供的 SWF 文件。服务器策略文件指定的域所提供的所有 SWF 文件都将被允许访问该服务器中的数据或资源。
跨域策略文件可影响对许多资源的访问,其中包括以下内容:
位图、声音和视频中的数据
加载 XML 和文本文件
对套接字和 XML 套接字连接的访问
将 SWF 文件从其它安全域导入到执行加载的 SWF 文件所在的安全域
策略文件语法
下面的示例显示了一个策略文件,该文件允许访问源自 *.example.com、www.friendOfExample.com 和 192.0.34.166 的 SWF 文件。
<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*.example.com" />
<allow-access-from domain="www.friendOfExample.com" />
<allow-access-from domain="192.0.34.166" />
</cross-domain-policy>
当某个 SWF 文件试图访问另一个域中的数据时,Flash Player 会尝试自动从该域加载策略文件。如果试图访问数据的 SWF 文件所在的域包括在该策略文件中,则数据将自动成为可访问数据。
默认情况下,策略文件必须命名为 crossdomain.xml,并且必须位于服务器的根目录中。但是,SWF 文件可以通过调用 Security.loadPolicyFile() 方法检查是否为其它名称或位于其它目录中。跨域策略文件仅适用于从其中加载该文件的目录及其子目录。因此,根目录中的策略文件适用于整个服务器,但是从任意子目录加载的策略文件仅适用于该目录及其子目录。
策略文件仅影响对其所在特定服务器的访问。例如,位于 https://www.loach.net.cn8080/crossdomain.xml 的策略文件只适用于在端口 8080 通过 HTTPS 对 www.loach.net.cn进行的数据加载调用。
跨域策略文件包含单个 <cross-domain-policy> 标签,该标签又包含零个或多个 <allow-access-from> 标签。每个 <allow-access-from> 标签包含一个属性 domain,该属性指定一个确切的 IP 地址、一个确切的域或一个通配符域(任何域)。通配符域由单个星号 (*)(匹配所有域和所有 IP 地址)或后接后缀的星号(只匹配那些以指定后缀结尾的域)表示。后缀必须以点开头。但是,带有后缀的通配符域可以匹配那些只包含后缀但不包含前导点的域。例如,foo.com 会被看作是 *.foo.com 的一部分。IP 域规范中不允许使用通配符。
如果您指定了一个 IP 地址,则只向使用 IP 语法从该 IP 地址加载的 SWF 文件(例如 http://65.57.83.12/flashmovie.swf)授予访问权限,而不向使用域名语法加载的 SWF 文件授予访问权限。Flash Player 不执行 DNS 解析。
您可以允许访问来自任何域的文档,如下面的示例所示:
<?xml version="1.0"?>
<!-- http://www.loach.net.cnrossdomain.xml -->
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>
每个 <allow-access-from> 标签还具有可选的 secure 属性,其默认值为 true。如果您的策略文件在 HTTPS 服务器上,并且要允许非 HTTPS 服务器上的 SWF 文件从 HTTPS 服务器加载数据,则可以将此属性设置为 false。
将 secure 属性设置为 false 可能会危及 HTTPS 提供的安全性。特别是将此属性设置为 false 时,会使安全内容受到电子欺骗和窃听攻击。Adobe 强烈建议不要将 secure 属性设置为 false。
如果要加载的数据位于 HTTPS 服务器上,但是加载数据的 SWF 文件位于 HTTP 服务器上,则 Adobe 建议将要执行加载的 SWF 文件移动到 HTTPS 服务器上,以便可以使安全数据的所有副本都能得到 HTTPS 的保护。但是,如果决定必须将要执行加载的 SWF 文件保存在 HTTP 服务器上,则需将 secure="false" 属性添加到 <allow-access-from> 标签,如以下代码所示:
<allow-access-from domain="www.loach.net.cn secure="false" />
不包含任何 <allow-access-from> 标签的策略文件相当于服务器上没有策略。
套接字策略文件
ActionScript 对象可实例化两种不同的服务器连接:基于文档的服务器连接和套接字连接。Loader、Sound、URLLoader 和 URLStream 等 ActionScript 对象可实例化基于文档的服务器连接,这些对象均根据 URL 加载文件。ActionScript Socket 和 XMLSocket 对象进行套接字连接,这些对象操作的是数据流而非加载的文档。Flash Player 支持两种策略文件:基于文档的策略文件和套接字策略文件。基于文档的连接需要基于文档的策略文件,套接字连接则需要套接字策略文件。
Flash Player 要求使用尝试连接希望使用的同类协议传输策略文件。例如,如果将策略文件放置在您的 HTTP 服务器上,则允许其它域中的 SWF 文件从该服务器(作为 HTTP 服务器)加载数据。但是,如果在这台服务器上未提供套接字策略文件,则禁止其它域的 SWF 文件在套接字级别连接到该服务器。检索套接字策略文件的方法必须与连接方法相匹配。
由套接字服务器提供的策略文件具有与任何其它策略文件相同的语法,只是前者还必须指定要对哪些端口授予访问权限。如果策略文件来自低于 1024 的端口号,则它可以对任何端口授予访问权限;如果策略文件来自 1024 或更高的端口,则它只能对 1024 端口和更高的端口授予访问权限。允许的端口在 <allow-access-from> 标签的 to-ports 属性中指定。单个端口号、端口范围和通配符都是允许值。
下面是一个 XMLSocket 策略文件示例:
<cross-domain-policy>
<allow-access-from domain="*" to-ports="507" />
<allow-access-from domain="*.example.com" to-ports="507,516" />
<allow-access-from domain="*.example2.com" to-ports="516-523" />
<allow-access-from domain="www.loach.net.cn to-ports="507,516-523" />
<allow-access-from domain="www.loach.net.cn to-ports="*" />
</cross-domain-policy>
在 Flash Player 6 中首次引入策略文件时,并不支持套接字策略文件。与套接字服务器的连接由跨域策略文件所在默认位置中的一个策略文件授权,跨域策略文件位于与套接字服务器位于同一个域中的 HTTP 服务器的端口 80 上。为尽可能保留现有的服务器排列,Flash Player 9 仍然支持此功能。但是,Flash Player 现在的默认设置是在与套接字连接相同的端口上检索套接字策略文件。如果希望使用基于 HTTP 的策略文件来授权套接字连接,则必须使用如下所示代码显式请求 HTTP 策略文件:
Security.loadPolicyFile("http://socketServerHost.com/crossdomain.xml")
此外,为授权套接字连接,HTTP 策略文件只能来自跨域策略文件的默认位置,而非来自任何其它 HTTP 位置。从 HTTP 服务器获取的策略文件隐式向 1024 和所有更高端口授予套接字访问权限;HTTP 策略文件中的任何 to-ports 属性均被忽略。
预加载策略文件
从服务器加载数据或连接到套接字是一种异步操作,Flash Player 只是等待跨域策略文件完成下载,然后才开始主操作。但是,从图像中提取像素数据或从声音中提取采样数据是一种同步操作,跨域策略文件必须在可以提取数据之前先加载数据。加载媒体时,需要指定媒体检查是否存在跨域策略文件:
使用 Loader.load() 方法时,设置 context 参数的 checkPolicyFile 属性,该参数是一个 LoaderContext 对象。
使用 <img> 标签在文本字段中嵌入图像时,将 <img> 标签的 checkPolicyFile 属性设置为 "true",如下所示:<img checkPolicyFile = "true" src = "example.jpg">。
使用 Sound.load() 方法时,设置 context 参数的 checkPolicyFile 属性,该参数是一个 SoundLoaderContext 对象。
使用 NetStream 类时,设置 NetStream 对象的 checkPolicyFile 属性。
设置上述参数时,Flash Player 首先会检查是否已经为该域下载了任何策略文件。然后考虑对 Security.loadPolicyFile() 方法的任何待定调用,以便查看它们是否在范围内,如果在范围内,则等待调用完成。然后,它查找服务器上默认位置中的跨域策略文件。
作者(开发人员)控制
用于授予安全权限的主 ActionScript API 是 Security.allowDomain() 方法,它将向指定域中的 SWF 文件授予权限。在下面的示例中,SWF 文件向 www.loach.net.cn域提供的 SWF 文件授予访问权限:
Security.allowDomain("www.loach.net.cn)
此方法为下列各项授予权限:
SWF 文件之间的跨脚本访问
显示列表访问
事件检测
对 Stage 对象的属性和方法的完全访问
调用 Security.allowDomain() 方法的主要目的是为外部域中的 SWF 文件授予权限以访问调用 Security.allowDomain() 方法的 SWF 文件的脚本。
如果将 IP 地址指定为 Security.allowDomain() 方法的参数,则不允许任何源自该指定 IP 地址的访问方进行访问。相反,只允许 URL 中包含该指定 IP 地址的访问方进行访问,而不允许其域名映射到该 IP 地址的访问方进行访问。例如,如果域名 www.loach.net.cn映射到 IP 地址 192.0.34.166,则对 Security.allowDomain("192.0.34.166") 的调用不会授予对 www.loach.net.cn的访问权限。
可以将通配符"*"传递给 Security.allowDomain() 方法以允许从所有域进行访问。由于这种方式会为"所有"域中的 SWF 文件授予访问执行调用的 SWF 文件的脚本的权限,因此请谨慎使用通配符"*"。
ActionScript 还包括一个权限 API,称为 Security.allowInsecureDomain()。此方法与 Security.allowDomain() 方法的作用相同,只是从安全 HTTPS 连接提供的 SWF 文件调用时,此方法还会允许非安全协议(例如 HTTP)提供的其它 SWF 文件访问执行调用的 SWF 文件。但是,在安全协议 (HTTPS) 中的文件与非安全协议(例如 HTTP)中的文件之间执行脚本访问操作并不是一种好的安全性做法;这样做会使安全内容受到电子欺骗和窃听攻击。下面是此类攻击的作用方式:由于 Security.allowInsecureDomain() 方法允许通过 HTTP 连接提供的 SWF 文件访问安全 HTTPS 数据,因此介入 HTTP 服务器和用户之间的攻击者能够将 HTTP SWF 文件替换为它们自己的文件,这样便可访问您的 HTTPS 数据。
另一种与安全性相关的重要方法是 Security.loadPolicyFile() 方法,该方法可让 Flash Player 在非标准位置检查是否存在跨域策略文件。
安全沙箱
客户端计算机可以从很多来源(如外部 Web 站点或本地文件系统)中获取单个 SWF 文件。当 SWF 文件及其它资源(例如共享对象、位图、声音、视频和数据文件)加载到 Flash Player 中时,Flash Player 会根据这些文件和资源的来源单独地将其分配到安全沙箱中。
远程沙箱
Flash Player 将来自 Internet 的资源(包括 SWF 文件)分类到单独的沙箱中,这些沙箱与各自 Web 站点原始域相对应。默认情况下,对这些文件授予访问其自身所在服务器中任何资源的权限。通过显式的 Web 站点许可和作者许可(例如跨域策略文件和 Security.allowDomain() 方法),可以允许远程 SWF 文件访问其它域的其它数据。
远程 SWF 文件无法加载任何本地文件或资源。
本地沙箱
"本地文件"是指通过使用 file: 协议或统一命名约定 (UNC) 路径引用的任何文件。本地 SWF 文件放置在三个本地沙箱中的一个内:
只能与本地文件系统内容交互的沙箱 ─ 出于安全性考虑,Flash Player 在默认情况下将所有本地 SWF 文件和资源放置在只能与本地文件系统内容交互的沙箱中。通过此沙箱,SWF 文件可以读取本地文件(例如通过使用 URLLoader 类),但是它们无法以任何方式与网络进行通信。这样可向用户保证本地数据不会泄漏到网络或以其它方式不适当地共享。
只能与远程内容交互的沙箱 ─ 编译 SWF 文件时,可以指定该文件作为本地文件运行时拥有网络访问权限。这些文件放置在只能与远程内容交互的沙箱中。分配到只能与远程内容交互的沙箱中的 SWF 文件将失去其本地文件访问权限,但允许这些 SWF 文件访问网络中的数据。不过,只有通过跨域策略文件或调用 Security.allowDomain() 方法来授予操作权限,才允许远程内容交互的 SWF 文件读取源自网络的数据。为授予此类权限,跨域策略文件必须向"所有"域授予权限,方法是使用 <allow-access-from domain="*"/> 或使用 Security.allowDomain("*")。
受信任的本地沙箱 ─ 注册为受信任(由用户或安装程序注册)的本地 SWF 文件放置在受信任的本地沙箱中。系统管理员和用户还可以根据安全注意事项将本地 SWF 文件重新分配(移动)到受信任的本地沙箱,或者从该沙箱中进行重新分配。分配到受信任的本地沙箱的 SWF 文件可以与其它任何 SWF 文件交互,也可以从任何位置(远程或本地)加载数据。
只能与远程内容交互的沙箱和只能与本地文件系统内容交互的沙箱之间的通信以及只能与本地文件系统内容交互的沙箱和远程沙箱之间的通信是严格禁止的。Flash 应用程序或用户/管理员不能授予允许此类通信的权限。
在本地 HTML 文件和本地 SWF 文件之间以任一方向访问脚本(例如使用 ExternalInterface 类)均要求涉及的 HTML 文件和 SWF 文件应位于受信任的本地沙箱中。这是因为浏览器的本地安全模型与 Flash Player 本地安全模型不同。
只能与远程内容交互的沙箱中的 SWF 文件无法加载只能与本地文件系统内容交互的沙箱中的 SWF 文件。只能与本地文件系统内容交互的沙箱中的 SWF 文件无法加载只能与远程内容交互的沙箱中的 SWF 文件。
设置本地 SWF 文件的沙箱类型
通过在 Adobe Flash CS3 创作工具中设置文档发布设置,您可以配置只能与本地文件系统内容交互的沙箱或只能与远程内容交互的沙箱的 SWF 文件。
最终用户或计算机管理员可以指定某个本地 SWF 文件是受信任的,以允许该文件从所有域(本地和网络)加载数据。这一点在"全局 Flash Player 信任"目录和"用户 Flash Player 信任"目录中指定。
Security.sandboxType 属性
SWF 文件的作者可以使用只读的静态 Security.sandboxType 属性来确定 Flash Player 向其分配该 SWF 文件的沙箱类型。Security 类包括表示 Security.sandboxType 属性可能值的常量,如下所示:
Security.REMOTE ─ SWF 文件来自 Internet URL,并遵守基于域的沙箱规则。
Security.LOCAL_WITH_FILE ─ SWF 文件是本地文件,但尚未受到用户信任,且没有使用网络名称进行发布。此 SWF 文件可以从本地数据源读取数据,但无法与 Internet 进行通信。
Security.LOCAL_WITH_NETWORK ─ SWF 文件是本地文件,且尚未受到用户信任,但已使用网络名称进行发布。此 SWF 文件可与 Internet 通信,但不能从本地数据源读取数据。
Security.LOCAL_TRUSTED ─ SWF 文件是本地文件,且已使用"设置管理器"或 Flash Player 信任配置文件受到用户信任。此 SWF 文件既可以从本地数据源读取数据,也可以与 Internet 进行通信。
限制网络 API
可以控制 SWF 文件对网络功能的访问,方法是通过在包含 SWF 内容的 HTML 页面的 <object> 和 <embed> 标签中设置 allowNetworking 参数。
allowNetworking 的可能值包括:
"all"(默认值)─ SWF 文件中允许使用所有网络 API。
"internal"─ SWF 文件可能不调用浏览器导航或浏览器交互 API(在本节后面部分中列出),但是它会调用任何其它网络 API。
"none"─ SWF 文件可能不调用浏览器导航或浏览器交互 API(在本节后面部分中列出),并且它无法使用任何 SWF 到 SWF 通信 API(也在本节后面部分中列出)。
调用被禁止的 API 会引发 SecurityError 异常。
要设置 allowNetworking 参数,在包含 SWF 文件引用的 HTML 页面的 <object> 和 <embed> 标签中添加 allowNetworking 参数并设置参数值。
HTML 页面也可能会使用脚本来生成 SWF 嵌入式标签。您需要更改该脚本,以便让它能够插入适当的 allowNetworking 设置。由 Flash 和 Adobe Flex Builder 生成的 HTML 页面使用 AC_FL_RunContent() 函数嵌入 SWF 文件的引用,您需要将 allowNetworking 参数设置添加到该脚本,如下所示:
AC_FL_RunContent( ... "allowNetworking", "none", ...)
当 allowNetworking 设置为 "internal" 时,以下 API 被禁止:
navigateToURL() 、fscommand() 、ExternalInterface.call() 。
当 allowNetworking 设置为 "none" 时,除了上面列出的那些 API 外,还会禁止以下 API:
sendToURL()、 FileReference.download()、 FileReference.upload()、 Loader.load() 、LocalConnection.connect()、 LocalConnection.send()、 NetConnection.connect()、 NetStream.play()、Security.loadPolicyFile()、 SharedObject.getLocal()、 SharedObject.getRemote()、Socket.connect() 、Sound.load()、 URLLoader.load()、 URLStream.load()、 XMLSocket.connect()。
即使所选 allowNetworking 设置允许 SWF 文件使用网络 API,但根据安全沙箱的限制,还可能存在其它限制。
当 allowNetworking 设置为"none"时,无法在 TextField 对象 htmlText 属性的 <img> 标签中引用外部媒体(这会引发 SecurityError 异常)。
当 allowNetworking 设置为 "none" 时,从导入的共享库添加到 Flash 创作工具(而不是 ActionScript)中的元件在运行时被阻止。
全屏模式安全性
Flash Player 9.0.27.0 和更高版本支持全屏模式,在该模式中 Flash 内容可以填满整个屏幕。要进入全屏模式,需将 Stage 的 displayState 属性设置为 StageDisplayState.FULL_SCREEN 常量。对于在浏览器中运行的 SWF 文件,存在一些安全注意事项。
要启用全屏模式,请在包含 SWF 文件引用的 HTML 页面的 <object> 和 <embed> 标签中添加 allowFullScreen 参数,并将参数值设置为"true"(默认值为"false")。
HTML 页面也可能会使用脚本来生成 SWF 嵌入式标签。您必须更改该脚本,以便让它能够插入适当的 allowFullScreen 设置。由 Flash 和 Flex Builder 生成的 HTML 页面使用 AC_FL_RunContent() 函数嵌入 SWF 文件的引用,您需要添加 allowFullScreen 参数设置,如下所示:
AC_FL_RunContent( ... "allowFullScreen", "true", ...)
仅当在响应鼠标事件或键盘事件时才会调用启动全屏模式的 ActionScript。如果在其它情况中调用,Flash Player 会引发异常。
在全屏模式下,用户无法在文本输入字段中输入文本。所有键盘输入和键盘相关的 ActionScript 在全屏模式下均会被禁用,但将应用程序返回标准模式的键盘快捷键(例如按 Esc)除外。
当内容进入全屏模式时,程序会显示一条消息,指导用户如何退出和返回标准模式。该消息将显示几秒钟,然后淡出。
如果某个调用方与 Stage 所有者(主 SWF 文件)没有位于同一安全沙箱,则调用 Stage 对象的 displayState 属性会引发异常。
管理员可以通过在 mms.cfg 文件中设置 FullScreenDisable = 1 对浏览器中运行的 SWF 文件禁用全屏模式。
在浏览器中,必须在 HTML 页面中包含 SWF 文件,才能进入全屏模式。
在独立的播放器或放映文件中始终允许全屏模式。
加载内容
SWF 文件可以加载以下内容类型: SWF文件、 图像、声音、 视频 。
加载 SWF 文件和图像
使用 Loader 类加载 SWF 文件和图像(JPG、GIF 或 PNG 文件)。除只能与本地文件系统内容交互的沙箱中的 SWF 文件之外,其它所有 SWF 文件都可以从任何网络域加载 SWF 文件和图像。只有本地沙箱中的 SWF 文件才能从本地文件系统中加载 SWF 文件和图像。但是,只能与远程内容交互的沙箱中的文件只能加载位于受信任的本地沙箱或只能与远程内容交互的沙箱中的本地 SWF 文件。只能与远程内容交互的沙箱中的 SWF 文件可加载非 SWF 文件(例如图像)的本地内容,但是无法访问所加载内容中的数据。
从不受信任的来源(如 Loader 对象的根 SWF 文件所在域以外的域)加载 SWF 文件时,您可能需要为 Loader 对象定义遮罩,以防止加载的内容(Loader 对象的子级)绘制到该遮罩之外的 Stage 部分中。
当调用 Loader 对象的 load() 方法时,可以指定一个 context 参数,该参数是一个 LoaderContext 对象。LoaderContext 类包括三个属性,用于定义如何使用加载的内容的上下文:
checkPolicyFile:仅当加载图像文件(不是 SWF 文件)时才会使用此属性。如果图像文件所在的域与包含 Loader 对象的文件所在的域不同,则指定此属性。如果将此属性设置为 true,Loader 将检查跨域策略文件的原始服务器。如果服务器授予 Loader 域适当权限,则来自 Loader 域中 SWF 文件的 ActionScript 可以访问所加载图像中的数据。换言之,可以使用 Loader.content 属性获取对表示所加载图像的 Bitmap 对象的引用,或使用 BitmapData.draw() 方法访问所加载图像中的像素。
securityDomain:仅当加载 SWF 文件(不是图像)时才会使用此属性。如果 SWF 文件所在的域与包含 Loader 对象的文件所在的域不同,则指定此属性。对于 securityDomain 属性而言,目前仅支持以下两个值:null(默认值)和 SecurityDomain.currentDomain。如果指定 SecurityDomain.currentDomain,则要求加载的 SWF 文件应"导入"到执行加载的 SWF 文件所在的沙箱中,这意味着其运行方式就像它已从执行加载的 SWF 文件自己的服务器中加载一样。只有在位于加载的 SWF 文件服务器上找到跨域策略文件时才允许这样做,从而允许执行加载的 SWF 文件所在的域进行访问。如果找到所需的策略文件,则一旦加载开始,加载方和被加载方可以自由地互相访问脚本,原因是它们位于同一沙箱中。请注意,多数情况可以通过执行普通加载操作然后让加载的 SWF 文件调用 Security.allowDomain() 方法来取代沙箱导入。由于加载的 SWF 文件将位于自己的原始沙箱中,并因而能够访问自己实际服务器上的资源,因此后一种方法会更易于使用。
applicationDomain:仅当加载使用 ActionScript 3.0 编写的 SWF 文件(不是图像或使用 ActionScript 1.0 或 2.0 编写的 SWF 文件)时才会使用此属性。当加载文件时,可以指定文件应放置在特定的应用程序域中,而不是默认放置在一个新的应用程序域中,这个新的应用程序域是执行加载的 SWF 文件所在应用程序域的子域。请注意,应用程序域是安全域的子单位,因此仅当要加载的 SWF 文件由于以下原因来自您自己的安全域时,才能指定目标应用程序域:该文件来自您自己的服务器,或者使用 securityDomain 属性已成功地将该文件导入到您的安全域中。如果指定应用程序域,但加载的 SWF 文件属于其它安全域,则在 applicationDomain 中指定的域将被忽略。
Loader 对象的一个重要属性就是 contentLoaderInfo 属性,该属性是一个 LoaderInfo 对象。与大部分对象不同,LoaderInfo 对象在执行加载的 SWF 文件和被加载的内容之间共享,并且双方始终可以访问该对象。当被加载的内容为 SWF 文件时,它可以通过 DisplayObject.loaderInfo 属性访问 LoaderInfo 对象。LoaderInfo 对象包括诸如加载进度、加载方和被加载方的 URL、加载方和被加载方之间的信任关系等信息及其它信息。
加载声音和视频
除只能与本地文件系统内容交互的沙箱中的那些 SWF 文件之外,所有 SWF 文件都允许从网络来源加载声音和视频,使用 Sound.load()、NetConnection.connect() 和 NetStream.play() 方法即可。
只有本地 SWF 文件才能从本地文件系统加载媒体。只有只能与本地文件系统内容交互的沙箱或受信任的本地沙箱中的 SWF 文件才能访问这些加载文件中的数据。
对加载的媒体还存在一些其它数据访问限制。
使用文本字段中的 <img> 标签加载 SWF 文件和图像
通过使用 <img> 标签,可以将 SWF 文件和位图加载到文本字段中,如以下代码所示:
<img src = 'filename.jpg' id = 'instanceName' >
通过使用 TextField 实例的 getImageReference() 方法,可以访问以这种方式加载的内容,如以下代码所示:
var loadedObject:DisplayObject = myTextField.getImageReference('instanceName');
但是请注意,以这种方式加载的 SWF 文件和图像会被放入与各自来源相应的沙箱中。
当在文本字段中使用 <img> 标签加载图像文件时,通过跨域策略文件可以允许访问图像中的数据。通过将 checkPolicyFile 属性添加到 <img> 标签上,可以检查是否存在策略文件,如以下代码所示:
<img src = 'filename.jpg' checkPolicyFile = 'true' id = 'instanceName' >
当在文本字段中使用 <img> 标签加载 SWF 时,可以允许通过调用 Security.allowDomain() 方法来访问该 SWF 文件的数据。
当在文本字段中使用 <img> 标签加载外部文件时(相对于使用嵌在 SWF 文件中的 Bitmap 类),会自动创建一个 Loader 对象作为 TextField 对象的子对象,并且会将外部文件加载到该 Loader 对象中,就如同使用了 ActionScript 中的 Loader 对象来加载文件一样。在这种情况下,getImageReference() 方法返回自动创建的 Loader。由于此 Loader 对象与调用代码位于同一安全沙箱中,因此访问此对象不需要任何安全检查。
但是,当引用 Loader 对象的 content 属性来访问加载的媒体时,需要应用安全性规则。如果内容是图像,则需要实现跨域策略文件;如果内容是 SWF 文件,则需要让 SWF 文件中的代码调用 allowDomain() 方法。
使用 RTMP 服务器传送的内容
Flash Media Server 使用实时媒体协议 (RTMP) 提供数据、音频和视频。SWF 文件通过使用 NetConnection 类的 connect() 方法并作为参数传递 RTMP URL 来加载此媒体。Flash Media Server 可以根据所请求文件的域来限制连接并防止内容被下载。
对于从 RTMP 源加载的媒体,不能使用 BitmapData.draw() 和 SoundMixer.computeSpectrum() 方法来提取运行时图形和声音数据。
跨脚本访问
如果两个使用 ActionScript 3.0 编写的 SWF 文件来自同一个域,例如,一个 SWF 文件的 URL 是 http://www.loach.net.cnswfA.swf,另一个文件的 URL 是 http://www.loach.net.cnswfB.swf,则一个 SWF 文件可以检查并修改另一个 SWF 文件中的变量、对象、属性、方法等等,反之亦然。这称为“跨脚本访问”。
在 AVM1 SWF 文件和 AVM2 SWF 文件之间不支持跨脚本访问。AVM1 SWF 文件是使用 ActionScript 1.0 或 ActionScript 2.0 创建的文件。(AVM1 和 AVM2 指的是 ActionScript 虚拟机。)但是,可以使用 LocalConnection 类在 AVM1 和 AVM2 之间发送数据。
如果两个使用 ActionScript 3.0 编写的 SWF 文件来自不同的域(例如,http://siteA.com/swfA.swf 和 http://siteB.com/swfB.swf),则在默认情况下,Flash Player 既不允许 swfA.swf 访问 swfB.swf 的脚本,也不允许 swfB.swf 访问 swfA.swf 的脚本。通过调用 Security.allowDomain(),一个 SWF 文件可向其它域中的 SWF 文件授予访问其脚本的权限。通过调用 Security.allowDomain("siteA.com"),swfB.swf 向来自 siteA.com 的 SWF 文件授予访问其脚本的权限。
在任何跨域的情况下,明确所涉及的双方非常重要。为了便于进行此讨论,我们将执行跨脚本访问的一方称为“访问方”(通常是执行访问的 SWF),将另一方称为“被访问方”(通常是被访问的 SWF)。当 siteA.swf 访问 siteB.swf 的脚本时,siteA.swf 是访问方,siteB.swf 是被访问方,如下图所示:


使用 Security.allowDomain() 方法建立的跨域权限是不对称的。在上例中,siteA.swf 可以访问 siteB.swf 的脚本,但 siteB.swf 无法访问 siteA.swf 的脚本,这是因为 siteA.swf 未调用 Security.allowDomain() 方法来授予 siteB.com 中的 SWF 文件访问其脚本的权限。可以通过使两个 SWF 文件都调用 Security.allowDomain() 方法来设置对称权限。
除了防止 SWF 文件受到其它 SWF 文件发起的跨域脚本访问外,Flash Player 还防止 SWF 文件受到 HTML 文件发起的跨域脚本访问。可以通过 ExternalInterface.addCallback() 方法建立的回调执行 HTML 到 SWF 的脚本访问。当 HTML 到 SWF 的脚本访问跨域时,被访问的 SWF 文件必须调用 Security.allowDomain() 方法(这与访问方是 SWF 文件时一样),否则操作将失败。
此外,Flash Player 还对 SWF 到 HTML 的脚本访问提供安全控制。
Stage 安全性
Stage 对象的某些属性和方法可用于显示列表中的任何 sprite 或影片剪辑。
但是,我们说 Stage 对象有一个所有者,即加载的第一个 SWF 文件。默认情况下,Stage 对象的以下属性和方法只能用于与舞台所有者位于同一安全沙箱中的 SWF 文件:
属性
方法
align
showDefaultContextMenu
addChild()
displayState
stageFocusRect
addChildAt()
frameRate
stageHeight
addEventListener()
height
stageWidth
dispatchEvent()
mouseChildren
tabChildren
hasEventListener()
numChildren
textSnapshot
setChildIndex()
quality
width
willTrigger()
scaleMode


为使与 Stage 所有者不在同一沙箱中的 SWF 文件能够访问这些属性和方法,舞台所有者 SWF 文件必须调用 Security.allowDomain() 方法来允许外部沙箱的域。
frameRate 属性是一种特殊情况:任何 SWF 文件均能读取 frameRate 属性。但是,只有位于 Stage 所有者的安全沙箱中的文件(或通过调用 Security.allowDomain() 方法被授予权限的文件)才能更改该属性。
此外,对于 Stage 对象的 removeChildAt() 和 swapChildrenAt() 方法还存在一些限制,但是这些限制与其它限制不同。要调用这些方法,不需要与 Stage 所有者位于同一域中,而是代码必须与受影响的子对象位于同一域中,或者子对象可以调用 Security.allowDomain() 方法。
遍历显示列表
一个 SWF 文件能够访问从其它沙箱中加载的显示对象受到一定限制。为使 SWF 文件能够访问由其它沙箱中的另一个 SWF 文件创建的显示对象,被访问的 SWF 文件必须调用 Security.allowDomain() 方法向进行访问的 SWF 文件所在的域授予访问权限。
要访问由 Loader 对象加载的 Bitmap 对象,图像文件的原始服务器上必须存在跨域策略文件,并且该跨域策略文件必须为尝试访问该 Bitmap 对象的 SWF 文件所在的域授予访问权限。
与加载的文件(和 Loader 对象)相对应的 LoaderInfo 对象包括以下三种属性(定义加载的对象和 Loader 对象之间的关系):childAllowsParent、parentAllowsChild 和 sameDomain。
事件安全性
根据调度事件的显示对象所在的沙箱,与显示列表相关的事件具有一定的安全性访问限制。显示列表中的事件具有冒泡阶段和捕获阶段(在处理事件中论述)。在冒泡阶段和捕获阶段期间,事件从源显示对象开始迁移,并经过显示列表中的父显示对象。如果父对象与源显示对象位于不同的安全沙箱中,则捕获阶段和冒泡阶段在父对象的下方停止,除非父对象的所有者与源对象的所有者互相信任。这种互相信任可以通过以下方式来建立:
拥有父对象的 SWF 文件必须调用 Security.allowDomain() 方法,以信任拥有源对象的 SWF 文件所在的域。
拥有源对象的 SWF 文件必须调用 Security.allowDomain() 方法,以信任拥有父对象的 SWF 文件所在的域。
与加载的文件(和 Loader 对象)相对应的 LoaderInfo 对象包括以下两种属性(定义加载的对象和 Loader 对象之间的关系):childAllowsParent 和 parentAllowsChild。
对于从显示对象以外的对象调度的事件,不存在任何安全检查或与安全相关的含义。
作为数据访问加载的媒体
默认情况下,一个安全沙箱中的 SWF 文件无法从另一个沙箱中加载的媒体所呈现或播放的图形或音频对象中获取像素数据或音频数据。但是,可以使用以下方法授予这种权限:
在加载的 SWF 文件中,调用 Security.allowDomain() 方法可授予对其它域中 SWF 文件的数据访问权限。
对于加载的图像、声音或视频,在被加载文件所在的服务器上添加跨域策略文件。此策略文件必须向试图调用 BitmapData.draw() 或 SoundMixer.computeSpectrum() 方法的 SWF 文件所在的域授予访问权限,以便从文件提取数据。
访问位图数据
借助 BitmapData 对象的 draw() 方法,可以将任何显示对象的当前显示像素绘制到 BitmapData 对象。这样能够包括 MovieClip 对象、Bitmap 对象或任何显示对象的像素。要使用 draw() 方法将像素绘制到 BitmapData 对象,必须满足以下条件:
如果是除所加载位图之外的源对象,则该源对象及其(如果是 Sprite 或 MovieClip 对象)所有子对象必须与调用 draw() 方法的对象位于同一域,或者它们必须位于调用方通过调用 Security.allowDomain() 方法可访问的 SWF 文件中。
如果是已加载的位图源对象,则该源对象必须与调用 draw() 方法的对象位于同一域,或者其源服务器必须包含一个授予调用域访问权限的跨域策略文件。
如果不满足上述条件,则会引发 SecurityError 异常。
当使用 Loader 类的 load() 方法加载图像时,可以指定一个 context 参数,该参数是一个 LoaderContext 对象。如果将 LoaderContext 对象的 checkPolicyFile 属性设置为 true,则 Flash Player 将在从其中加载该图像的服务器上检查是否存在跨域策略文件。如果存在跨域策略文件且该文件允许执行加载的 SWF 文件所在的域进行访问,则会允许该文件访问 Bitmap 对象中的数据,否则就不允许。
此外,还可以通过文本字段中的 <img> 标签在加载的图像中指定 checkPolicyFile 属性。
访问声音数据
以下与声音相关的 ActionScript 3.0 API 存在一些安全限制:
SoundMixer.computeSpectrum() 方法 ─ 对于与声音文件位于同一安全沙箱的 SWF 文件,始终允许使用该方法。对于其它沙箱中的文件,则需经过安全检查。
SoundMixer.stopAll() 方法 ─ 对于与声音文件位于同一安全沙箱的 SWF 文件,始终允许使用该方法。对于其它沙箱中的文件,则需经过安全检查。
Sound 类的 id3 属性 ─ 对于与声音文件位于同一安全沙箱的 SWF 文件,始终允许使用该属性。对于其它沙箱中的文件,则需经过安全检查。
每个声音都具有两种与之关联的沙箱(一个内容沙箱和一个所有者沙箱):
声音的源域确定内容沙箱,内容沙箱则确定是否可以通过声音的 id3 属性和 SoundMixer.computeSpectrum() 方法提取声音中的数据。
启动声音播放的对象确定所有者沙箱,所有者沙箱则确定是否可以使用 SoundMixer.stopAll() 方法停止声音。
当使用 Sound 类的 load() 方法加载声音时,可以指定一个 context 参数,该参数是一个 SoundLoaderContext 对象。如果将 SoundLoaderContext 对象的 checkPolicyFile 属性设置为 true,则 Flash Player 将在从其中加载该声音的服务器上检查是否存在跨域策略文件。如果存在跨域策略文件且该文件允许执行加载的 SWF 文件所在的域进行访问,则会允许该文件访问 Sound 对象的 id 属性,否则就不允许。此外,设置 checkPolicyFile 属性可以为加载的声音启用 SoundMixer.computeSpectrum() 方法。
可以使用 SoundMixer.areSoundsInaccessible() 方法确定对 SoundMixer.stopAll() 方法的调用是否会因调用方无法访问一个或多个声音所有者的沙箱而停止全部声音。
调用 SoundMixer.stopAll() 方法会停止与 stopAll() 的调用方位于同一所有者沙箱中的那些声音。它还会停止由调用 Security.allowDomain() 方法的 SWF 文件来启动播放的声音,以允许调用 stopAll() 方法的 SWF 文件所在的域进行访问。任何其它声音均不会停止,可以通过调用 SoundMixer.areSoundsInaccessible() 方法来确定此类声音是否存在。
调用 computeSpectrum() 方法要求播放的每个声音应与调用该方法的对象位于同一沙箱中,或者位于已向调用方的沙箱授予权限的源;否则会引发 SecurityError 异常。对于从 SWF 文件库中嵌入声音加载的声音,通过在加载的 SWF 文件中调用 Security.allowDomain() 方法授予权限。对于从非 SWF 文件的源(源自加载的 MP3 文件或 Flash 视频)中加载的声音,源服务器上的跨域策略文件将授予访问所加载媒体中数据的权限。如果声音是从 RTMP 数据流加载的,则无法使用 computeSpectrum() 方法。
访问视频数据
可以使用 BitmapData.draw() 方法捕获视频当前帧中的像素数据。
视频有两种不同形式:
RTMP 视频
渐进式视频(该视频是从没有 RTMP 服务器的 FLV 文件加载的)
无法使用 BitmapData.draw() 方法访问 RTMP 视频。
当调用 BitmapData.draw() 方法,并且 source 参数为渐进式视频时,BitmapData.draw() 的调用方必须与 FLV 文件位于同一沙箱,或者 FLV 文件所在的服务器上必须存在一个策略文件,用以向执行调用的 SWF 文件所在的域授予访问权限。通过将 NetStream 对象的 checkPolicyFile 属性设置为 true,可以请求下载该策略文件。
加载数据
SWF 文件可以将服务器的数据加载到 ActionScript 中,也可以将 ActionScript 中的数据发送到服务器。加载数据与加载媒体的操作方式不同,原因是加载的信息将直接显示在 ActionScript 中,而不是作为媒体显示。通常,SWF 文件可以从它们自己的域中加载数据。但是,要从其它域加载数据,它们通常需要跨域策略文件。
使用 URLLoader 和 URLStream
可以加载诸如 XML 文件或文本文件等数据。URLLoader 和 URLStream 类的 load() 方法受到跨域策略文件权限的控制。
如果使用 load() 方法从与调用该方法的 SWF 文件所在域不同的域中加载内容,则 Flash Player 会在被加载资源所在的服务器上检查是否存在跨域策略文件。如果存在跨域策略文件,并且该文件向执行加载的 SWF 文件所在的域授予访问权限,则可以加载数据。
连接到套接字
默认情况下,禁用对套接字和 XML 套接字连接的跨域访问。此外,默认情况下还禁止访问与低于 1024 的端口上的 SWF 文件位于同一个域的套接字连接,但可以通过提供以下任一位置中的跨域策略文件来允许访问这些端口:
与主套接字连接相同的端口
不同端口
与套接字服务器位于同一个域的 HTTP 服务器的端口 80 上
如果提供的跨域策略文件与主套接字连接位于同一端口,或者位于不同端口,则通过在跨域策略文件中使用 to-ports 属性来枚举允许的端口,如下例所示:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy
SYSTEM "http://www.loach.net.cnxml/dtds/cross-domain-policy.dtd">
<!-- Policy file for xmlsocket://socks.mysite.com -->
<cross-domain-policy>
<allow-access-from domain="*" to-ports="507" />
<allow-access-from domain="*.example.com" to-ports="507,516" />
<allow-access-from domain="*.example.org" to-ports="516-523" />
<allow-access-from domain="adobe.com" to-ports="507,516-523" />
<allow-access-from domain="192.0.34.166" to-ports="*" />
</cross-domain-policy>
要检索与主套接字连接位于相同端口中的套接字策略文件,只需调用 Socket.connect() 或 XMLSocket.connect() 方法;并且,如果指定的域与执行调用的 SWF 文件所在的域不同,Flash Player 将自动尝试从正在尝试的主连接所在的相同端口中检索策略文件。要从与主连接位于同一服务器上的不同端口检索套接字策略文件,需使用特殊的"xmlsocket"语法调用 Security.loadPolicyFile() 方法,如下所示:
Security.loadPolicyFile("xmlsocket://server.com:2525");
先调用 Security.loadPolicyFile() 方法,然后再调用 Socket.connect() 或 XMLSocket.connect() 方法。Flash Player 随后将一直等待完成策略文件请求,之后再决定是否允许主连接。
如果要实现套接字服务器,并且需要提供套接字策略文件,则应决定是使用接受主连接的同一端口提供策略文件,还是使用不同的端口来提供策略文件。无论是哪种情况,服务器均必须等待客户端的第一次传输之后再决定是发送策略文件还是建立主连接。当 Flash Player 请求策略文件时,它始终会在建立连接后传输以下字符串:
<policy-file-request/>
服务器收到此字符串后,即会传输该策略文件。程序对于策略文件请求和主连接并不会使用同一连接,因此应在传输策略文件后关闭连接。如果不关闭连接,Flash Player 将关闭策略文件连接,之后重新连接以建立主连接。
发送数据
当 SWF 文件中的 ActionScript 代码向服务器或资源发送数据时,将会发生数据发送操作。对于网络域 SWF 文件,始终允许发送数据。本地 SWF 文件则只有在位于受信任的本地沙箱或只能与远程内容交互的沙箱中时,才能向网络地址发送数据。
可以使用 flash.net.sendToURL() 函数向 URL 发送数据。还可以使用其它方法向 URL 发送请求。这些方法包括 Loader.load() 和 Sound.load() 等加载方法以及 URLLoader.load() 和 URLStream.load() 等数据加载方法。
上载和下载文件
FileReference.upload() 方法可以将用户选择的文件上载到远程服务器。必须先调用 FileReference.browse() 或 FileReferenceList.browse() 方法,然后再调用 FileReference.upload() 方法。
调用 FileReference.download() 方法可打开一个对话框,用户可以在该对话框中从远程服务器下载文件。
如果服务器要求用户身份验证,则只有在浏览器中运行的 SWF 文件(即使用浏览器插件或 ActiveX 控件的文件)才可以提供对话框来提示用户输入用户名和密码以进行身份验证,并且只适用于下载。Flash Player 不允许上载到需要用户身份验证的服务器。
如果执行调用的 SWF 文件位于只能与本地文件系统内容交互的沙箱中,则不允许执行上载和下载操作。
默认情况下,SWF 文件不会在自身所在服务器之外的服务器上执行上载或下载操作。如果其它服务器提供跨域策略文件向执行调用的 SWF 文件所在的域授予访问权限,则执行调用的 SWF 文件可以在其它服务器上执行上载或下载操作。
从导入到安全域的 SWF 文件加载嵌入内容
当加载 SWF 文件时,可以设置用于加载文件的 Loader 对象的 load() 方法中的 context 参数。此参数是一个 LoaderContext 对象。将此 LoaderContext 对象的 securityDomain 属性设置为 Security.currentDomain 时,Flash Player 将在被加载 SWF 文件所在的服务器上检查是否存在跨域策略文件。如果存在跨域策略文件,并且该文件向执行加载的 SWF 文件所在的域授予访问权限,则可以作为导入媒体加载 SWF 文件。这样,执行加载的文件可以获得对 SWF 文件的库中对象的访问权限。
SWF 文件访问其它安全沙箱中被加载 SWF 文件的类的另一种方法是:使被加载的 SWF 文件调用 Security.allowDomain() 方法,以向执行调用的 SWF 文件所在的域授予访问权限。可以将对 Security.allowDomain() 方法的调用添加到被加载 SWF 文件的主类的构造函数方法中,然后使执行加载的 SWF 文件添加事件侦听器,以便响应由 Loader 对象的 contentLoaderInfo 属性调度的 init 事件。当调度此事件时,被加载的 SWF 文件已经调用构造函数方法中的 Security.allowDomain() 方法,因此被加载 SWF 文件中的类可用于执行加载的 SWF 文件。执行加载的 SWF 文件可以通过调用 Loader.contentLoaderInfo.applicationDomain.getDefinition() 从被加载的 SWF 文件中检索类。
处理旧内容
在 Flash Player 6 中,用于某些 Flash Player 设置的域基于 SWF 文件所在的域的末尾部分。这些设置包括对摄像头和麦克风访问权限、存储配额及永久共享对象存储的设置。
如果 SWF 文件所在的域包含的段数超过两个(如 www.loach.net.cn,则会去除该域的第一段 (www),并使用该域的剩余部分。因此,在 Flash Player 6 中,www.loach.net.cn和 store.example.com 都使用 example.com 作为这些设置的域。同样,www.loach.net.cnuk 和 store.example.co.uk 都使用 example.co.uk 作为这些设置的域。这样会导致出现问题,使得来自不相关域(如 example1.co.uk 和 example2.co.uk)的 SWF 文件可以访问相同的共享对象。
在 Flash Player 7 和更高版本中,默认情况下会根据 SWF 文件所在的精确域来选择播放器设置。例如,来自 www.loach.net.cn的 SWF 文件会对 www.loach.net.cn使用一组播放器设置,而来自 store.example.com 的 SWF 文件会对 store.example.com 使用单独的一组播放器设置。
在使用 ActionScript 3.0 编写的 SWF 文件中,当 Security.exactSettings 设置为 true(默认值)时,Flash Player 将针对精确域使用播放器设置。当设置为 false 时,Flash Player 将使用 Flash Player 6 中所用的域设置。如果要更改 exactSettings 的默认值,则必须在需要 Flash Player 选择播放器设置的任何事件(例如使用摄像头或麦克风,或者检索永久共享对象)发生之前进行更改。
如果发布了版本 6 的 SWF 文件并通过该版本创建了永久共享对象,则要从使用 ActionScript 3.0 编写的 SWF 中检索这些永久共享对象,必须先将 Security.exactSettings 设置为 false,然后再调用 SharedObject.getLocal()。
设置 LocalConnection 权限
使用 LocalConnection 类可以开发可以互相发送指令的 SWF 文件。LocalConnection 对象只能在运行于同一台客户端计算机上的 SWF 文件之间通信,但这些 SWF 文件可以在不同的应用程序中运行。例如,一个 SWF 文件在浏览器中运行,而一个 SWF 文件在放映文件中运行。
对于每一次 LocalConnection 通信,都存在一个发送方 SWF 文件和一个侦听器 SWF 文件。默认情况下,Flash Player 允许在同一域中的 SWF 文件之间进行 LocalConnection 通信。对于不同沙箱中的 SWF 文件,侦听器必须通过使用 LocalConnection.allowDomain() 方法来允许发送方具有访问权限。作为参数传递到 LocalConnection.allowDomain() 方法的字符串可以包含以下任意项:确切域名、IP 地址和通配符 *。
allowDomain() 方法的格式已更改,与其在 ActionScript 1.0 和 2.0 中的格式不同。在这两个早期版本中,allowDomain() 是可以实现的回调方法。在 ActionScript 3.0 中,allowDomain() 则是调用的 LocalConnection 类的内置方法。由于此更改,allowDomain() 的用法与 Security.allowDomain() 基本相同。
SWF 文件可以使用 LocalConnection 类的 domain 属性确定其所在的域。
控制对主机网页中脚本的访问
通过使用以下 ActionScript 3.0 API 可实现外出脚本访问:
flash.system.fscommand() 函数
flash.net.navigateToURL() 函数(当指定 navigateToURL("javascript: alert('Hello from Flash Player.')" 等脚本访问语句时)
flash.net.navigateToURL() 函数(当 window 参数设置为"_top"、"_self"或"_parent"时)
ExternalInterface.call() 方法
对于本地运行的 SWF 文件,仅当 SWF 文件和包含该文件的网页(如果存在)位于受信任的本地安全沙箱中时,才能成功调用这些方法。如果内容位于只能与远程内容交互的沙箱或只能与本地文件系统内容交互的沙箱中,则对这些方法的调用将失败。
HTML 代码中用于加载文件的 AllowScriptAccess 参数控制能否从 SWF 文件内执行外出脚本访问。
在 HTML 代码中为承载 SWF 文件的网页设置此参数。可以在 PARAM 或 EMBED 标签中进行设置。
AllowScriptAccess 参数可以有 "always"、"sameDomain" 和 "never" 这三个可能值中的一个:
当 AllowScriptAccess 为"sameDomain"时,仅当 SWF 文件和网页位于同一域中时才允许执行外出脚本访问。这是 AVM2 内容的默认值。
当 AllowScriptAccess 为"never"时,外出脚本访问将始终失败。
当 AllowScriptAccess 为"always"时,外出脚本访问将始终成功。
如果未在 HTML 页面中为 SWF 文件指定 AllowScriptAccess 参数,则默认为 AVM2 内容的"sameDomain"。
AllowScriptAccess 参数可以防止从一个域中承载的 SWF 文件访问来自另一个域的 HTML 页面中的脚本。对从另一个域承载的所有 SWF 文件使用 AllowScriptAccess="never" 可以确保位于 HTML 页面中的脚本的安全性。
共享对象
Flash Player 提供使用"共享对象"的功能,这些对象是永久位于 SWF 文件外部的 ActionScript 对象,它们或者位于用户的本地文件系统中,或者位于远程 RTMP 服务器上。共享对象与 Flash Player 中的其它媒体相似,也划分到安全沙箱中。但是,共享对象的沙箱模型稍有不同,因为共享对象不是可以跨域边界访问的资源,而是始终从共享对象存储区获得的资源,该存储库特定于调用 SharedObject 类的方法的每个 SWF 文件的域。通常,共享对象存储区比 SWF 文件所在的域更精确:默认情况下,每个 SWF 文件使用特定于其整个源 URL 的共享对象存储区。
SWF 文件可以使用 SharedObject.getLocal() 和 SharedObject.getRemote() 方法的 localPath 参数,以便使用仅与其部分 URL 关联的共享对象存储区。这样,SWF 文件可以允许与其它 URL 的其它 SWF 文件共享。即使将 '/' 作为 localPath 参数传递,仍然会指定特定于其自身所在域的共享对象存储区。
用户可通过使用"Flash Player 设置"对话框或"设置管理器"来限制共享对象访问。默认情况下,可以将共享对象创建为每个域最多可以保存 100 KB 的数据。管理用户和用户还可限制写入文件系统的能力。
可以通过为 SharedObject.getLocal() 方法或 SharedObject.getRemote() 方法的 secure 参数指定 true 来指定共享对象是安全的。请注意有关 secure 参数的以下说明:
如果此参数设置为 true,则 Flash Player 将创建一个新的安全共享对象或获取一个对现有安全共享对象的引用。此安全共享对象只能由通过 HTTPS 传递的 SWF 文件来读取或写入,SWF 文件将调用 SharedObject.getLocal() 并将 secure 参数设置为 true。
如果此参数设置为 false,则 Flash Player 将创建一个新的共享对象或获取一个对现有共享对象的引用,后者可由通过非 HTTPS 连接传递的 SWF 文件来读取或写入。
如果执行调用的 SWF 文件不是来自 HTTPS URL,则为 SharedObject.getLocal() 方法或 SharedObject.getRemote() 方法的 secure 参数指定 true 会导致 SecurityError 异常。
对共享对象存储区的选择基于 SWF 文件的源 URL。即使在导入加载和动态加载这两种情况下也是如此,这时 SWF 文件不是源自简单的 URL。导入加载是指在 LoaderContext.securityDomain 属性设置为 SecurityDomain.currentDomain 时加载 SWF 文件。在这种情况下,被加载的 SWF 文件将具有一个伪 URL,该 URL 以执行加载的 SWF 文件所在的域开头,后跟实际的源 URL。动态加载是指使用 Loader.loadBytes() 方法加载 SWF 文件。在这种情况下,被加载的 SWF 文件将具有一个伪 URL,该 URL 以执行加载的 SWF 文件的完整 URL 开头,后跟一个整数 ID。在导入加载和动态加载这两种情况下,都可以使用 LoaderInfo.url 属性检查 SWF 文件的伪 URL。选择共享对象存储区时,可将该伪 URL 完全视为真实的 URL。可以指定使用部分或全部伪 URL 的共享对象 localPath 参数。
用户和管理员可以选择禁止使用"第三方共享对象"。当在 Web 浏览器中执行的任何 SWF 文件的源 URL 与浏览器地址栏中显示的 URL 属于不同的域时,该 SWF 文件可以使用这样的共享对象。用户和管理员可以出于隐私原因选择禁止使用第三方共享对象,从而可以避免跨域跟踪。为避开这种限制,可能希望确保使用共享对象的任何 SWF 文件都只在 HTML 页面结构内部进行加载,这样可以确保 SWF 文件位于浏览器的地址栏中所示的同一个域中。试图使用第三方 SWF 文件中的共享对象时,如果第三方共享对象禁止使用,则 SharedObject.getLocal() 和 SharedObject.getRemote() 方法将返回 null。
摄像头、麦克风、剪贴板、鼠标和键盘访问
当 SWF 文件试图使用 Camera.get() 或 Microphone.get() 方法访问用户的摄像头或麦克风时,Flash Player 将显示一个"隐私"对话框,用户可以在该对话框中允许或拒绝对其摄像头或麦克风的访问。用户和管理用户还可以通过 mms.cfg 文件、"设置 UI"和"设置管理器"中的控制基于站点或全局禁用摄像头访问。用户加以限制后,Camera.get() 和 Microphone.get() 方法均返回 null 值。可以使用 Capabilities.avHardwareDisable 属性确定是已经通过管理方式禁止了 (true) 对摄像头和麦克风的访问,还是允许 (false) 对摄像头和麦克风的访问。
System.setClipboard() 方法允许 SWF 文件用纯文本字符串替换剪贴板内容。这不会带来任何安全性风险。为避免因剪切或复制到剪贴板的密码和其它敏感数据所带来的风险,并未提供相应的"getClipboard"(读取)方法。
Flash 应用程序仅可以监视在其焦点以内发生的键盘和鼠标事件,无法检测其它应用程序中的键盘或鼠标事件。

二 : IDL入门教程

IDL入门教程

安徽遥感考古工作站藏书

1

IDL IDL入门教程

第一章 起步篇...........................................................................................................9

本章概述................................................................................................................................9

书写本书的背景....................................................................................................................9

运用本书..............................................................................................................................10

IDL所需的版本............................................................................................................................10 IDL运行期间所需颜色的数量.....................................................................................................10

本书的风格习惯............................................................................................................................11 本书中所用的IDL程序和数据文件.............................................................................................13 获取更多的帮助...........................................................................................................................15

使用IDL命令.......................................................................................................................15

IDL命令解析................................................................................................................................15 创建变量.......................................................................................................................................18 使用IDL图形窗口........................................................................................................................23

第二章 简单的图形显示.........................................................................................26

本章概述..............................................................................................................................26

IDL中简单的图形显示.......................................................................................................26

创建线画图..........................................................................................................................26

定制线画图..........................................................................................................................29

改变线条的线型和粗细...............................................................................................................29 用符号代替线条显示数据...........................................................................................................29 用不同的颜色绘制线画图...........................................................................................................32 限定线画图的范围.......................................................................................................................32 改变线画图的风格.......................................................................................................................33

在线画图上绘出多种数据集..............................................................................................34

在多个轴的图上显示数据...........................................................................................................36

创建曲面图..........................................................................................................................36

定制曲面图..........................................................................................................................39

旋转曲面图...................................................................................................................................39 为曲面赋色...................................................................................................................................40 修改曲面图外观...........................................................................................................................41

创建阴影曲面图..................................................................................................................42

改变阴影处理参数.......................................................................................................................42 用其它数据集为阴影处理提供参数...........................................................................................43

创建等值线图......................................................................................................................44

选择等值线数目..................................................................................................................46

扩展:idl / idl教程 / idl程序设计

修改等值线图......................................................................................................................47

改变等值线图的外观...................................................................................................................48 给等值线图赋色...........................................................................................................................49 2

IDL IDL入门教程

创建填充的等值线图..........................................................................................................50

在显示窗口定位图形输出..................................................................................................51

设置图形边缘...............................................................................................................................52 设置图形位置...............................................................................................................................53 设置图形区域...............................................................................................................................54 创建多个图形...............................................................................................................................54

给图形显示添加文本..........................................................................................................58

找出可用字体的名称...................................................................................................................59 用XYOutS命令添加文本.............................................................................................................59 用矢量字体使用XYOut...............................................................................................................60 排列文本.......................................................................................................................................61 删除文本.......................................................................................................................................61 改变文本的方向...........................................................................................................................62

给图形显示添加线和符号..................................................................................................62

图形显示添加色彩..............................................................................................................63

第三章 图像数据处理.............................................................................................72

本章概要..............................................................................................................................72

图像处理..............................................................................................................................72

显示图像.......................................................................................................................................72 调整图像数据...............................................................................................................................74 显示24位图像.............................................................................................................................76 控制图像显示顺序.......................................................................................................................77 改变图像尺寸...............................................................................................................................77 在显示窗口中定位图像...............................................................................................................78 从显示器中读取图像...................................................................................................................81

IDL中基本的图像处理.......................................................................................................82

直方图均衡化...............................................................................................................................82 平滑图像.......................................................................................................................................66 增强图像棱边...............................................................................................................................68 图像的频域滤波...........................................................................................................................68

第四章 图形显示技术.............................................................................................84

本章概要..............................................................................................................................84

IDL的颜色运用...................................................................................................................84

扩展:idl / idl教程 / idl程序设计

使用索引颜色模式和RGB颜色模式...........................................................................................84 在24位显示设备上装载色谱表.................................................................................................89 获得色谱表的拷贝.......................................................................................................................89 修改和创建色谱表.......................................................................................................................90 保存自己的色谱表.......................................................................................................................91

创建自己的轴标注..............................................................................................................92

调整轴刻度间隔...........................................................................................................................92 3

IDL IDL入门教程

格式化轴的标注...........................................................................................................................93

用IDL处理残缺的数据.......................................................................................................96

用IDL建立三维坐标系.......................................................................................................98

建立三维散点图...........................................................................................................................98 从图形原点定位3D坐标轴........................................................................................................100 组合简单图形显示............................................................................................................101 IDL中的动画数据.............................................................................................................103 建立动画工具..............................................................................................................................104 装载动画缓冲区..........................................................................................................................104 运行动画工具..............................................................................................................................104 动画的控制..................................................................................................................................104 存储动画的像素映射图..............................................................................................................105 其它类型图形数据的动画..........................................................................................................105 网格化数据以便图形显示................................................................................................106 德洛内三角形法网格化..............................................................................................................107 数据的球形网格化......................................................................................................................109

第五章 图形显示技巧...........................................................................................111 本章概要............................................................................................................................111 将光标用于图形显示........................................................................................................111 什么时候返回的光标位置?......................................................................................................111 哪一个鼠标键和光标共同作用呢?..........................................................................................112 用光标标注图形输出..................................................................................................................112 在图像上使用Cursor命令...........................................................................................................113 在循环中使用Cursor命令...........................................................................................................114 从显示中删除注释............................................................................................................115 删除注释的异或法......................................................................................................................115 删除注释的设备拷贝法..............................................................................................................117 Z图形缓冲区中的图形显示技巧.....................................................................................121 Z图形缓冲区的实现....................................................................................................................121 一个Z图形缓冲区实例:两个曲面............................................................................................122 用Z图形缓冲区使图像变形........................................................................................................124 Z图形缓冲区中的透明效果........................................................................................................127 将Z图形缓冲区效果与体数据着色相结合................................................................................128

扩展:idl / idl教程 / idl程序设计

第六章 在IDL中读写数据......................................................................................129 本章概要............................................................................................................................129 打开文件进行读写............................................................................................................130 查找和选择数据文件..................................................................................................................130 获取逻辑设备号..........................................................................................................................132 读写格式化数据................................................................................................................133 4

IDL IDL入门教程

写自由格式文件..........................................................................................................................133 读写自由格式文件的实例..........................................................................................................136 用确定的文件格式写入..............................................................................................................140 从字符串中读取格式数据..........................................................................................................141 读写非格式化数据............................................................................................................141 读取非格式化图像数据文件......................................................................................................142 写非格式化图像数据文件..........................................................................................................142 非格式化数据文件的一些问题..................................................................................................144 用关联变量存取非格式化数据文件..........................................................................................144 读写常用文件格式的文件................................................................................................147 创建彩色GIF文件........................................................................................................................147 创建彩色JPEG文件.....................................................................................................................148 查询图像文件信息......................................................................................................................150

第七章 图形硬拷贝输出.........................................................................................151 本章概要............................................................................................................................151 选择图形硬拷贝输出设备................................................................................................151 配置图形硬拷贝输出设备................................................................................................152 常用的Device命令关键字...........................................................................................................153 创建PostScript文件......................................................................................................................154 将图形送到硬拷贝设备中................................................................................................154 打印PostScript文件..........................................................................................................155 在运行MacOS系统的计算机上打印PostScript文件..................................................................156 在Windows计算机上打印PostScript文件...................................................................................156 生成封装的PostScript文件输出......................................................................................156 封装PostScript图形的预览..........................................................................................................157 生成彩色的PostScript输出..............................................................................................157 PostScript中的彩色图像与灰度图像..........................................................................................158 在PostScript设备上创建高质量的输出..........................................................................158 显示设备和PostScript设备之间的相同点..................................................................................159 显示设备与PostScript设备之间的不同点..................................................................................159 在横向输出模式中计算PostScript的偏移量..................................................................171 用PS_Form配置PostScript设备......................................................................................172 配置和使用打印设备........................................................................................................173 用打印设备定位图形..................................................................................................................174

扩展:idl / idl教程 / idl程序设计

第八章......................................................................................................IDL编程基础 188 本章概述............................................................................................................................188 编写IDL批处理文件.........................................................................................................188 5

IDL IDL入门教程

编写IDL主程序.................................................................................................................189 过程和与函数中变量的作用范围..............................................................................................191 创建定位参数..............................................................................................................................191 定义可选的或必须的定位参数..................................................................................................192 定义关键字..................................................................................................................................193 创建输出型参数..........................................................................................................................196 编写IDL函数.....................................................................................................................198 方括号和函数的调用..................................................................................................................200 使用程序控制语句............................................................................................................201 IDL中表达式的真和假...............................................................................................................201 将多个语句处理成单个语句......................................................................................................201 If…Then…Else控制语句............................................................................................................202 条件表达式..................................................................................................................................203 FOR循环控制语句......................................................................................................................203 WHILE循环控制语句.................................................................................................................204 REPEAT...UNTIL 循环控制语句...............................................................................................204 CASE控制语句............................................................................................................................204 GOTO控制语句...........................................................................................................................205 错误处理控制语句......................................................................................................................205 编译和执行IDL程序模块.................................................................................................207 程序编译规则:..........................................................................................................................208 程序编译和自动运行规则..........................................................................................................208 特殊编译命令..............................................................................................................................209

第九章..................................................................................................编写 IDL 程序 177 本章概述............................................................................................................................177 基本的ImageBar程序.......................................................................................................177 给程序ImageBar增加一个“先擦除”功能...............................................................................181 向ImageBar程序增加颜色敏感功能...........................................................................................181 给ImageBar中的命令传递关键字..............................................................................................183 根据窗口大小改变字符大小......................................................................................................185 程序ImageBar的最终代码..........................................................................................................186 在图形用户界面中包装ImageBar..............................................................................................187

第十章.........................................................................................编写简单的组件程序 230 本章概述............................................................................................................................230 组件程序的结构................................................................................................................230 组件程序如何对事件作出反应........................................................................................231 编写组件定义模块............................................................................................................231 定义和创建程序组件..................................................................................................................232 在屏幕上实现组件......................................................................................................................233 使绘图组件成为当前图形窗口..................................................................................................234 6

扩展:idl / idl教程 / idl程序设计

IDL IDL入门教程

在绘图组件窗口上显示图形......................................................................................................234 保存程序运行时所需要的信息..................................................................................................234 创建事件循环和注册程序..........................................................................................................235 运行程序......................................................................................................................................236 创建无阻塞组件程序..................................................................................................................236 编写事件处理模块............................................................................................................237 事件结构中的公共字段..............................................................................................................237 事件处理函数..............................................................................................................................238 将事件处理程序和组件联系起来..............................................................................................239 编写Quit按钮的事件处理程序...................................................................................................240 编写改变图形窗口大小的事件处理程序..................................................................................241 进行小量地修改................................................................................................................242 添加颜色敏感..............................................................................................................................242 采用更高效的内存管理..............................................................................................................243

第十一章.................................................................................................组件编程技巧 210 本章概述............................................................................................................................210 改变颜色表........................................................................................................................210 保护公共块..................................................................................................................................211 一个可选择颜色表的工具..........................................................................................................211 指定Group Leader........................................................................................................................214 给组件程序增加Group Leader....................................................................................................215 在24位显示器上改变颜色表....................................................................................................215 在组件程序中使用指针....................................................................................................217 使用Cleanup过程防止内存泄露.................................................................................................219 使用伪事件进行程序通信................................................................................................220 创建一个具有“记忆功能”的程序..........................................................................................221 保护组件程序的颜色........................................................................................................223 通过组件跟踪事件来保护颜色..................................................................................................224 通过绘图组件事件来保护颜色..................................................................................................225 保存或者发布程序的图形................................................................................................226

第十二章.....................................................................................................对话框程序 247 本章概述............................................................................................................................247 创建模式对话框................................................................................................................247 阻塞的组件程序..........................................................................................................................247 模式组件程序..............................................................................................................................248 编写模式对话框的定义模块......................................................................................................248 编写模式对话框的事件处理模块..............................................................................................252 测试模式对话框程序..................................................................................................................253 创建非模式的对话框........................................................................................................253 7

扩展:idl / idl教程 / idl程序设计

IDL IDL入门教程

编写非模式对话框程序..............................................................................................................254 编写非模式对话框的事件处理模块..........................................................................................256 测试非模态对话程序..................................................................................................................257

附录A..................................................................................................组件的事件结构 258 事件结构的定义................................................................................................................258 公共字段的定义..........................................................................................................................258 基本组件的事件结构........................................................................................................259 base组件.......................................................................................................................................259 按钮组件......................................................................................................................................259 绘图组件......................................................................................................................................259 下拉式列表组件..........................................................................................................................259 标签组件......................................................................................................................................260 列表组件......................................................................................................................................260 滑动条组件..................................................................................................................................260 表单组件......................................................................................................................................260 文本组件......................................................................................................................................262 复合组件的事件结构........................................................................................................262 CW_Animate................................................................................................................................262 CW_Arcball..................................................................................................................................262 CW_BGroup.................................................................................................................................262 CW_Clr_Index.............................................................................................................................263 CW_Color_Sel.............................................................................................................................263 CW_DefROI.................................................................................................................................263 CW_Field.....................................................................................................................................263 CW_Form.....................................................................................................................................263 CW_Flisder..................................................................................................................................263 CW_Orient...................................................................................................................................263 CW_PDMenu...............................................................................................................................264 CW_RGBSlider............................................................................................................................264 CW_Zoom....................................................................................................................................264 组件程序的事件结构........................................................................................................264 Xcolors.........................................................................................................................................264 其他组件的事件结构........................................................................................................264 键盘焦点事件..............................................................................................................................264 组件退出请求事件......................................................................................................................265 组建计时器事件..........................................................................................................................265 组件跟踪事件..............................................................................................................................265

扩展:idl / idl教程 / idl程序设计

附录B.......................................................................................................数据文件描述 266 8

IDL IDL入门教程

第一章 起步篇

本章概述

本章意在解释写这本书的目的,通过阅读本书能学到什么,以及为读者提供一些能使读者更方便地使用本书中IDL编程例子的信息。[www.loach.net.cn]将学会如下几点:

1. 本书是如何组织的。

2. 怎样使用本书。

3. 如何下载和组织随本书附带的文件。

4. 如何使用IDL的变量,关键字和命令。

5. 如何创建和运行IDL的矢量和数组。

6. 如何使用IDL的图形窗口。

书写本书的背景

本书是在多年来教科学家和工程师使用和操作IDL(Interactive Data Language)的基础上创作的,而且教学的绝大部分时间是为IDL的开发者Research Systems公司工作。当笔者在回答一个又一个问题之后,笔者意识到多数问题属于一些同类问题。事实是,多数人想用IDL做许多同样的事情。想做的是分析和演示数据,写出高效率的程序来解决科学问题,并且最主要的是快速做完工作。多数人并不想做的事情是阅读计算机软件教科书。IDL是一套大型软件并且在不但壮大。随之而来的是大量的文档资料,笔者知道没有人愿意去读这些资料。如果让某人独自开始学习IDL的奥秘,IDL将是件可怕的事情,甚至对有经验的用户来说也是一样。本书意在使读者掌握IDL,教给读者在日常运行IDL所必需知识的80%。更为重要的是,本书的例子使IDL更容易理解。无论如何,本书将演示如何使用IDL。

本书的读者是IDL初学者,特别是哪些不得不自学IDL的读者。学好IDL需要很长的阶段。多数人不能利用工作中的时间学习IDL,笔者想写一本能满足这两类人学习IDL的书。总之,本书为不喜欢读教科书并能通过例子学得最好的人全面介绍IDL的精髓。本书在IDL编程技术和技巧方面只做了简要概略,而这些技术只能通过练习获取。最根本的是,这是一本笔者在学习IDL时所期望的书。

9

IDL IDL入门教程

运用本书

笔者曾试图使本书每章能具有独立性,这样能拿起本书就可翻到任何一章去学习最需要的知识。[www.loach.net.cn)但在安排章节时,或多或少是根据笔者在IDL教学时的顺序安排。如果刚开始学IDL,那么按照书中的顺序从头开始学完本书将更合理。书中后面的几章编程教程是建立在前面几章中讲过的概念和技巧的基础上的。

IDL所需的版本

希望读者在学习本书时使用的是最新的IDL版本。本书写作时使用的是IDL5.2版。使用较早版本可以使用本书中编程例子的大部分,但笔者没有试图使本书中的例子程序与较早的IDL软件版本兼容。特别是,较早版本的用户在使用长文件名(如果在Windows环境下)、指针(必须用句柄代替它)以及方括号来引用数组下标时(必须用圆括号代替它)时存在困难。如果需要升级软件,可以在Research Systems公司的WWW网址 http://www.rsinc.com/上查找关于Research Systems公司和当地IDL代理商的信息,包括如何升级软件的信息。

IDL运行期间所需颜色的数量

书中程序例子是按IDL在256种颜色模式下运行编写的,使用通常称为索引颜色的模式(详细细节参考83页的“使用IDL的颜色”章节)。这意味着所显示的颜色是索引号或是与色彩表相连的颜色,这样在色彩表中的颜色变化时,所显示的颜色也一同变化。启动IDL并在IDL命令行键入如下IDL命令,能发现所用的颜色模式。

IDL>Window

IDL>Print, !D.N_Colors

当!D.N_Colors的值大于或等于256时,仍然能够使用书本中的例子,但必需对代码做一点改变。大多数人使用的颜色值都小于256。比较典型的颜色值介于200与245之间。笔者推测在本书中至少要用150种颜色。那就是说,!D.N_Colors的值应在150至256之间。 少于150种颜色会怎样?

如果在IDL运行中少于150种颜色,并且计算机运行在公用桌面环境(CDE),可以将CDE环境下的颜色数设置为不是“高”的那种。设置为“中”或“低”的情况下,程序将运行良好。在视窗环境操作手册的在线帮助中查找如何改变这个设置。

如果不是用的共用桌面环境,颜色数也少于150,并且不是在PC机或Macintosh计算机上运行IDL的话,那么很可能运行了其它应用程序,该应用程序使用了要分配给IDL的颜色值。网页浏览器很可能就是这样的应用程序。退出当前任务,重新登录,并在重新登录后最先启动IDL。键入以上命令,如果仍然得到少于150种的颜色,那么需要联系Research Systems公司的技术人员,以获取更多的帮助。

如果颜色数少于150种,并且是在PC或Macintosh计算机上运行的IDL,那么,检查显卡以确保设置为256色。一般可通过显示器的控制面板完成。详细细节参考计算机文档资料。 10

IDL IDL入门教程

多于256种颜色将会怎么样呢?

如果在IDL运行中多于256种颜色,并且IDL是运行在X Window环境下的计算机上,就可让IDL使用8位的伪彩色显示级别。(www.loach.net.cn)

退出IDL,并重新启动IDL。在做任何操作之前,键入以下命令:

IDL>Device,Pseudo_Color=8.Decomposed=0

为了确认是在使用8位伪彩色显示级别,键入:

IDL>Help, /Device

所显示的信息使读者确信使用的是伪彩色显示级别,并且所使用的颜色数为256或少于256。如果想使用本书中的例子,每次进入IDL时都需要键入DEVICE命令。可以将此命令放在IDL启动文件中。查看IDL文档资料以获取更多的详细资料。

如果在IDL运行中多于256种颜色,并且是在PC或Macintosh计算机上运行IDL,需要检查显示卡的设置参数以确保设置为256色。一般通过显示器或显示面板完成。详细细节参考计算机文档资料。修改参数后必须重新启动IDL。

如果喜欢在16位或24位的颜色模式(在Macintosh和PC计算机上只支持16位,而且如果是24位也将作为16位处理)中工作,那么键入以下命令以确保颜色分解已被关闭: IDL>Device, Get_Visual_Depth=thisDepth

IDL>IF thisDepth GT 8 THEN Device, Decomposed=0

如果在这种模式下对颜色表做些修改,记住这些修改不会在显示窗口中立即更新。必须在显示窗口中刷新图形以查看这些颜色改变是否起作用。详细细节参考83页的“IDL的颜色运用”章节。

扩展:idl / idl教程 / idl程序设计

创建IDL的启动文件

记住,每次启动IDL来使用本书的命令时,都必须执行以上命令。为此,可以将这些命令输入IDL 的启动文件中。当每次IDL启动时,启动文件中的命令都被执行,这就像在IDL命令提示符下键入这些命令。为了解如何在使用的计算机中创建IDL开始文件,可在IDL命令行键入以下命令,以获取在线帮助:

IDL>? Startup

本书的风格习惯

笔者尽量用统一的风格贯穿全书,这样不会被本书文字的功能和目的所迷惑。首先,在IDL命令行或IDL编辑器窗口所键入的命令总是以Courier字体形式来书写:

Surface, data

在IDL命令行键入的命令都显示在IDL提示符“IDL>”的后面:

IDL>Surface, data

其它的IDL命令都是在文本编辑窗口键入的。可以选择自己的文本编辑器或使用IDL提供的文本编辑器,这由读者决定。

11

IDL IDL入门教程

大写

在本书中,用大写这种形式来书写IDL命令。[www.loach.net.cn]这种形式完全是任意的。 IDL对字母的大小写不敏感,但与操作系统打交道的命令(例如:UNIX操作系统对IDL所打开的文件名的大小写敏感)和执行字符串比较命令时除外。大写可以有助于记住命令和关键字名,并且一目了然地知道命令行中哪些单词是函数名。

所有IDL命令和关键字的第一个字母大写。此外,任何有助于记忆的字母也用大写。例如: Surface, data, charsize=2.0, Color=180

XLoadCT

Widget_Control, tlb, Set_UValue=info, /No_Copy

变量名的第一个字母没有用大写字母,但是当变量名中的字母有可能构成单词时使用大写。例如: data=FIndGen(11) buttonValue=thisValue

ptrToData=Ptr_New()

IDL的保留字全部用大写字母,例如:

REPEAT test UNTIL

FOR j=0,10 DO BEGIN

ENDWHILE

在IDL命令行或文本编辑器上,当键入命令时,可以随意使用大写字母。

注释

在IDL命令中,分号右边的任何文本都被视为是注释,IDL解释器将忽略它。简言之,可在IDL的程序中写入注释。通常在分号的前后加上空格,并让注释行缩进三个空格。例如: ; This is the loop part of the program.

FOR j=0,10 DO BEGIN

data=j*2

count=count +j

ENDFOR

偶尔,会在命令行的末端看到一个注释,这是在定义IDL结构变量的字段时,特别这样做的。例如:

info={r:r,$ ; The red color vector

g:g,$ ; The green color vector

b:b,} ; The blue color vector

12

IDL IDL入门教程

续行符 IDL中的续行符是美元的符号“$”。[www.loach.net.cn]这表示IDL命令延续到下一命令行(见上例)。在本书中将看到很多续行符。建议在IDL命令行中不使用续行符,应该在IDL命令行行输入完整的IDL命令。IDL命令行将忽略续行符。例如,可以用如下方法键入上述命令:

IDL>info={r:r, g:g, b:b}

在出现输入错误或在以后需修改命令时,这将使得重新键入这些命令变得更加简单。 有时需要完全按照书中出现的IDL命令输入。笔者将告知什么情况下这样做。当在IDL命令行想键入For循环时就需要这样做。在命令行中一次键入多行命令是非常聪明的做法。必须让IDL解释器认为这些命令为一个命令。这就需要在IDL的命令行上正确使用行续符($)和多行命令符(&)。

本书中所用的IDL程序和数据文件

当使用这本书时,许多IDL程序和数据文件已经准备就绪。IDL程序文件经常有一个.pro扩展名,数据文件有一个.dat扩展名。还有一些.txt扩展名的文件。这些是文本文件。 安装程序和数据文件

建议创建一个名为coyote的子目录,并把所有的程序,文本,数据文件都放在其中。coyote子目录通常是IDL目录下的一个子目录(让IDL内部的系统变量!Dir指向这个目录),当然它并非一定要在这个目录下,可以在任何地方创建。IDL主目录是另外一个存放这些文件的好地方。当需要这些文件时,不直接在coyote子目录下修改,而是将这些文件拷贝到当前工作目录下是一个好注意。这样就保留了原始的没有修改的文件。

如果没有选择创建一个coyote子目录,那么就将本书提供的程序将默认这些文件已经放在当前目录中。这个目录是一个启动IDL时的目录,或者是在PC或Macintosh计算机上IDL的Preferences对话框中Startup所指定的目录。

获取IDL的主目录和当前目录

如果不知道IDL的主目录是什么。启动IDL,键入以下命令:

IDL>CD, Current=homeDirectory

IDL>Print, homeDirectory

当前目录不一定是主目录。在IDL运行期间,可以用同样的命令随时获得当前目录: IDL>CD, Current=currentDirectory

IDL>Print, currentDirectory

注意,如果按上述做法装载数据文件时遇到问题,请确保是在所希望的目录下。不用IDL主目录(例如:5.2 Windows版的IDL软件中,IDL5.2就是IDL的主目录)作为工作目录可能会是一个好注意,因为这样很容易删除重要文件。

13

IDL IDL入门教程

下载本书所用的程序和数据文件

书中文件可以通过互联网以匿名FTP登录下载。[www.loach.net.cn)如果在使用网络浏览器,进入Coyote's Guide to IDL Programming热连接,网址是:

http://www.dfanning.com/

如果用匿名ftp,文件可以通过网络浏览器在如下网址找到:

ftp://ftp.frii.com/pub/dfanning/outging/coyote

用文本或ASCII模式下载所有的程序和文本文件(例如:那些带.pro或.txt扩展名的文件),用BINARY模式下载所有的数据文件(例如:那些带.dat扩展名的文件)。如果愿意,并且电脑能解压缩zip文件,下载coyotefiles.zip文件就可一次性地将所有的程序、文本文件和数据拷贝下来。

确保Coyote目录在IDL的搜索路径内

无论在什么地方创建coyote目录或储存本书的文件,需要确保这个目录在 IDL搜索路径中。在IDL中,路径用!path系统变量给出。以后将学到更多关于该系统变量的作用,但现在只要知道它是一系列的子目录,当IDL遇到不认识的命令时就这些子目录查找相应的命令。打印该系统变量可以看到当前的IDL搜索路径:

扩展:idl / idl教程 / idl程序设计

IDL>Print, !path

如果使用的是PC机,这些子目录用分号隔开;在Macintosh或VMS机器上,它们用逗号隔开;在UNIX机器上,它们用冒号隔开。

想在IDL搜索路径中添加coyote目录,当IDL的当前目录在coyote目录下时键入AddPath命令即可(如果没有创建coyote目录,可以将IDL的当前路径改变为存放本书文件的目录名,然后键入AddPath命令)。使用CD命令来转换到IDL的当前的目录。例如,如果coyote目录是IDL主目录下的一个子目录,并且这个主目录是当前目录,可以键入如下命令来在IDL的搜索路径中添加coyote目录:

IDL>CD, 'coyote'

IDL>AddPath

如果每次运行IDL时都想进入coyote目录(或本书文件所在的目录)并且运行AddPath程序,也许会想到将该命令添加到IDL启动文件中(详细细节参考第四页的“创建IDL的启动文件”)。或者,想将coyote目录永久性地添加到IDL的搜索路径中。(这取决于使用的操作系统和IDL的配置文件。关于设置!Path系统变量,可参考IDL的在线帮助)

拷贝数据文件

如果愿意,可从计算机上其它地方拷贝本书所用到的IDL数据文件,不必通过匿名的ftp来下载。为此,可使用CopyData命令,这个命令是刚下载的文件之一。进入coyote目录(或书中文件所在的目录),如果使用的是IDL5版,只需键入CopyData:

IDL>CopyData

如果运行的是IDL更早的版本,将通过Demo关键字为CopyData程序提供IDL的演示目录(演示目录名在IDL先前版本中各不相同,而且不一定被安装)。如在PC机上演示目录经常命名为“C:\RSI\IDLDEMO4”。所以应该键入如下命令:

14

IDL IDL入门教程

IDL>CopyData,Demo="C:\RSI\IDLDEMO4"

数据文件将从不同的地方被选出并拷贝到当前目录上。[www.loach.net.cn)本书附有这些数据文件的一个列表,说明了它们的类型和大小。见313页的“附录 B: 数据文件描述”。

获取更多的帮助

当在安装这些程序文件或在IDL编程的其它方面需要帮助时,查看Coyote's Guide to IDL Progamming网页。将找到关于本书和IDL常规编程的信息。如果情况更糟的话,也可以在那里看到一张表格,通过该表格可以直接和笔者联系。Fanning软件顾问和Coyote's Guide to IDLProgramming的网址为:

http://www.dfanning.com/

使用IDL命令

本书是一本实践性很强的书。当阅读它时,笔者宁愿读者坐在电脑前,也不愿读者坐在火炉前。笔者希望读者键入命令并查看发生了什么。为此,本书前半部分的多数命令需要在IDL命令行上键入(如果想保存所键入的命令,可以创建一个日志文件来记下它们。参考第11页的“创建日志命令”)。

随着IDL5.0的问世,IDL慢慢地变得越来越像程序语言。例如,对象图形引擎并不真正地用来在IDL命令行上使用的,而是专门设计用在IDL编程中。但是从命令行键入IDL命令中能学到很多东西。特别是,能学会画出某些东西,测试一些东西,并可用数据文件做实验。称之为“循序渐进”。这是学习IDL的最好方法之一。

下面是刚开始所必需知道的。首先,将看到本书中的许多类似下面的命令:

Contour,peak,lon,lat,XStyle=1,YStyle=1, /Follow,$

Levels=vals,C_Label=[1,0,1,0,0,1,1,0]

如果知道所看到的东西是什么将非常有助于学习。

IDL命令解析 在上面的命令中,单词Contour是IDL命令或所希望运行程序的名字。它必须被完整地拼出。一些命令会很长,但不能缩写。命令行中peak,lon,以及lat是变量。它们可以用来将信息传入或传出命令或程序。XStyle,YStyle,Follow,Levels以及C_Lables为关键字。一般来将关键字对命令来说可选的。如同变量,它们用来将信息传入或传出IDL命令或IDL程序。

定位参数

在以上命令中的三个变量peak,lon,及lat称为定位参数。在这个特殊例子中,这些定位参数为输入变量(例如,它们把数据传入命令),但仅仅看到她们并不能辨认出其是不是输入变量。它们也可以简单地用作输出变量(或者,在某种情况下,它们既可以是输入变量也可以是输出变量)。其命令行语法完全一样。只有通过上下文,通过阅读关于这类命令或程序的公开文档才能辨别。

一个定位参数在命令名的右边有其确定的顺序。(注意,以下讨论的关键字参数不会影响定位 15

IDL IDL入门教程

参数的顺序)。[www.loach.net.cn]在这个例子中,peak变量必须在Contour命令右边,在lon变量的左边。lon变量必须在peak变量的右边,lat变量的左边。不能遗漏第二个参数,只给定第一和第三个定位参数。 例如,下面这两条命令的格式是不正确的并会导致错误。第一条命令的定位参数顺序被改变,

第二条命令遗漏了第二个定位参数。

Contour, lon, peak, lat, XStyle=1, YStyle=1, /Follow, $

Levels=vals, C_Labels=[1,0,1,0,0,1,1,0]

Contour, peak, , lat, XStyle=1, YStyle=1, /Follow, $

Levels=vals, C_Labels=[1,0,1,0,0,1,1,0]

一般情况下,命令的定位参数必须给定参数,但并不总是如此。例如,在上面正确的命令中,peak是Contour命令必需的参数,但是lon和lat是可选定位参数。

关键字参数

XStyle,YStyle,Follow,Level和C_Labels都是关键字参数。与定位参数不同,关键字参数能任何顺序出现在命令名右边。它们甚至能出现在定位参数中间而不影响定位参数之间的相对位置。换句话说,关键字参数不能像定位参数那样对待。以下的Contour命令是个有效构造。 Contour, peak, Level=vals, lon, XStyle=1, YStyle=1, $

/Follow, lat, C_Lavels=[1,0,1,0,0,1,1,0]

一般情况下,关键字参数是可选参数。像定位参数一样,它们也能成为命令的输入变量或输出变量。将通过本书或阅读命令的文档得知这一点。

注意在上列命令中关键字的使用方法。关键字能设置为一个特定值(例如,XStyle=1),一个变量(例如,Levels=vals),一个数组(例如,C_Labels=[1,0,1,0,0,1,1,0]),甚至可以用一个斜杠字符来设定(例如,/Follow)。

注意最后的一条语法。有些关键字有二进制特性。换句话说,它们要么on/off, yes/no, true/false, 1/0,等等。能经常发现这些关键字通过/Keyword这种语法来设置或打开。语法/Keyword等同于语法Keyword=1。

扩展:idl / idl教程 / idl程序设计

事实上,以上Contour命令能被写成这样:

Contour,peak,Levels=vals,lon,/XStyle,/YStyle,$

/Follow,lat,C_Lavels=[1,0,1,0,0,1,1,0]

这个命令和上面的命令是一回事。命令不能写成这样的原因是,它可能错误地暗示了X轴和Y轴关键字有二进制特性,但它们不是,它们能被设置为除0和1以外的其它值。

IDL过程和函数

这个特殊的命令Contour是一个IDL过程。IDL命令要么是过程,像这个命令一样,要么是函数。如下的IDL命令BytScl就是一个函数:

scaled=BytScl(image, Top=199, Min=0, Max=maxValue)

注意Contour过程和BytScl函数的不同。首先,在函数命令中,定位参数和关键字放在一对圆括号中的。在过程命令中,参数和关键字仅排列在一个命令行上。但是,最重要的区别是函数命令显示地返回一个值,等号左边的一个变量用于返回该值。这是IDL中函数命令和过程命令根本的区别。

函数命令总是显示地返回一个值,这个数值必须赋予给一个变量。函数返回值可能是任一种IDL变量,包括数值,数组和结构。在这个例子中,返回值scaled是一个与image定位参数具有 16

IDL IDL入门教程

相同维数的字节型数组。(www.loach.net.cn]

有时将看到一个函数和过程写在一起,例如,考虑一下这两个命令:

scaled=BytScl(image,Top=199,Min=0,Max=maxValue)

TV,scaled

第一个命令是一个函数命令,另一个是过程命令,此过程使用函数的返回值作为其定位参数,两个命令写成如下这样在IDL中很常见:

TV,BytScl(image,Top=199,Min=0,Max=maxValue)

在这种情况下,BytScl命令首先被执行并得到一个返回值,此返回值作为TV命令的定位参数。

花一些时间熟悉各种IDL命令,就能立即识别哪个是过程,哪个是函数,但尽量记住这一点:当正在从一个命令中寻找某个值时,要想到这个命令可能是一个函数。在本书后面中将学会怎样写IDL过程和函数。

用IDL命令帮助

IDL有全面的在线帮助系统,能为读者提供有关IDL命令和参数的非常有帮助的信息。通过在IDL命令行中输入一个问号,或在IDL开发环境下拉菜单中选择Help菜单项目获得在线帮助。IDL文档集中的大部份信息都可通过在线帮助获得。为了获得IDL在线系统帮助,仅仅需要在IDL的命令行中输入一个问号,如下:

IDL>?

创建命令日志

也许希望将在命令行里面输入的命令保存为日志或记录。如果是这样,可创建一个日志文件。日志文件是一个IDL批处理文件(参考205页的“创建IDL批处理文件”)。在IDL中用Journal命令打开一个日志文件,并指定想打开的文件名。该文件将是一个用于写信息的新文件。从IDL命令行不能添加日志文件。例如,为了写一个命名为book_commands.pro的日志文件, 键入: IDL>Journal, 'book_commands'

随后所有在IDL命令行上键入的命令都将写入这个日志文件。

IDL>a=[3,5,7,3,6,9]

IDL>Help, a

IDL>Plot, a

当想关闭日志文件时,再次在IDL命令行键入Journal命令,如下:

IDL>Journal

日志文件是能编辑的一个简单的ASCII文本文件。如果愿意,可用任何一个文本编辑,包括由IDL的PC版本附带的编辑器。当想再次执行日志文件中的命令时,在IDL命令行键入@作为开头字母。例如,要执行在上面book_commands.pro文件中的命令,如下:

IDL>@book_commands

确定创建的每个日志文件有唯一的名称。不能添加日志到这些日志文件,所以,如果第二次建立的日志文件名和第一次相同,许多操作系统将会毫无警告地覆盖第一个日志文件。

如果每次建立日志文件时都想要一个唯一文件名,可用下列的IDL程序完成:

PRO Journal_Unique

Journal, String('journal_',Bin_Data(SysTime()),'.pro',$

17

IDL IDL入门教程

Format='(A,I4,5I2.2,A)') END

然后,用Journal_Unique代替Journal,就可以建立每次都具有唯一文件名的日志文件。(www.loach.net.cn)

创建变量

在这本书中将创建许多变量。如果以前对变量有所了解将会大有益处。变量名必须以字母开头。它们可以包括其它字母,数字,下划线,美元符号。一个变量名最长可达255个字符。本书的习惯是让变量名的首写字母小写。下面是一些有效的变量名: ptrToData image2 this_image a$handle

变量名有两个重要属性:数据类型和组织结构。数据类型指出属于数据类型中的哪一种。在IDL中有14种基本数据类型。在图表1中将看到每一种数据类型,每个类型创建的变量的字节大小,变量创建方式,数据类型之间强制转换的IDL函数名称。除了数据类型外,一个变量有一个组织结构。有效的组织结构有标量(例如单个数值)、矢量(真正的一维数组)、数组(最高可达8维)和IDL结构(能包含各种数据类型的变量和组织结构,结构中独立的组成部分称为字段)。

数据类型 字节型 16位有符号整型 32位有符号长整型 64位有符号整型 16位无符号整型 32位无符号长整型 64位无符号整型

浮点型 双精度浮点型

复数 双精度复数 字符串 指针 对象

字节数 1 2 4 8 2 4 8 4 8 8 16 0-32767

4 4

创建变量 Var=0B Var=0 Var=0L Var=0LL Var=0U Var=0UL Var=0ULL Var=0.0 Var=0.0D

Var=Complex(0.0,0.0)Var=Dcomplex(0.0D,0.

0D) Var=’’或Var=”” Var=Ptr_New() Var=Obj_New()

数据类型函数 thisVar=Byte(variable) thisVar=Fix(variable) thisVar=Long(variable) thisVar=Long64(variable) thisVar=UInt(variable) thisVar=ULong(variable) thisVar=Ulong64(variable) thisVar=Float(variable) thisVar=Double(variable) thisVar=Complex(variable) thisVar=DComplex(variable) thisVar=String(variable)

None None

表1:IDL中的14种基本数据类型。表中显示了每种数据类型的字节数,创建变量的方法,

用语数据类型之间强制转换的IDL函数

正如所看到的,IDL是一个善于处理矢量或数组数据的软件,所以有大量的IDL命令用于创建不同数据类型的矢量和数组。特别是,有许多创建各类数据类型的数组的函数,该数组的每个元素的初始值为零,而且还有许多创建各类数据类型的数组的函数,该数组的每个元素的初始值为其在数组中的索引位置。在表2中将看到这些函数列表。例如,创建100*100初始值为零的字节型数组,输入:

扩展:idl / idl教程 / idl程序设计

IDL>array=BytArr(100,100)

18

IDL IDL入门教程

创建一个有100个元素的浮点型矢量,初始数值为从0到99,输入:

IDL>vector=FIndGen(100)

将在本书中看到使用这些IDL函数的各种方式。[www.loach.net.cn)

动态改变变量的属性

IDL最强大的功能之一是大多数命令都能在任何数据类型或组织结构上起作用。这是因为IDL在运行时能改变变量的数据类型和组织结构(像世界上其它强大的事物一样,这种动态改变变量的属性的能力也有潜在的巨大危险!必须小心,确信知道正在使用哪种数据)。例如,在IDL中,本质上讲变量是毫无意义的(像在Fortran或者C程序中),因为这种变量的数据类型很容易改变。例如:

num=3 ; Initialize NUM as a scalar integer.

num=num*5.2 ; Variable NUM changes to a float!

变量num被初始化为一个整数,由于数学运算的结果和重新赋值,它被动态地改变成浮点数值。这是因为IDL在数学计算当中为了保证最高的精度,将低精度的数据类型提升为高精度的数据类型。当num被再赋值(在等号的左边),它被提升为一个浮点数去保持等号右边计算的精度。思考下面这个例子:

result=4*x

在这种情形下,是不可能知道变量会产生哪种数据类型和组织结构,因为对x变量一无所知。事实上,结果主要取决于变量x的数据类型和数据结构。如果x是10个元素的浮点矢量,结果将会是10个元素的浮点矢量。如果它是100*200的长整数数组,结果也将是100*200的长整数数组。注意如果x有一个字节的数据类型,那结果将是一个整数数据类型(在这种情形下,组织结构并没有多大影响)。这是由于被整数乘的结果。

记住等号右边的表达式总是在将数据类型和组织结构赋予等号左边的变量前计算的。IDL将变量提升到能保持表达式的计算精度的数据类型。

注意整型变量

关于整型变量想简单地提一提,以免使用它们时遇到麻烦。有两种常见的错误。第一种涉及到整数数学。思考一下这个示例:

result=12/5

也许期望的是一个值为2.4浮点变量,但是它不是的,而是一个值为2的整数。知道为什么吗?是的,方程式右边的两个数字为整数。这是一个整数除法的例子。如此之下,找出错误并不难,但有时问题会更微小。

例如,假如想知道IDL图形窗口的比率。窗口的大小(像素点或整数值)被储存在两个系统变量中。也许会写出如下的IDL代码:

aspect=!D.X_Size / !D.Y_Size

它可以花掉很长的时间找出为什么比率为零。正确的方法是写出代码以强制将一个整数值变成为一个浮点,如下:

aspect=Float(!D.X_Size) / !D.Y_Size

现在的比例变量就是一个所期望的浮点数了。 数据类型

字节型 初始化函数 BytArr 产生索引值的函数 BIndGen

19

IDL IDL入门教程

16位有符号整型 32位有符号长整型 64位有符号整型 16位无符号整型 32位无符号长整型 64位无符号整型

浮点型 双精度浮点型

复数 双精度复数 字符串 指针 对象

IntArr LonArr Lon64Arr UIntArr ULonArr ULon64Arr FltArr DblArr ComplexArr DComplexArr StrArr PtrArr ObjArr

IndGen LIndGen L64IndGen UIndGen ULIndGen UL64IndGen FIndGen DIndGen CIndGen DCIndGen SIndGen None None

表2: IDL函数可以创建矢量和多维数组,并将其每个元素初始为0或为它们本身的索引号码。[www.loach.net.cn]

使用整型变量另外一个常遇到的问题是没有意识到IDL的整型在其它编程语言中被称为短整型。或者说,IDL的一个整型只有两个字节长。整型在其它程序语言中有四个字节(四个字节的整数在IDL的整数中是一个长整数)。

两个字节的整数只能大到32767。大于这个值通常由于“溢出”而被IDL当作为负数。用短整数会在两种情况下遇到麻烦。首先,在循环中没有考虑到短整数的因素,例如,假如想读一个数据文件,但不知道有多少行。可以写入如下代码: COUNT=0

WHILE NOT EOF(lun) DO BEGIN READF,lun,temp data(count)=temp COUNT=count+1 ENDWHILE

如果数据文件多于32,768行,这个代码就失败了。原因是count变量初始为一个整数,这个代码更好的写法如下: count=0L

WHILE NOT EOF(lun) DO BEGIN READF,lun,temp data(count)=temp count=count +1L ENDWHILE

现在随便读取多少行都可以。

另外一个常犯这种错误的地方是For循环中。按如下写法来写出For循环命令是一个好注意: FOR j=OL,num-1 DO...

第二种在使用短整型可能会遇到麻烦的方式是,当在读取用其它编程语言生成的数据时(或者反过来)。如果读取用C或Fortran程序生成的整型数据,应该确保在IDL中用长整型来读这些数据。同样,应该用长整型数据来写那些将被C或Fortran程序视为整型来读入的文件。

20

IDL IDL入门教程

使用矢量和数组

IDL是一种在善于处理矢量和数组中的程序语言(IDL的第一个版本的原形是APL,是一种在数组运算上非常优秀的程序语言)。(www.loach.net.cn]要成为一个高效的IDL程序员,必须知道怎样对数组进行数学运算。在本书中,将看到许多这方面的例子,但在开始前,需要注意两个重点。

创建矢量

在IDL命令行,可以用一对方括号创建一个矢量(矢量只是一维的数组)或一个数组,如下: IDL>vector=[1,2,3]

这是一个整型矢量,因为数据值为整型值。

可以用Help命令,获取关于数据类型和变量组织结构的信息,如下:

IDL>Help,vector

VECTOR INT =Array[3]

如果想增加从第四个元素到矢量中,在IDL中可以很轻松地完成。只需键入: IDL>vector=[vector,4]

IDL>Print,vector

1 2 3 4

数组下标的应用

假设打算在数组的第二和第三个元素之间添加另外一个元素,数组下标可以帮助完成。数组下标的上界和下界被冒号隔开。例如,指定上述矢量的前三个元素,如下所示: IDL>Print, vector(0:2)

1 2 3

注意,矢量下标的起始值是0,而不是1,并且矢量下标使用圆括弧以示区别。这使得有时很难将一个函数调用和一个数组下标引用区别开来。为了解决这个问题,IDL允许使用方括弧来引用数组下标。也就是说,当运行IDL5时,可以键入:

扩展:idl / idl教程 / idl程序设计

IDL>Print, vector[0:2]

本书已被修改成使用方括弧引用下标,以避免同函数调用相混淆。倘若正在使用IDL的IDL4.x版本,要运行此命令就得用圆括弧代替方括弧。

要用数组下标将另一个元素插入第二和第三个元素之间,可键入:

IDL>vector=[vector[0:1],5,vector[2:3]]

IDL>Print, vector

1 2 5 3 4

矢量也可用上表中谈到的数组创建函数建立。例如,建立一个值在0到50之间的6个元素浮点矢量,可键入:

IDL>vector=FIndGen(6)*10

IDL>Print,vector

0.000000 10.0000 20.0000 30.0000 40.0000 50.0000

21

IDL IDL入门教程

数组的建立

数组也可以在IDL命令行中建立。(www.loach.net.cn]例如,可以建立一个两行三列的数组,如下所示: IDL>array=[[1,2,3],[4,5,6]]

IDL>Print, array

输出IDL输出窗口中将会如下所示:

1 2 3

4 5 6

注意,这等同于先建立一个矢量,然后Reform命令将此变形为一个三行二列的数组,如下所示:

IDL>vector=IndGen(6)+1

IDL>array=Reform(vector,3,2)

IDL>Print, array

这表明矢量和排列是以行的顺序存储在IDL中的。这一点在编写IDL程序的过程中非常重要,因为将经常用到IDL这种数据存储方式的优势。

数组中元素的存取

假设想读出刚建立的数组中位于第一列第二行的元素(元素的值为4),可以键入: IDL>Print,array[0,1]

注意,下标的顺序先是列标,后是行标。这正好与已习惯的线性代数中的矩阵或行列式相反(同时,行标与列标比想象的小1,因为排列下标值的起始值是0而不是1)。

列-行下标源于极大的图像数据,IDL最初就是为处理这种数据而开发的。数据中的一行对应图像的一个独立扫描行。这种数据存储形式使数据操作迅速而精确。决定一套软件是使用列-行下标,还是使用行-列下标,完全可以自由决定。没有任何特殊原因选此弃彼。

可以使用一维下标来存取该数组中的同一个元素。要知道数组元素是以行顺序存储的,所以获得数组中的第四个元素。可以键入以下语句来存取:

IDL>Print, array[3]

用一维下标存取多维数组,这在许多IDL程序中是一个强大的工具。

也可以用一维向量来做数组的下标。例如,倘若要存取数组中的第一,二,四和第六个元素,可键入:

IDL>indices=[0,1,3,5]

IDL>Print,array[indices]

矢量和子数组的提取

IDL可很容易地从数组内提取出矢量和子数组。例如:看看这个拥有随机数据的数组: IDL>data = RandomU(seed, 10, 20)

想提取出第6-10列和第12-15行的数据,可键入:

IDL>subarray = data[5:9, 11:14]

如果要将第8列的数据画出来,可以使用下标*代表所有的行,如下所示:

IDL>Plot, data[7,*]

22

IDL IDL入门教程

要建立一个第14行的矢量,键入:

IDL>vector = data[*,13]

要建立一个数据为数组中最后5行的数组,键入:

IDL>subarray = data[*,15:19]

IDL>Help, subarray

现在可以看到子数组是一个10列*5行的数组。(www.loach.net.cn]

同样可以用*代表剩下的所有数据。例如,用数组的最后5列建立一个子数组,也可键入: IDL>subarray = data[5:*,*]

IDL>Help, subarray

通过对本书中范例的练习,会对数组以及数组的处理方法了解得更多。

使用IDL图形窗口

通过对本书中范例的练习,会对IDL图形窗口的了解得更多,但在开始之前,最好先了解下面一些东西。

图形窗口的建立

首先,一个图形窗口可直接用Window命令建立,或是在没有窗口打开的情况下,间接通过运行图形显示命令来打开。例如,可以建立并启动一个窗口,只须键入:

IDL>Window

注意,此窗口的标题栏中有一个0,这是此窗口的索引号。当图形窗口建立后,每个图形窗口都有唯一的一个图形窗口索引号。Window命令如果没有任何定位参数总是创建出索引号为0的图形窗口。称之为“窗口0”。在IDL的一次运行中,最少可同时打开128个图形窗口。可以为0到31号图形窗口指定一个索引号。对于32到127号图形窗口,可以用Window命令带上Free关键字(以下将谈到)来创建,IDL将为它们赋上索引号。例如:想创建一个索引号为10的图形窗口,键入:

IDL>Window, 10

倘若某个索引号图形窗口的窗口已经存在,再用Window命令创建相同索引号图形窗口,Window命令将首先删除旧窗口,然后建立一个带有此索引号的新窗口。

如果愿意(当在IDL程序中建立窗口时,这通常是一个不错的注意),可以用一个未用的索引号或者已经打开但是空白窗口的索引号来创建新的图形窗口。关键字Free即为此目的而设,如下所示:

IDL>Window, /Free

用关键字Free建立的图形窗口,将会具有一个大于31的索引号。关键字Free是建立索引号大于31的常规图形窗口的唯一途径。

确定当前图形窗口

现在在显示器上至少已经打开了三个图形窗口,但只有一个是当前图形窗口。当前图形窗口用于接受图形命令的输出结果。当前图形窗口的索引号总是存储在!D.Window系统变量中。如果没有创建和打开图形窗口,系统变量!D.Window的值为-1。

23

IDL IDL入门教程

可以创建一个图形窗口,并存储其图形窗口索引号,以便以后删除该窗口或使其成为活动窗口。(www.loach.net.cn)可键入:

IDL>Window, /Free

IDL> thisWindowIndex = !D.Window

使图形窗口成为当前窗口

为使一个窗口成为当前图形窗口(可在其内显示图形),可使用Wset命令和图形窗口索引号来设定。例如,希望当前图形窗口为10号窗口时,键入:

IDL>Wset, 10

随后所有的图形命令的结果都将显示到10号窗口内。

注意,当一个图形窗口创建完成后,该窗口即成为当前窗口(但是,用Widget_Draw产生的窗口不是这样)。为了在某个窗口内绘制图形,该窗口必须是当前图形窗口。

扩展:idl / idl教程 / idl程序设计

删除图形窗口

可用Wdelete命令和图形窗口的索引号删除图形窗口。被删除的图形窗口不必是当前图形窗口。例如,删除窗口10,键入:

IDL>Wdelete, 10

删除当前显示器上的所有图形窗口有一个技巧:

IDL>WHILE !D.Window NE –1 DO Wdelete, !D.Window

图形窗口的位置和尺寸

在创建图形窗口时,图形窗口的位置和尺寸是根据内部运算规则确定的。在Window命令中,用关键字可以设置图形窗口的位置和尺寸。例如,用关键字XSize 和YSize创建一个宽200像素,高300像素的窗口,键入:

IDL>Window, 1, XSize=200, YSize=300

可用相对于显示器左上角的像素坐标或设备坐标来定位窗口。例如,用关键字XPos 和YPos将窗口的左上角定位于显示器(75,150)处,键入:

IDL>Window, 2, XPos=75, YPos=150

将图形窗口设置到显示器最前面

创建一个图形窗口时,该窗口拥有输入焦点,同时也成为当前图形窗口。也就是说,对于窗口管理器来讲,该图形窗口现在为激活窗口(仅仅因为一个图形窗口拥有窗口输入焦点,并不意味它是当前图形窗口)。为了输入一个命令,不得不将窗口焦点移回到命令输入窗口。在某些平台上,特别是在PC机上,这会导致图形窗口隐藏到其它窗口后面。

有时,在显示器上一个图形窗口隐藏其它窗口的后面,想将该窗口拖到前面以便能看见。在不改变窗口输入焦点的情况下,要将一个图形窗口显示在前面,可用Wshow命令和图形窗口索引 24

IDL IDL入门教程

号来完成。(www.loach.net.cn)

IDL>Wshow, 1

注意,光标和窗口焦点仍在键入IDL命令的命令输入窗口或其它窗口内。

用Wshow命令将窗口显示在前面但并不将窗口改变为当前窗口。如果既想将该窗口拖到前面,又想将其变为当前窗口,那么可同时键入Wshow 和Wset命令:

IDL>Wshow, 2

IDL>Wset, 2

注意,如果输入不带参数的Wshow命令,在显示器上将当前窗口拖到前面。当不清楚哪个是当前图形窗口和只想将当前窗口拖到前面而不从IDL命令窗口移动开焦点时,这个命令是非常有用的。

IDL>Wshow

注意,在PC机和Macintosh机器上,可以用ALT-TAB键或者OPTION-TAB键来循环选择已经在显示器上打开的窗口,让其可见并拥有窗口焦点。

在图形窗口上设置标题

有时希望在图形窗口上设置标题,而不仅仅是图形窗口索引号。可以使用Title关键字将标题设置到窗口上,键入:

IDL>Window, Title=’Example IDL Graphics Commands’

清除图形窗口内容

可以使用Erase命令清除当前图形窗口内容:

IDL>Erase

如果想用一种特定的颜色索引号,去清除当前图形显示(如果在24位颜色模式下可以用一个24位颜色值),可以用color关键字。例如,可以用以下命令实现用炭灰色清除当前图形显示: IDL>TVLCT, 70, 70, 100

IDL>Erase, Color=100

想清除非当前图形窗口(系统变量!D.Window指向的窗口)的内容,必须使该窗口成为当前图形窗口,接着使用Erase命令。

25

IDL IDL入门教程

第二章 简单的图形显示

本章概述

科学分析最基本的能力就是以简单的线画图、等值线图和曲面图来显示所研究的数据。(www.loach.net.cn]在这一章中,将知道用这些方式来显示数据是多么容易。也将学会用系统变量和关键字来定位和标注简单的图形显示。

将学会如下几点:

1. 如何用Plot命令将数据显示为线画图。

2. 如何用Surface和Shade_Surf命令将数据显示为曲面图。

3. 如何用Contour命令将数据显示为等值线图。

4. 如何在显示窗口上定位显示图形。

5. 如何用公共关键字来标注和自定义图形显示。

IDL中简单的图形显示

IDL中一个简单的图形显示可认为是栅格图形的一个实例。也就是说,可用Plot, Contour或者Surface命令通过一种算法来点亮显示窗口内相应的像素点而形成栅格图形。这种栅格图形没有永久性。换言之,一旦IDL显示图形和点亮相应的像素点后,IDL就不知道自己做了些什么。这意味着,在用户重置图形窗口大小时,IDL无法进行相应的响应。总之,在这种模式下图形显示不能被刷新,除非再次输入图形命令。

但是,栅格图形命令在IDL中被广泛应用,因为它们简单快捷。而且,将看到,如果仔细地用栅格图形命令编写IDL程序时,可以克服许多与栅格图形命令相关的限制。本章将介绍一些关于如何用栅格图形命令写出可调节尺寸的IDL图形窗口或进行直接硬拷贝输出的必备概念。本章的图形命令都是Research Systems公司所说的直接图形。

另外一种被Research Systems公司称为对象图形的图形方式在IDL5.0中被引入。对象图形使用时相对难一点,但它在IDL编程方面更强大更灵活。对象图形不是为了在命令行使用而开发的,而是用在IDL的程序中,特别是用于带有界面的程序中(带有图形用户界面的程序)。本书对对象图形不做介绍。

创建线画图

生成线画图最简单的方法是绘出一个矢量。可以用LoadData命令打开时序数据集。LoadData命令是本书所带的一个IDL程序(详细细节参考第5页的“本书中所用的IDL程序和数据文件”)。它用来装载本书的编程例子中所需的数据。键入如下语句以查看所能使用的数据集:

IDL>curve=LoadData()

如果输入LoadData命令时忘掉了括号,需要在它正常工作前重新编译LoadData程序。原因是,IDL在命令行会认为它是一个变量并进行相应地处理。重新编译后,“loaddata”这个函数名在IDL的函数名列表中。键入:

26

IDL IDL入门教程

IDL>.Compile LoadData

时序数据是在LoadData数据列表上的第一个数据集。(www.loach.net.cn]点击它,数据就被装入到curve变量中。另外一种选择第一个数据集的方法是,按如下方法使用LoadData:

IDL>curve=LoadData(1)

要查看curve变量如何被定义,键入:

IDL>Help, curve

CURVE FLOAT =Array[101]

将发现curve是一个具有101个元素的浮点矢量(或一维数组)。

要绘出该矢量,可键入:

扩展:idl / idl教程 / idl程序设计

IDL>Plot, curve

IDL试图用少量的信息尽可能地绘出漂亮的线画图。在这种情况下,x轴或水平轴被标识为从0到100,这与矢量中的元素个数相对应。而y轴或垂直轴则是用数据坐标来标识(它是取决于数据的坐标轴)。

但大多数情况下,线画图用于显示一组数据(独立数据)相对另外一组数据(非独立数据)的关系。例如,上面的曲线可能代表在某段时间内采集数据的信号。可能需要绘制某个时刻的信号值。在这种情况下,需要一条与该曲线矢量具有相同元素个数的矢量(这样可以获得一一对应的相关性),并将该矢量转换为实验中所用的时间单位。例如,可以创建一个时间矢量,并绘出它与上述曲线矢量的关系图:

IDL>time=FIndGen(101)*(6.0/100)

IDL>Plot, time, curve

FIndGen命令创建一个元素值为0到100的共101

个元素的矢量。乘法因子按比例缩

图1:独立数据(时间)与非独立数据(曲线)关系图。

小每个元素的大小,最后的结果是一个元素值为0到6之间的共101个元素的矢量。图形输出结果应与图1相似。

注意,在此图中的坐标轴上没有相应的标题。在图上设置标题是很容易的,只要用XTitle和YTitle关键字既可实现。例如,为此曲线图加标题,可键入:

27

IDL IDL入门教程

IDL>Plot, time, curve, XTitle='Time Axis', $

YTitle='Signal Strength'

甚至可以用Title关键字对整个图形设置标题,键入:

IDL>Plot,time,curve,XTitle='Time Axis', $

YTitle='Signal Strengh',Title='Experiment 35M'

输出结果应与图2一样。[www.loach.net.cn]

图2:简单的带坐标轴标题和图形标题的线画图

注意图形显示应该为在黑色背景下的白线图,而上图显示为在白色背景下的黑线。这些插图包含在用IDL生成的PostScript文件中。一般情况下Postscript文件把图形颜色和背景颜色反过来。(参考189页的“问题:PostScript设备对背景颜色和图形颜色的不同处理”。)

注意,图形标题稍微大于坐标轴的标题。事实上,是1.25倍的关系。可以用CharSize关键字改变所有图形注记的大小。例如,可以将坐标轴标题的字符放大50%:

IDL>Plot, time, curve, XTitle='Time Axis', $

YTitle='Signal Strength', Title='Experiment 35M', $

CharSize=1.5

如果希望所有的图形显示的字符比正常情况下大,可以通过绘图系统变量上设置CharSize的大小,如下:

IDL>!P.CharSize=1.5

现在,所有后续的图形显示都将用较大的字符,除非用CharSize关键字在图形输出命令中特别地控制。

甚至可以用[XYZ]CharSize关键字单独改变每个轴的标识字符的大小。例如,如果想使Y轴的注记比X轴的大两倍,则可键入:

IDL>Plot, time, curve, XTitle='Time Axis', XCharSize=1.0,

$

28

IDL IDL入门教程

YTitle='Signal Strength', YCharSize=2.0

记住,[XYZ]Charsize关键字使用当前字符的大小作为基础计算出各自的大小。(www.loach.net.cn)当前字符的大小一般储存在!P.CharSize系统变量中。这意味着,如果设置XCharSize关键字为2,当!P.CharSize系统变量也被设置为2时,字符将比平常大四倍。

定制线画图

上面是简单的线画图,除了数据本身外,没有多少其它信息。然而,有许多方法可用来定制和标注线画图。Plot函数可以被50多种不同的关键字修饰。下面的事情也许想做一做:

1. 改变线型或粗细。

2. 使用符号,符号之间可以有线条和没有线条存在。

3. 创建自己的绘图符号。

4. 给线图加入颜色提示重要特性。

5. 改变刻度标记的长度或刻度标记之间的间隔。

6. 使用对数来标度图形坐标轴。

7. 改变绘图范围来绘出感兴趣的数据段。

8. 删除坐标轴或改变绘图方式。

改变线条的线型和粗细

例如,想用不同的线型画出数据。如画一条线型为长虚线的线条,可以这样实现: IDL>Plot, time, curve, LineStyle=5

对于线画图来说,可通过LineStyle关键字选用表3中列出的索引号确定不同的线型。例如,想使用虚线画出曲线,可以把LineStyle关键字的值设置为2:

IDL>Plot, time, curve, LineStyle=2 索引号

1

2

3

4

5 线型 实线 点线 虚线 划点线 划点点线 长虚线

表3:可以通过赋予LineStyle这个关键字不同索引号来改变线型

线画图中线的粗细同样能够被改变。例如,如果想使用比正常值粗3倍的虚线来显示图形,可键入:

IDL>Plot, time, LineStyle=2, Thick=3

用符号代替线条显示数据

假如想用符号代替线条显示数据,就象LineStyle关键字一样,也存在类似的索引号供选择,以确定不同的线画图符号。表4给出了能通过PSym(绘图符号)关键字来选择的索引号。例如, 29

IDL IDL入门教程

可以通过设置PSym为2,用星号来绘图,如下: IDL>plot, time, curve, Psym=2 输出的图形应与图3中的图形相似。[www.loach.net.cn]

图3:用符号而不是线条来显示线画图。

索引号 0 1 2 3 4 5 6 7 8 9 10 -PSym

绘图符号

无符号,通过线条连接点

加号 星号 点 菱形 三角形 方形 X

用户自定义符号(用UserSym过程来定义)

未用 直方图

负值表示用线条连接相应的符号

表4:这些符号索引号可以通过PSym关键字来引用以便在绘图中使用不同的符号。注意绘图符号

为负值时表示用线条来连接相应的符号。

30

IDL IDL入门教程

用线条和符号来显示数据

扩展:idl / idl教程 / idl程序设计

赋予PSym关键字一个负值就可以用线条将图形符号连接起来。[www.loach.net.cn]例如,可用实线与三角形符号绘出数据,键入:

IDL>Plot, time, curve, PSym=-5

为创建一个更大的符号,可用SymSize关键字。下面的语句画出的符号为正常的两倍。符号值为4时符号的大小为正常值的4倍,依此类推。

IDL>Plot, time, curve, PSym=-5, SymSize=2.0

创建自己的图形符号

如果富有创造力,甚至可以创建自己的图形符号。UserSym命令就用于此目的。在创建了一个特殊的图形符号之后,可通过设置PSym关键字为8来选择它。以下是一个创建五角星符号的例子。 x,y矢量定义五角星的顶点,它们的值为偏离原点(0,0)的位置。可以用UserSym命令通过设置关键字Fill创建一个填充的图形符号:

IDL>x=[0.0, 0.5, -0.8, 0.8, -0.5, 0.0]

IDL>Y=[1.0, -0.8, 0.3, 0.3, -0.8, 1.0]

IDL>TvLCT, 255, 255, 0, 150

IDL>UserSym, x, y, Color=150, /Fill

IDL>Plot, time, curve, PSym=-8, SymSize=2.0

输出结果应与图4相似。

图4:用UserSym程序创建的符号来绘制的图。

31

IDL IDL入门教程

用不同的颜色绘制线画图

可以用不同的颜色绘制线画图(颜色将在第83页的“IDL的颜色运用”中详细讨论。[www.loach.net.cn]现在,只须按如下键入TvLCT命令即可,以后将学到这个命令意味着什么。实质上,装载了三个颜色矢量,每个矢量的三个分量分别代表颜色的三个组成部分红,绿,蓝。这三种颜色矢量为碳灰,黄,绿色。)例如将颜色索引号1、2和3分别设置为碳灰,黄,绿色,键入:

IDL>TvLCT, [70,255,0], [70,255,255], [70,0,0], 1

在碳灰背景下绘黄色图,键入:

IDL>Plot, time, curve, Color=2, Background=1

如果只是想使线条成为不同的颜色,首先必须将NoData关键字打开来绘图,然后用OPlot命令(下面要讨论的)覆盖该图。例如,在碳灰色背景上绘制黄色外框,数据用绿色显示,键入: IDL>Plot, time, curve, Color=2, Background=1, /NoData

IDL>OPlot, time, curve, Color=3

限定线画图的范围

并非所有的数据都必须在一个线画图中绘出,可以用关键字限定绘图的数据量。例如,可仅绘出位于X轴上2至4之间的数据,键入:

IDL>Plot, time, curve, XRange=[2, 4]

或者仅绘出Y值在10至20之间,X值在2至4之间的部分数据图形,键入: IDL>Plot, time, curve, YRange=[10, 20], XRange=[2, 4]

也可以通过给定关键字数据范围来反转数据的方向。例如,可将Y轴的0点设置为图形的顶端,如下:

IDL>Plot, time, curve, YRange=[30, 0]

输出结果应与图5相似。

如果所选择的轴的范围不适合IDL关于坐标轴美观标记的规定,IDL将忽略所要求的范围。试一试如下的命令:

IDL>Plot, time, curve, XRange=[2.45, 5.64]

X轴上显示的范围将是从2至6,这并不是对IDL所要求的精度。为确保轴上显示的范围正如所要求的那样,可将XStyle关键字设置为1,如下:

IDL>Plot, time, curve, XRange=[2.45, 5.64], XStyle=1

下一节将学到更多关于[XYZ]Style关键字的知识。

32

IDL IDL入门教程

图5: 将Y轴0点设置为图形顶端的图形

改变线画图的风格

可以方便地改变线画图的许多特性,包括它们的外观形式。[www.loach.net.cn)例如,可能不在意线画图的方框。如果是这样,可以用[XYZ]Style这些关键字改变线画图的特性。表5给出了可通过这些关键字来改变线画图风格的值。例如,为除去方框线,只留下X轴或Y轴,可键入:

IDL>Plot, time, curve, XStyle=8, YStyle=8 值

1

2

4

8

16 对坐标轴的影响 精确的坐标轴范围 扩展坐标轴范围 不显示整个坐标轴 不显示外框(只画坐标轴) 屏蔽Y轴起始值为0的设置(只有Y轴有此属性)

表5:[XYZ]Style关键字参数表,用于设置坐标轴的属性。注意:这些值可以累加从而设置坐标

轴的多个而非单个属性。

可以完全隐藏一个轴。例如,仅用Y轴显示图形,可键入:

IDL>Plot, time, curve, XStyle=4, YStyle=8

33

IDL IDL入门教程

输出结果应与图6相似:

图6: 关闭X轴和方框只剩Y轴的线画图

可以用Y轴和Y方向的网格线来显示同一幅图:

IDL>Plot, time, curve, XStyle=4, YTickLen=1, YGridStyle=1

[XYZ]Style关键字可以一次设置坐标轴的多个特性。[www.loach.net.cn)可以通过累加适当的值来实现。例如,可以从表5中看出,强制使用精确的坐标轴范围的参数值为1,而用来删除方框线的参数值为8。为实现上述两项功能,即让X轴显示精确的范围又隐藏方框线,可将两个参数值相加: IDL>plot, time, curve, xstyle=8+1, xrange=[2, 5]

在线画图上创建网格线,通常可用TickLen关键字来完成。如下:

IDL>Plot, time, curve, TickLen=1

将[XYZ]TickLen关键字设置为一个负值可以创建向外的刻度标记。例如,为创建向外的刻度标记,可键入:

IDL>Plot, time, curve, TickLen=-0.03

在某个轴上创建向外的刻度标记,可将[XYZ]TickLen关键字设置为一个负值。例如,只在X轴上创建向外的刻度标记,键入:

IDL>Plot, time, curve, XTickLen=-0.03

可以用[XYZ]Ticks和[XYZ]Minor关键字,在一个轴上选择主要的和次要的刻度标记的个数。例如,在X轴上创建两个主要的刻度间隔,每个主要的刻度间隔内设置10个次要的刻度标记,键入:

IDL>Plot, time, curve, XTicks=2, XMinor=10, XStyle=1

在线画图上绘出多种数据集

没有必要限制自己仅仅用一组数据绘制线画图。IDL

扩展:idl / idl教程 / idl程序设计

程序允许在同一套坐标轴内显示任意多 34

IDL IDL入门教程

套数据。(www.loach.net.cn)OPlot命令就用于此目的。键入以下命令,输出结果应与图7相似:

IDL>Plot, curve

IDL>OPlot, curve/2.0, LineStyle=1

IDL>OPlot, curve/5.0, LineStyle=2

初始的Plot命令为以后的绘图建立数据比例(!X.S和!Y.S是比例参数)。或者说, !X.S和!Y.S系统变量告诉IDL如何在数据范围内取点以及如何将该点显示在设备坐标空间上。要确保初始图形有足够的轴长,以便包容以后绘制的所有图形,否则数据将被裁剪掉。可在第一个Plot命令中用XRange和YRange关键字来创建一个足够大的数据范围。为区别不同的数据集,可用不同的线型,不同的颜色,不同的图形符号等。Oplot命令接受很多被Plot命令接受的关键字。 IDL>TvLCT, [255, 255, 0], [0, 255, 255], [0, 0, 0], 1

IDL>Plot, curve, /NoData

IDL>OPlot, curve, Color=1

IDL>OPlot, curve/2.0, Color=2

IDL>OPlot, curve/5.0, Color=3

图7:在同一个线画图上可以绘制无限多套数据集

35

IDL IDL入门教程

图8:具有两个Y轴的线画图。[www.loach.net.cn]第二轴是用Axis命令来定位的。一定要用Save关键字来将数据

比例保存起来

在多个轴的图上显示数据

有时,希望在同一个线画图上显示两个或多个数据集,并用不同的y轴表示不同的数据集。使用Axis命令很容易建立所需数量的坐标轴。使用Axis命令的关键是使用save关键字来存储正确的绘图比例参数(即存储在!X.S和!Y.S系统变量中的比例参数),以便后续图形的调用。

下面的例子在已绘出一幅图后,用带Save关键字的Axis命令建立第二个Y轴。OPlot命令中的曲线将调用通过Axis命令保存的比例因子,以确定其在图形中的位置。正确的命令是如下: IDL>Plot, curve, YStyle=8, YTitle='Solid Line', $

Position=[0.15, 0.15, 0.85, 0.95]

IDL>Axis, YAxis=1, YRange=[0, Max(curve*5+1)], /Save, $

YTitle='Dashed Line'

IDL>OPlot, curve*5, LineStyle=2

Position关键字用来确定第一个图形在页面内的位置。为了解更多关于Position关键字的知识,可参阅第48页的“在图形显示窗口中确定图形输出位置”章节。输出图形应与图8相似。 创建曲面图

在IDL程序中,任何二维的数据组都可以用Surface命令生成一个曲面图(经过自动消隐)。首先,必须打开数据文件,用LoadData命令打开 Elevation Data

数据集。键入:

36

IDL IDL入门教程

IDL>peak=LoadData(2)

通过键入Help命令,可以发现这是一个41*41的浮点数组。(www.loach.net.cn)键入:

IDL>Help, peak

这个数组可以用一个命令使之视面图:

IDL>Surface, peak, CharSize=1.5

输出结果应与图9相似。

图9:利用高程数据生成简单的曲面图。

注意,如果仅用单个数组作为变量调用Surface命令,它将把该数组作为其元素个数(此例在X和Y方向都为41)的函数来绘图。(可以使用CharSize关键字来改变字符的大小,以便更容易看清楚)。但是,正如前面使用Plot命令一样,可以规定X和Y轴的数值,以便显示的图形具有实际意义。例如,X和Y轴的数值可以是经纬度坐标。这里,使纬度范围为从24度到48度,经度范围为-122度到-72度:

IDL>lat=FIndGen(41)*(24./40)+24

IDL>LON=FIndGen(41)*50.0/41-122

IDL>Surface, peak, lon, lat, XTitle='Longitude', $

YTitle='Latitude', ZTitle='Elevation', CharSize=1.5

输出结果应与图10相似。

37

IDL IDL入门教程

图10:一个具有实际意义坐标值的曲面图。(www.loach.net.cn]

以上命令中的lon和lat参数是单调递增并且是规则的。它们描述了曲面网格线的位置。但网格没有必要是规则的。试想一下,如果使经度数据点不规则分布会出现什么情况。例如,可以键入以下命令模拟随机分布的经度点:

IDL>seed=-1L

IDL>newlon=RandomU(seed, 41)*41

IDL>newlon=newlon[Sort(newlon)]*(24./40)+24

IDL>Surface, peak, newlon, lat, XTitle='Longitude', $

YTitle='Latitude', ZTitle='Elevation', CharSize=1.5

现在发现经度X值是没有规则分布的。尽管看起来数据被重新取样了,然而却不是。能很容易地在经度和纬度数据点指定的位置处画出曲面图的网格线。输出结果应与图11相似。

38

IDL IDL入门教程

图11:同样的曲面图,但其X矢量具有不规则的空间分布

定制曲面图

有70多个不同的关键字可以用来修饰曲面图。(www.loach.net.cn]实际上,许多关键字在Plot命令中已经学过。例如在上面的代码中,就使用了相同的标题关键字对曲面图的轴进行标记。然而要注意,当用Title关键字时,所添加的标题被旋转了,从而保证标题总是位于曲面图的XY平面内。键入: IDL>surface, peak, lon, lat, XTitle='Longitude', $

YTitle='Latitude', Title='Mt.Elbert', Charsize=1.5

这并非总是所希望的。如果想使图形标题位于与显示面平行的平面内,就必须用Surface命令绘制曲面图,而用XYOutS命令显示标题(第55页有关于XYOutS命令的详细信息)。比如,键入:

IDL>Surface, peak, lon, lat, Xtitle=’Longitude’, $

Ytitle=’Latitude’, Charsize=1.5

扩展:idl / idl教程 / idl程序设计

IDL> XYOutS, 0.5, 0.90, /Normal, Size=2.0, Align=0.5, $

‘Mt.Elbert’

旋转曲面图

在观察曲面图时可能希望能旋转一个角度。曲面图可以用Ax关键字使其绕X轴或用Az关键字使其绕X轴旋转。当从轴上的正值向原点观察时,曲面图以逆时针方向,按某个角度值旋转。当Az和Ax关键字被忽略时其缺省值是30度。例如,使曲面图绕Z轴旋转60度,绕X轴旋转35

度,则可键入:

39

IDL IDL入门教程

IDL> Surface, peak, lon, lat, Az=60, Ax=35, Charsize=1.5

输出结果应与图12相似。(www.loach.net.cn)

图12:用Az和Ax关键字使曲面图旋转

为曲面赋色

有时,可能想为曲面图上赋上颜色以强调某种特性。给曲面图着色是很简单的,只需使用在线画图中用过的赋色关键字即可。(颜色将在第83页的“IDL的颜色运用”中详细讨论。现在,只须按如下键入TvLCT命令即可,以后将学到这个命令意味着什么。实质上,装载了三个颜色矢量,每个矢量的三个分量分别代表颜色的三个组成部分红,绿,蓝。这三种颜色矢量为碳灰,黄,绿色)。例如,在碳灰色背景上创建一个黄色的曲面图,可键入:

IDL> TvLCT, [70, 255, 0], [70, 255, 255], [70, 0, 0], 1

IDL> Surface, peak, Color=2, Background=1

如果想使曲面图的底面的颜色不同于顶面,比如说绿色,可以使用Bottom关键字来实现: IDL>Surface, peak, Color=2, Background=1, Bottom=3

如果想将轴以不同的颜色显示,比如绿色,而不是曲面,必须键入两个命令。第一个命令使用NoData关键字,只将轴绘出。第二个命令是在关闭轴线后绘出曲面本身。查看第31页的表5,了解[XYZ]Style关键字的参数值及其含意:

IDL>Surface, peak, Color=3, /NoData

IDL>Surface, peak, /NoErase, Color=2, Bottom=1,XStyle=4, YStyle=4, ZStyle=4

用不同的颜色画出曲面的格网线也是有可能的,而不同的颜色代表不同的数据。比如,可用第二个数据集覆盖第一个,第二个数据集含有对第一个数据集的格网进行着色后的信息。

为了说明如何工作,可打开一个名为Snow Pack的数据集,并用以下这些命令将此数据作为 40

IDL IDL入门教程

一个曲面显示。(www.loach.net.cn]注意,Snow Pack数据集的大小与peak数据集一样,都是41*41浮点数组: IDL>snow=LoadData(3)

IDL>Help, snow

IDL>Surface, snow

现在,通过用snow的变量值对peak变量的格网着色,将snow变量中的数据覆盖到peak变量中数据的上面。首先,用LoadCT命令装载色彩表内的一些颜色。实际的阴影处理是通过shades关键字完成的,如下:

IDL>LoadCT, 5

IDL>Surface, peak, Shades=BytScl(snow, Top=!D.Table_Size-1)

注意,必须用BytScl命令将snow数据集调整为所使用IDL时的色彩数。如果调整失败,只能看到一套坐标轴,而看不到曲面显示。这是因为,数据必须调整到曲面阴影处理时所需的0到255的范围。

修改曲面图外观

有很多关键字可以用来修改曲面图的外观或形式。例如,可以显示一个带边缘的曲面图。使

用Skirt关键字来指定边缘该画到何处。试试下面命令:

IDL>Surface, peak, Skirt=0

IDL>Surface, peak, Skirt=500, Az=60

图13:带边缘的曲面图

上面第一个命令的输出结果应与图13相似。

如果仅绘出水平线,获得一种层叠线形图,比如,键入:

IDL>Surface, peak, /Horizontal

如果愿意,可以通过关键字来只显示曲面的底面或顶面,而不是两者都显示(缺省是两者都 41

IDL IDL入门教程

显示)。(www.loach.net.cn)键入:

IDL>Surface, peak, /Upper_Only

IDL>Surface, peak, /Lower_Only

有时可能只想显示曲面本身,而不需要轴线。 可键入:

IDL>Surface, peak, XStyle=4, YStyle=4, ZStyle=4

创建阴影曲面图

创建阴影曲面图同样很简单,可使用Gouraud光源阴影算法创建阴影曲面图,键入: IDL>Shade_Surf, peak

Shade_Surf命令接受大多数被Surface命令接受的关键字。例如,如果想旋转阴影曲面,可以键入:

IDL>Shade_Surf, peak, lon, lat, Az=45, Ax=30

输出图形应与图14相似。

图14: 用Gouraud光源阴影算法生成的阴影曲面图

改变阴影处理参数

用Set_Shading命令可以改变Shade_Surf命令所使用的阴影处理参数。例如,要将光源的光线的方向从平行Z轴的默认值[0,0,1]改变为平行X轴的方向[1,0,0],可键入:

IDL>Set_Shading, Light=[1, 0, 0]

IDL>Shade_Surf, peak

也可以从色彩表中挑选哪种颜色索引号用作阴影处理。例如,当想把红色色普表(色普表3) 42

IDL IDL入门教程

装载到颜色索引号100到199之中,并将之用于阴影处理,可键入:

IDL>LoadCT, 3, NColors=100, Bottom=100

IDL>Set_Shading, Values=[100, 199]

IDL>Shade_Surf, peak

注意将光源位置和颜色参数恢复原值,否则练习的继续,可能会造成混乱。(www.loach.net.cn]

IDL>LoadCT,5

IDL>Set_Shading, Light=[0,0,1], Value=[0,!D,Table_Size-1]

用其它数据集为阴影处理提供参数

首先,就象Surface命令一样,其它数据集也可以为阴影处理时的各数据点提供颜色值。正如前述,

扩展:idl / idl教程 / idl程序设计

可以用Shades关键字为曲面上各点指定颜色索引号。每个像素点的阴影处理都是根据该点周围数据值通过插值求出。例如,下面是一个用snow变量生成的阴影曲面图:

IDL>Shade_Surf, snow

现在用这个数据集来对最初的高程数据集进行阴影处理,键入:

IDL>Shade_Surf,peak,lon,lat, Shades=BytScl(snow, Top=!D.Table_Size)

输出结果应如图15所示:

图15:用snow数据集对peak数据进行阴影处理

如果要求根据数据点的高程值来对曲面进行阴影处理,可简单地对数据集本身进行字节比例缩放即可,键入:

IDL>Shade_Surf, peak, Shades=BytScl(peak, Top=!D.Table_Size)

将另一数据集覆盖在曲面图上是一种给数据升维的方法。例如,可将一组数据集覆盖在一个三维曲面图上,就可以直观的获得四维的信息。如果同时让两组数据集合随时间活动起来,就可以 43

IDL IDL入门教程

直观的获得五维信息。[www.loach.net.cn](关于数据动画参阅104页的“IDL的动画数据”)

有时只是想将原始曲面覆盖在经过阴影处理的曲面图上,通过结合使用Shade_Surf命令和Surace命令可轻松的做到。例如:

IDL>Shade_Surf, peak

IDL>Surface, peak, /NoErase

创建等值线图

在IDL中,任意二维数组都可以用一个Contour命令显示为等值线图。如果已经在这次IDL运行中定义了peak变量,可直接使用该变量。如果没有定义,可以使用LoadData命令来载入Elevation Data中的数据集。键入:

IDL>peak=LoadData(2)

IDL>Help, peak

这个数据集通过一个简单的命令即可显示为等值线图(图16):

IDL>Contour, peak, CharSize=1.5

图16:一个基本的等值线图,注意X、Y轴的标记代表该数组中的元素个数

注意,如果仅用单个二维数组作为参数调用Contour命令,它将把该数组作为其元素个数(此例在X和Y方向都为41)的函数来绘图。如前述所用Surface命令一样,可以指定X轴和Y轴的数值,以便使其具有实际意义。例如,可以象前述一样使用经度和纬度矢量。如下所示: IDL>lat=FIndGen(41)*(24./40)+24

IDL>lon=FindGen(41)*50.0/40-122

IDL>Contour, peak, lon, lat, XTitle='Longitude', $

YTitle='Latitude'

44

IDL IDL入门教程

注意轴被自动缩放了。(www.loach.net.cn)从很多地方可以看到这一点。首先,等值线没有延伸到等值线图的边缘,其次,可以发现轴上的标记与lon矢量和lat矢量的最小值和最大值不同。

IDL>Print, Min(lon), Max(lon)

IDL>Print, Min(lat), Max(lat)

为了防止轴的自动缩放,可以设置XStyle和YStyle关键字,如下:

IDL>Contour, peak, lon, lat, XTitle='Longitude', $

YTitle='Latitude', XStyle=1, YStyle=1

该命令得到图形应如图17所示。

图17:具有实际数量意义的等值线图

在早期的IDL版本中,Contour 命令使用所说的单元画法来计算并绘画数据的等值线。在这种方法中,等值线图是从图底画到图顶。这种方法是有效的,但是它不允许选项,比如标注等高线。而单元跟踪法被用来完整地画出围绕等值线图的每一条等值线。这需要较长的时间,但可以允许对等值线作更多的控制。例如,等值线可以断开用于等值线的标注。这种单元跟踪法可以用Follow关键字来调用:

IDL>Contour, peak, lon, lat, XStyle=1, YStyle=1,

/Follow

45

IDL IDL入门教程

从IDL5版本开始,Contour命令一般都使用单元跟踪法来绘制等值线图。(www.loach.net.cn)所以,Follow关键字已经过时了。但该关键字仍然被使用,是因为它对自动标注其它每条等值线的有益作用。选择等值线数目

缺省情况下,IDL选择6条匀称的等值线间隔(即有5条等值线)绘制等值线图。但是,可以用几种不同的方法改变缺省值。例如,

可以用Nlevels关键字告诉IDL需要绘制多少条等值线。IDL将计算出等间隔的等值线间隔数。例如,要绘制具有12条等间隔的等值线图,可键入: IDL>Contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Follow, $

Nlevels=12

输出结果应与图18相似。可选择高达29条的等值线。

图18:这是将等值线级别设置为12的等值线图。注意,每隔一条等值线都会标注一条,这

是使用Fellow关键字的一种副作用。

不幸的是,尽管IDL文档称IDL将采用给定的等间隔的等值线间隔数,但事实上不是这样。如果注意观察所创建的等值线图,会注意到IDL只计算出少于12条的间隔线。显然,NLevels关键字的值在IDL中只能作为等值线选择算法中的一个“建议”。

因此,大多数IDL程序员都是自己计算等值线数目。例如,能精确规定哪条等值线应该画,并用Levels关键字传给Contour命令,而不是用NLevels关键字,如下所示:

IDL>vals=[200, 300, 600, 750, 800, 900, 1200, 1500]

IDL>Contour, peak, lon, lat, XStyle=1, YStyle=1, /Follow, $

46

IDL IDL入门教程

Levels=vals

要选择12个间距相等的等值线间隔,可编写如下代码:

IDL>nlevels=12

IDL>step=(Max(peak)-Min(peak))/nlevels

IDL>vals=Indgen(nlevels)*step+Min(peak)

IDL>Contour, peak, lon, lat, XStyle=1, YStyle=1, /Follow, $

Levels=vals

如果喜欢,可以C_Labels关键字精确的指定哪一根等值线应该标注。(www.loach.net.cn)这个关键字是一个其元素与等值线数目相等的矢量(如果元素个数与等值线数目不匹配,那么元素就不能象其它关键字那样循环使用)。如果某元素的值是1(或更精确,只要是正数),相应的等值线就给予标注;如果某元素的值是0,相应的等值线就不予标注。如果某条等值线没有元素值与之对应时,那么这条等值线就不标注。例如,要标注第一,第三,第六和第七条等值线,可键入:

扩展:idl / idl教程 / idl程序设计

IDL>Contour, peak, lon, lat, XStyle=1, YStyle=1, /Follow, $

Levels=vals, C_Labels=[1, 0, 1, 0, 0, 1, 1, 0]

要标注所有的等值线,可以使用Replicate命令来将1复制所需要的次数。键入:

IDL>Contour, peak, lon, lat, XStyle=1, YStyle=1, /Follow, $

Levels=vals, C_Labels=Replicate(1, nlevels)

修改等值线图

等值线图可用与Plot命令和Surface命令中相同的关键字进行修改。但是仍然还有许多仅适用于Contour命令的关键字。它们中的大部分经常用于修改等值线本身。例如,用坐标轴标题注释等值线图,可键入:

IDL> contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Follow, Xtitle=’Longitude’, Ytitle=’Latitude’, $ Charsize=1.5, Title=’Study Area 13F89’, Nlevels=10

也可以用C_Annotation关键字在等值线上标注释。可以用字符串标记每一条等值线: IDL> contour, peak, Xstyle=1, Ystyle=1, /Follow, $

Xtitle=’Longitude’, Ytitle=’Latitude’, Charsize=1.5, Title=’Study Area 13F89’,$

C_annotation=[‘Low’,’Middle’,’High’], Levels=[200, 500, 800] 输出结果应与图19中的图例相似。

47

IDL IDL入门教程

图19: 等值线可以用自己提供的文本标识

改变等值线图的外观

修改等值线图的外观有许多方法。[www.loach.net.cn)这里有一些例子。能改变的特性之一是等值线的线型(见表3列出的可选用的线型值)。例如,为了使等值线成为虚线的线型,键入:

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

/Follow, C_LineStyle=2

假如需要隔二条等值线有一条虚线,可以用C_LineStyle 关键字指定一个线型索引矢量,假如等值线数比索引号多,那么这些索引号将被循环使用或被重复使用。键入 :

IDL> contour, peak, lon, lat, XStyle=1, Ystyle=1, /Follow, $

Nlevels=9, C_LineStyle=[0, 0, 2] 输出结果应与图20相似.

图20:可以修改等值线图的许多方面。这是隔二条等值线有一条虚线的等值线图。

可以改变等值线的宽度。例如,要使等值线具有双倍的宽度,可键入:

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

Nlevels=12, C_Thick=2, /Follow

通过指定一个线宽矢量,可以间隔修改等值线的宽度:

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

Nlevels=12, C_Thick=[1, 2], /Follow

通过修改等值线图,可以很容易地看到等值线图的下坡方向。键入:

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

/ Follow, Nlevels=12, /Downhill

48

IDL IDL入门教程

输出结果应与图21相似。[www.loach.net.cn)

图21:用Downhill来显示等值线图的下坡方向。

给等值线图赋色

给等值线图着色的方法有许多种。(颜色将在第83页的“IDL的颜色运用”中详细讨论。现在,只须按如下键入TvLCT命令即可,以后将学到这个命令意味着什么。实质上,装载了三个颜色矢量,每个矢量的三个分量分别代表颜色的三个组成部分红,绿,蓝。这三种颜色矢量为碳灰,黄,绿色。)假如需要一张以碳灰颜色为背景的黄色等值线图,可键入:

IDL> TvLCT, [70, 255], [70, 255], [70, 0], 1

IDL> Contour, peak, lon, lat, XStyle=1, YStyle=1, $

NLevels=10, Color=2, Background=1, /Follow

也可以用C_Color关键字给等值线本身单独添上颜色。如果需要将上图的等值线变为绿色 ,键入:

IDL> TvLCT, 0, 255, 0, 3

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Follow, $

Nlevels=10, Color=2, Background=1, C_Colors=3

关键字C_Color也可以被表达为色彩表索引号的矢量,并以循环的方式绘制等值线。也可以使用Tek_Color命令为等值线创建或者装入颜色,如下:

IDL> Tek_Color

IDL> TvLCT, [70, 255], [70, 255], [70, 0], 1

IDL>Contour, peak, lon, lat, XStyle=1, YStyle=1, $

49

IDL IDL入门教程

Nlevels=10, Color=2, Background=1, $

C_Colors=IndGen(10)+2, /Follow

可以很容易地用C_Colors关键字使每间隔二条等值线有一条蓝色的等值线,其余的等值线为绿色。[www.loach.net.cn)键入: IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $ NLevels=12, Color=2, Background=1, $ C_Colors=[3, 3, 4], /Follow 创建填充的等值线图

有时,不只是想观察等值线,也想看看填充后的等值线图。创建一张填充的等值线图,只需使用关键字Fill即可。首先,装入12种颜色于色彩表中作为填充颜色。色彩索引号由关键字C_Colors给出。键入:

IDL> LoadCT, 0

IDL> LoadCT, 4, Ncolors=12, Bottom=1

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Fill, $

NLevels=12, /Follow, C_Colors=Indgen(12)+1

用这种方法填充颜色还是存在许多问题,尽管从显示看不是很明显。事实上,在等值线图有一个以背景颜色填充的”洞”。假如将背景色与图形颜色交换一下,就可以看得更清楚一些(事实上,PostScript中就是这样做的。这也是致使许多IDL程序人员焦头烂额的原因)。

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Fill, $

NLevels=12, /Follow, C_Colors=Indgen(12)+1, $

Background=!P.Color, Color=!P.Background

“洞”产生的原因是由于IDL用第一种颜色填充了第一和第二条等值线间的空间。用第一种填充颜色去填充第零条(或背景)和第一条等值线之间的空间似乎更合理。但是,要使IDL这样做,不得不给定自己的等值线数目,并用关键字Levels传送给Contour命令。通常可用下述代码实现: IDL> step = (Max(peak) – Min(peak)) / 12.0

扩展:idl / idl教程 / idl程序设计

IDL> clevels = IndGen(12)*step + Min (peak)

现在,就得到了正确的等值线填充颜色。

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Fill, $

Levels=clevels, /Follow, C_Colors=Indgen(12)+1, $

Background=!P.Color, Color=!P.Background

通常情况下,在填充等值线图时,经常定义等值线数目不失为一种好的方法。此外,要将填充的等值线图和色彩棒一起显示时,那么,创建自己的等值线数目是确保等值线数目与色彩棒的级数一致的唯一方法。

有时候,需要填充有丢失数据的等值线图或者是等值线超出了图形边界的等值线图,这种情况称为“开放的等值线”。 IDL处理这些开放的等值线时有时比较困难。填充这类等值线图的最好办法是使用关键字Cell_Fill,而不是使用 Fill 关键字。这将导致Contour命令使用单元填充算法。这种算法没有 Fill关键字使用的算法效率高,但在这种情况下可以获得更好的填充效果。假如需要将填充的等值线图放在地图投影上,使用Cell_Fill关键字也是个好主意。 IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

50

IDL IDL入门教程

Levels=clevels, C_Colors=Indgen(12)+1, /Cell_Fill

单元填充算法有时会破坏等值线图的坐标轴。(www.loach.net.cn)可以通过不带数据的等值线图的重新绘制来修复。键入:

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

Levels=clevels, /NoData, /NoErase, /Follow

有时,

可能想在已填充好颜色的等值线图上看到等值线。在IDL中用Overplot关键字可以轻而易举地实现。键入:

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, $

Levels=clevels, /Fill, C_Colors=IndGen(12)+1

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, /Follow, $

Levels=clevels, /overplot

输出结果应与图22相似

图22:在已填充的等值线图上覆盖等值线

注意,不要混淆Overplot和NoErase关键字。它们是相似的,但确切地说是不一样的。在等值线图上,Overplot关键字仅仅绘出等值线,而不绘出等值线图的坐标轴。 NoErase关键字则是绘出完整的等值线图,而不删除在屏幕上已显示的内容。

在显示窗口定位图形输出

IDL有几种在显示窗口中定位线画图、曲面图、等值线图和其它图形的方法(比如地图投影)。 51

IDL IDL入门教程

为了理解 IDL 怎样定位图形,了解一些定义很重要。(www.loach.net.cn)图形位置是指在显示窗口上被图的坐标轴框 起来的部分中的位置。图形位置不包括坐标轴标识,坐标轴标题或者其它注释(见下面的图 23) 。 图形区域是显示窗口的一部分,包括图形位置,也包括环绕图形位置的空间,用来注明坐标轴标 识,坐标轴标题和图标题等。图形边界定义为在显示窗口内不包括图形位置的区域。 图形位置可以用!P.Position 系统变量设置,或者用 Position 关键字对 Plot, Surface, Contour 或其它 IDL 图形命令进行设置。整个图形区域可用!P.Region 系统变量设置,或者通 过!X, !Y 和!Z 系统变量的 Region 字段来设置单个坐标轴的区域。图形边界可以用[XYZ]Margin 关键字来对 Plot, Surface, Contour 或 IDL 的其它图形命令进行设置,或者通过!X, !Y 和!Z 系统变量的 Margin 字段来设置。 在缺省值情况下,IDL 是在将图形输出到显示窗口的时候设置图形边界的。但是,正如所看 到的,这并不是最好的选择。有时,使用图形定位来定位图形显示会更好,尤其是,当在一个显 10 示窗口中显示多个命令的输出结果时。 5 00 20 40 80 图形位置10 5 00 20 40 80 图形区域10 5 00 20 40 80 图形边缘图 23: 图形位置是被坐标轴包围起来的区域。图形区域与图形位置类似,但它还包括图形标题 和其它注释的区域。图形边缘正好与图形位置相反。图形边缘由字符的单位确定,而图形位置和 图形区域是由归一化的坐标单位确定。设置图形边缘图形边缘可以用图形命令中的[XYZ]Margin 关键字设置,或者通过!X,!Y 和 !Z 系统变量的 Margin 字段来设置。关于图形边缘的特殊地方在于根据字符尺寸来确定的单位。X 方向的边缘是52

IDL IDL入门教程

用两元素矢量来设置的,这两个元素分别规定左右的偏移量。[www.loach.net.cn]Y方向的边缘用同样的方法确定底部和顶部的偏移量。缺省边缘值是X轴方向为10和3,Y轴方向为4和2。为了查看当前字符尺寸的设备坐标值或像素坐标值,可键入:

IDL> Print, !D.X_Ch_Size, !D.Y_Ch_Size

例如,在苹果机(Macintosh)中,缺省的字符尺寸在X方向上为6个像素,在Y方向上为9个像素。因此,一张等值线图的边缘就被确定为图形的左边为60个像素(6*10),右边为18个像素(6*3)。如果CharacterSize关键字在 Contour命令中设置为2,那么将会出现图形的左边边缘为120个像素,而图形的右边边缘为36个像素。

例如,为了将图形四周边缘都改变为3个缺省的字符宽度,可键入:

IDL> Plot, time, curve, Xmargin=[3, 3], Ymargin=[3, 3]

注意,如果同时改变字符尺寸,图形将出现非常大的差异。因为图形边界是由字符的尺寸确定的。键入:

IDL> !X. Margin = [3, 3]

IDL> !Y. Margin = [3, 3]

IDL> Contour, peak, CharacterSize=2.5

IDL. Contour, peak, CharacterSize=1.5

假如用其它的字符尺寸来做一些同样的操作,会发现,字符尺寸越大,字符将变得很大并且图形部分将变得很小,这并不是所希望看到的。当向下继续学习时,请确保将图形边界已恢复为缺省值。键入:

IDL> !X.Margin = [10, 3]

IDL> !Y.Margin = [4, 2]

注意,不象许多其它系统变量通过将其设置为零即可恢复其缺省值那样,系统边缘变量则必须直接将其设置为缺省值。假如没有键入以上的两条命令,现在就键入。

设置图形位置

设置图形位置需要设置一个四个元素的矢量,该矢量依次给定图形在显示窗口中的左下角和右上角坐标[X0,Y0,X1,Y1]。这些坐标值通常为归一化的值,其范围在0至1之间(如:0常常代表显示窗口的左边或者底部,1常常代表显示窗口的右边或者顶部。)

扩展:idl / idl教程 / idl程序设计

设想需要将图形输出结果在显示窗口的上半部分显示,可以按如下设置!P.Position系统变量并显示图形:

IDL> !P.Position = [0.1, 0.5, 0.9, 0.9]

IDL> Plot, time, curve

所有后面的图形输出定位方法都是类似的。将!P.Position系统变量复位,以便后面的图形输出能正常地显示在窗口中。键入:

IDL> !p.position = 0

假如仅想给一张图形显示定位,可以用图形命令的Position关键字规定一个图形位置。如果要在整个显示窗口的左半部分显示等值线图,可以键入:

53

IDL IDL入门教程

IDL> Contour, peak, Position=[0.1, 0.1, 0.5, 0.9]

注意,Position关键字可以用来在相同的显示窗口输入多幅图形。(www.loach.net.cn]只要确保在输入第二幅图形和所有的后续图形时,使用NoErase关键字。这可防止在显示图形时删除前面已显示的图形。对于所有的图形输出命令来说,这是一项缺省特性,但是TV和TVScl命令是例外。

在一张等值线图上加入一条线图,可键入:

IDL> Plot, time, curve, Position=[0.1, 0.55, 0.95, 0.95]

IDL> Contour, peak, Position=[0.1, 0.1, 0.95, 0.45], /NoErase

设置图形区域

图形区域与图形位置一样,都是由归一化坐标值来确定的。同样可以通过设置!P.Region系统变量来指定。由于不存在和其它图形命令等效的关键字,因此设置图形区域没有设置图形位置方便。如果希望后续图形能正常地使用整个显示窗口,应确保已经将系统变量复位了。例如,在显示窗口上方三分之二的部分区域中显示一幅图形,键入:

IDL> !P.Region = [0.1, 0.33, 0.9, 0.9]

IDL> Plot, time, curve

将!P.Region系统变量复位,以便后续图形能正常地在窗口内显示。键入:

IDL> !P,Region = 0

创建多个图形

正如所见,通过使用图形位置和图形区域系统变量以及上面所讨论的关键字可以在一个显示窗口中定位多个图形(只要绘制第二个和后续的图形时使用了NoErase关键字)。但是使用!P.Multi系统变量在显示窗口内创建多个图形更加容易。!P.Multi由以下五个元素的矢量定义。

!P.Multi[0] !P.Multi的第一个元素包括剩下的要在显示窗口或者PostScript页上绘制的图

形数目。这有点不直观,以下就可以看到它是如何使用的。通常设置为0,

意思是,没有剩下要在显示窗口输出的图形。接下来的图形命令将删除显示

的图形,并且开始绘制新的多个图形中的第一个。

!P.Multi[1] 此元素规定了该页上图形的列数

!P.Multi[2] 此元素规定了该页上图形的行数

!P.Multi[3] 此元素规定了在Z方向上叠加的图形数目(仅适用已经建立了三维坐标系的情

况下)

!P.Multi[4] 此元素规定了是先按行显示图形(!P.Multi[4]=0),还是先按列显示图形

(!P.Multi[4]=1)。

54

IDL IDL入门教程

假如想将!P.Multi

参数设置为按两行两列在显示窗口内显示四幅图形,并且,先按列显示图形,键入: IDL> !P.Multi = [0, 2, 2, 0, 1] 显示图形时,如果要求每个图形占据窗口的四分之一位置,键入:

IDL> window, Xsize=500, Ysize=500

IDL> Plot, time, curve, LineStyle=0

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, Nlevels=10

IDL> Surface, peak, lon, lat

IDL> shade_Surf, peak, lon, lat

输出结果应与图24相似

图24: 在单个显示窗口内可以绘制多幅图形

给多幅图形的图留下标题空间

当IDL计算图形位置时,是用整个显示窗口来决定每幅图形的大小。[www.loach.net.cn)但是,有时想在显示窗口上有额外的空间来放图形标题或者其它类型的注释。可以通过使用!X,!Y和!Z!系统变量的“外 55

IDL IDL入门教程

边缘”字段为多幅图形留出空间。(www.loach.net.cn]外边缘字段仅仅在 P.Multi系统变量被使用时有效。它们与正常的图形边缘一样,也是按字符单位来计算的。

如果想为刚刚创建好的四个图形的总图加上一个标题,应为标题留出空间。键入: IDL> !P.Multi = [0, 2, 2, 0, 1]

IDL> !Y.Omargin = [2, 4]

IDL> Plot, time, curve, LineStyle=0

IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, Nlevels=10

IDL> Surface, peak, lon, lat

IDL> Shade_Surf, peak, lon, lat

IDL> XYOuts, 0.5, 0.9, /Normal, ‘Four Graphics Plots’, $

Alignment=0.5, Charsize=2.5 输出结果应于图25相似。

图25: 使用关键字!Y.OMargin在多幅图形的上方留出4个字符高度的空间来放标题 使用!P.Multi变量创建不对称的排列

使用!P.Multi变量绘图没有必要是对称排列。例如,需要曲面图与阴影图一上一下地排列显 56

IDL IDL入门教程

示在显示窗口的左边,而显示窗口的右边是一张用同样数据生成的等值线图。[www.loach.net.cn]可键入: IDL> !P.Multi = [0, 2, 2, 0, 1]

IDL> !Y.OMargin=[0,0]

IDL> Surface, peak, lon, lat

IDL> Shade_Surf, peak, lon, lat

IDL> !P.Multi = [1, 2, 1, 0, 0] IDL> Contour, peak, lon, lat, Xstyle=1, Ystyle=1, Nlevels=10

第一个!P.Multi命令设置了一个二列二行的排列形式,第一、第二张图已制好。第二个 !P.Multi命令设置了一个二列一行的排列形式。但要注意 !P.Multi[0]被设置为1。结果是等值线图进入了显示窗口的第二个位置而不是第一个。结果由图26可以看出。

图26: 可以使用!P.Multi在显示窗口定位图形的不对称排列

扩展:idl / idl教程 / idl程序设计

注意:与PLOT和CONTOUR命令不一样,TV命令与!P.Multi一起使用无效。但是,在此书中可以用TVImage程序代替TV命令,该程序在已经下载的程序中。如果设置了MULTI关键字,!P.Multi系统变量就有效。试试这些命令:

IDL> image = LoadData(7)

IDL> !P.Multi=[0, 2, 2]

IDL> FOR j=0, 3 DO TVImage, image, /Multi

57

IDL IDL入门教程

确保已经将!P.Multi复位,以便在一页上显示单个图形。[www.loach.net.cn)象许多系统变量一样, !P.Multi可以通过设置!P.Multi=0重新设置为它的缺省值。

IDL> !P.Multi = 0

给图形显示添加文本

图形注释和其它文本可以通过许多方式添加到图形显示上。最通常的方法是通过图形显示命令的关键字。被添加的文本可以三种字体“风格”中的任意一种形式出现:矢量字体(有时也称为软字体或 Hershey 字体)、TrueType字体、硬字体。字体类型可以根据表6通过设置!P.Font系统变量或者在图形输出命令中设置 Font关键字来加以选择。 !P.Font

-1

1 字体选择 矢量字体(也叫软字体或Hershey字体) 硬字体 TureType轮廓字体

表6: 字体“风格”可以通过设置!P.Font系统变量或者Font关键字为适当值来加以选择。矢

量字体是直接图形命令的缺省字体,它们有不依赖于平台的优点

在缺省值情况下,直接图形程序使用的是矢量或软字体的形式。矢量字体由矢量坐标描述。其结果是,它们是独立于平台并且极易在三维空间旋转。但是,许多人发现,对于高质量的硬拷贝输出来说,矢量字体太“瘦”了。为此,需要更丰满的字体(比如:TrueType字体或者PostScript打印机硬件字体)。通过设置!P.Font系统参数为-1或者在图形输出命令上设置Font关键字为-1,就选择矢量字体了。如:

IDL> Plot, time, curve, Font=-1, Xtitle=’Time’, $

Ytitle=’Signal’, Title=’Experiment 35F3a’

TrueType字体也称为轮廓字体,这种字体由一系列的外形轮廓描述的,这些轮廓通过创建一系列的多边形来填充。IDL有四种TrueType字体家族系列:Times, Helvetica, Courier, 和Symbol。TrueType字体渲染需要花更长的时间,因为这种字体首先必须刻绘出来,然后创建相应的填充多边形,最后填充。并且许多人发现这种字体在低分辨率显示器上用小磅值时显示效果不好。但是它们有可以旋转的优点,并且硬拷贝输出上较美观。TrueType字体是IDL对象图形系统的一种缺省字体。

用缺省的Helvetica TrueType字体的外形来绘制图形,须设置Font关键字为1。如: IDL> Plot, time, curve, Font=1, Xtitle=’Time’, $

Ytitle=’Signal’, Title=’Experiment 35F3a’

TrueType字体可以用Device命令通过Set_Font和TT_Font关键字来选择。如下: IDL> Device, Set-Font=’Courier’, /TT_Font

IDL> Plot, time, curve, Font=1, Xtitle=’Time’, $

Ytitle=’Signal’, Title=’Experiment 35F3a’

在IDL中,为了掌握更多的TrueType字体,可以使用联机帮助系统。

IDL> ? fonts

58

IDL IDL入门教程

硬字体通过设置!P.Font系统变量或 Font关键字为0来加以选择。(www.loach.net.cn]通常情况下,硬字体并不用于图形显示中,而是在当内容被输出到硬拷贝输出设备时使用,例如 PostScript打印机。直到最近的IDL版本,硬字体都不能很好地在三维空间内旋转。因此,在使用类似于Surface等三维命令时,一般都不使用硬字体。

IDL> Plot, time, curve, Font=0, Xtitle=’Time’, $

Xtitle=’Signal’, Title=’Experiment 35F3a’

找出可用字体的名称

可以用以下Device命令找出可用的硬字体名。如:

IDL> Device, Font=’*’, Get_FontNames=fontnames

IDL> For j=0, N_Elements(fontnames)-1 DO Print, fontnames[j]

只要使用TT_Font关键字,TureType字体名称可用类似的方法找出。TT_Font关键字用来选择系统上可用的TureType字体。(可以把自己的TureType字体加到IDL提供的四种系列类型内。如何实现可参考IDL的联机帮助系统。)

IDL> Device, FONT=’*’, Get_FontNames=fontnames, /TT-Font

IDL> For j=0, N_Elements(fontnames)-1 DO Print, fontnames[j]

可用的矢量字体名称在表7给出。

用XYOutS命令添加文本

在IDL 中一个非常重要的命令是XYOutS命令(“在XY 给定的位置,输出一个字符串”)。这个命令用来在窗口的特定位置放入一个文本字符串。(XYOutS 的第一个位置参数是X的位置,第二个位置参数是Y的位置)。例如,给线画图加上一个较大的标题,键入如下命令:

IDL> Plot, time, curve, Position=[0.15, 0.15, 0.95, 0.85]

IDL> XYOutS, 0.5, 32, ‘Results: Expe riment 35F3a’, Size=2.0

注意,是用数据坐标来给定X和Y的位置,同时Y坐标在图形边界之外。在缺省的情况下,XYOutS过程使用数据坐标系统。但是,如果选用适当的关键字,设备坐标系统 和归一化的坐标系统也可使用。

(数据坐标系统自然地由其自身描述。设备坐标有时称为像素坐标,设备坐标系统经常和图像一起使用。归一化的坐标系统在每个方向从0到1。当需要用独立于设备输出图形时,经常使用归一化坐标。)

例如,可以象如下使用归一化坐标把标题加在线画图上。当编写IDL程序时,用归一化坐标确定标题和其它注释等是一种很好的主意。这不仅更容易于在显示窗口定为图形,也便于在PostScript和其它硬拷贝输出文件中定位图形。

IDL> Plot, time, curve, Position=[0.15, 0.15, 0.95, 0.85]

59

IDL IDL入门教程

IDL> XYOutS, 0.2, 0.92, ‘Results: Experiment 35F3a’, $ Size=2.0, /Normal

数值 !3 !4 !5 !6 !7 !8 !9 !10 !11

描述 Simplex Roman Simplex Greek Duplex Roman Complex Roman Complex Greek Complex Italian Math Font Special CharactersGothic English

扩展:idl / idl教程 / idl程序设计

数值 !12 !13 !14 !15 !16 !17 !18 !20 !X

描述 Simplex Script Complex Script Gothic Italian Gothic German Cyrillic Triplex Roman Triplex Italian Miscellaneous 回到刚进入时的字体

表7:Hershey字体和其相应的在IDL中用于选择各字体的索引号

用矢量字体使用XYOut

XYOutS命令可用于矢量字体,TureType字体或硬字体,只需按上述的那样,简单的设置Font关键字值即可。[www.loach.net.cn)这儿的讨论是关于矢量字体的,因为在直接图形命令中,此字体系统使用最频繁。表7中给出可获得的矢量字体和其相应的索引号,可以通过索引号来选择的特定字体。

矢量字体或Hershey字体的主要优点是它们的平台独立性,并且可在三维空间中缩放和旋转。例如,可以用Triplex Roman字体输出上图的标题,键入:

IDL> Plot, time, curve, Position=[0.15, 0.15, 0.95, 0.85]

IDL> XYOutS, 0.2, 0.92, ‘!17Results: Experiment 35F3a!X’, $ Size=2.0, /Normal

Triplex Roman字体由!17转义序列来选定。标题串末端的!X将使字体转变为Simplex Roman字体,而Simplex Roman字体是在变为Triplex Roman字体前所使用的字体。这个转变步骤是非常重要的。否则,缺省设置将变为Triplex Roman,并且所有接下来的串标记都将使用Triplex Roman字体。试试使用 Greek字符集作为X轴的标题,并且按下面输出下图的标题。键入: IDL> Plot, time, curve, Xtitle=’!17w’, $ Position=[0.15, 0.15, 0.95, 0.85]

IDL> XYOutS, 0.2, 0.92, ‘Experiment 35F3X’, size=2.0, /Normal

可以在图27中看到结果。现在留心,即使没有规定该图的标题什么字符集,标题也是用Greek字符集输出的。恢复为缺省项Simplex Roman的唯一办法是用显式地使用 Simplex Roman字体输出另一个字符串,例如:

IDL> XYOutS, 0.5, 0.5, ‘!3Junk’, /Normal, CharSize=-1

60

IDL IDL入门教程

注意,在上面的代码中CharSize关键字的使用。(www.loach.net.cn]当这个关键字值为-1时,字符串被隐藏,不在窗口显示。

图27:当选择一种Hershey字体要注意,否则可能为用户提供一个看上去象希腊字母的标题 排列文本

可以用XYOutS

命令的Alignment关键字通过相对位置来定位文本。当Alignment的值为0时,字符串居左排列(这是缺省值);当Alignment的值为1时,字符串居右排列;当Alignment的值为0.5时,将根据X和Y值所定义的位置居中排列。例如:

IDL> Window, Xsize=300, Ysize=250

IDL> XYOutS, 150, 55, ‘Research’, Alignment=0.0, $

/Device, CharSIZE=2.0

IDL> XYOutS, 150, 110, ‘Research’, Alignment=.5, $

/Device, CharSIZE=2.0

IDL> XYOutS, 150, 170, ‘Research’, Alignment=1.0, $

/Device, CharSize=2.0

IDL> Plots, [0.5,0.5], [1.0,0.0], /Normal

删除文本

用XYOutS书写的文本有时可以通过用背景颜色书写同样的文本来删除。Color关键字与!P.Background系统变量一起使用可以达到这个目的。需要指出的是,这仅仅在文本只是写在背景上没有任何东西的情况下奏效。通常还有别的更有效的方法来删除注释。(可参见118页的“从 61

IDL IDL入门教程

显示窗口删除注释”的例子)。(www.loach.net.cn]为了明白如何用背景颜色删除注释,键入:

IDL> window, Xsize=300, Ysize=250

IDL> XYOutS, 150, 110, ‘Research’, Alignment=0.50, $

/Device, CharSize=2.0

IDL> XYOutS, 150, 110, ‘Research’ Alignment=0.50, $

/Device, CharSize=2.0, Color=!P.Background

改变文本的方向

用XYOutS命令输出的文本可以通过Orientation关键字相对于水平方向上的角度来定向。Orientation关键字可确定文本基线从水平基线开始旋转的度数。键入:

IDL> Window,Xsize=300, Ysize=250

IDL> XYOutS, 150, 110, ‘Research’, Alignment=0.5, $

/Device, CharSize=2.0, Orientation=45

IDL> XYOutS, 150,180, ‘Research’, Alignment=0.50, $

/Device, CharSize=2.0, Orientation=-45

给图形显示添加线和符号

给图形添加注释的另一种有效程序是PlotS命令,它是用来在图形显示上添加符号或线条。PlotS命令可在二维或三维空间中使用。

用PlotS程序画线,只需简单地提供含有X和Y坐标的矢量即可,矢量中的X、Y值是需要连接的点的X、Y坐标值。例如,从点(0,15)到点(6,15)在线画图上画一条基线,键入: IDL> Window, XSize=500, YSize=400

IDL> Plot, time, curve

IDL> PlotS, [0,6], [15,15], LineStyle=2

输出结果应与图28相似。

PlotS程序可以用来在任何位置标上符号。下面是在曲线上每五个点处标注一个菱形符号的实例。

IDL> TvLCT, [70, 255, 0], [70, 255, 250], [70, 0, 0], 1

IDL> Plot, time, curve, Background=1, Color=2

IDL> index = IndGen(20)*5

IDL> Plots, time[index], curve[index], Psym=4, $

Color=3, SymSize=2

62

IDL IDL入门教程

图28:用PlotS 命令画一条虚线跨过图形的中部

PlotS命令也可以用来在图上重要信息的周围画出一个方框。[www.loach.net.cn]通过PlotS命令与其它图形命令组合,如XYOutS命令,可以有效地注释图形显示。例:

IDL> TvLCT, [70, 255,0], [70,255,255], [70, 0, 0],1

IDL> Plot, time, curve, Background=1, Color=2

IDL> box_x_coords = [0.4, 0.4, 0.6, 0.6, 0.4]

IDL> box_x_coords = [0.4, 0.6, 0.6, 0.4,0.4]

IDL> PlotS, box_x_coords, box_y_coords, Color=3, /Normal

IDL> XYOutS, 0.5, 0.3, ‘Critical Zone’, Color=3, Size=2, $

扩展:idl / idl教程 / idl程序设计

Alignment = 0.5, /Normal

注意,可以容易地使用XYOutS和PlotS命令为图形显示创建图例。

图形显示添加色彩

另一种有效的标注图形显示的方法是使用颜色。Polyfill命令是一个低级的图形显示命令,它可用特殊的颜色或图案填充任意形状的多边形(无论是在二维或还是在三维环境中定义的)。例

如,可以使用Polyfill命令用红颜色填充上面线画图中方框:

IDL> TvLCT, 255, 0,0 ,4

IDL> Erase, Color=1

IDL> Polyfill, box_x_coords, box_y_coords, Color=4, /Normal

IDL> Plot, time, curve, Background=1, Color=2, /NoErase

63

IDL IDL入门教程

IDL> PlotS, Box_x_coords, box_y_coords, Color=3, /Normal

IDL> XYOutS, 0.5, 0.3, ‘Critical Zone’, Color=3, Size=2, $

Alignment = 0.5, / Normal

颜色有时代表一个数据集的另外一维的特性。(www.loach.net.cn]例如,可以二维圆形(或多边形)显示XY数据,而每个多边形的颜色就可表现出数据的某些附加特性,比如温度和人口密度等。看看是如何实现的。

IDL没有构建圆的模块,但是很容易编写这样一个功能模块。打开文本编辑器,键入代码来创建IDL的Circle功能。

FUNCTION CIRCLE, xcenter, ycenter, radius

Points = (2 * ! PI / 99.0) * FindGen(100)

x = xcenter + radius * Cos(points)

y = ycenter + radius * Sin(points)

RETURN, Transpose([x],[y])

END

组成圆周的X和Y值将以2*100数组形式返回。可以将该数组输入到Polyfill命令中。以Circle.pro保存该程序,并通过键入如下命令进行编译:

IDL> .Compile circle

然后,创建随机分布的X和Y数据。(将Seed设置回初始状态,这样输出结果将与图29看上去相似)。键入:

IDL> seed = -3L

IDL> x = RandomU(seed,30)

IDL> y = RandomU(seed,30)

将Z值设为这些X值和Y值的函数。键入:

IDL> z = (3 * ( (x-0.5)^2) + 5*((y-0.25)^2)) * 1000

打开窗口绘制XY位置,就可以看到这些数据是怎样以随机形式分布的。键入:

IDL> Window, Xsize=400, Ysize=350

IDL> Plot, x, y, Psym=4, Position=[0.15, 0.15, 0.75, 0.95],$

Xtitle=’X Locations’, Ytitle=’Y Locations’

将以不同颜色的圆显示与XY位置相关的Z数据。需要加入一张颜色表,并且使Z数据缩放至可获得的颜色数的范围内。键入:

IDL> LoadCT, 2

IDL> zcolors = Bytscl(z, Top=!D.Table_Size-1)

在这个例子里使用的Circle程序有许多弱点。主要缺点是它并非总是生成圆。假如用数据坐标系统来给定圆的坐标,圆形可能将以椭圆的形式显示,主要取决于图形长宽比例以及其它影响因素。(要获得非常棒的圆,可以从NASA Goddard Astrophysics的IDL例库中下载TVCircle程 64

IDL IDL入门教程

序,可以用浏览器通过来找到该例库)。[www.loach.net.cn]为避免Circle程序中的这种不足,可用Convert_Coord命令将数据坐标转换为设备坐标。键入:

IDL> coords = Convert_Coord (X, Y, /Data, /To_Device)

IDL> x = coords(0,*)

IDL> y = coords(1,*)

最后需要使用Polyfill命令画出表示Z数据的彩色圆。键入:

IDL> For j=0, 29 Do Polyfill, Circle(x(j), y(j), 10), $

/Fill, Color=zcolors(j), /Device

附带地说一下,最好有一个色棒能够告知Z值和各种颜色的某些关系。可以用本书的Colorbar程序增加一个色棒,键入:

IDL> Colorbar, Position = [0.85, 0.15, 0.90, 0.95], $

Range=[Min(z), Max(z)], /Vertical, $

Format=’(I5)’, /Right, Title=’Z Values’ 输出结果应与图29相似

图29:在二维图中圆的颜色代表了第三维信息

65

IDL IDL入门教程

平滑图像

可以通过将每个像素值与它周围相邻像素值进行平均来平滑图像。[www.loach.net.cn)这就是均值或方盒平滑。均值平滑是由IDL中的功能函数Smooth完成的,它是在给定的奇数宽度的范围内实现等加权值平滑。例如,如果周围是3*3宽度,那么每个像素由它和它的周围八个像素值的平均值代替。 比较一幅没有经过平滑处理的图像和经过5*5 方盒的均值平滑处理后的图像,键入: IDL>Window,0,XSize=192*3,YSize=192

IDL>TV,image,0,0

IDL>smoothed=Smooth(image,5,/Edge_Truncate)

IDL>TV, smoothed, 192, 0

注意,与命令Smooth一起使用的关键字Edge_Truncate。该关键字可复制图像边缘附近的像素,以便实现整幅图像的平滑。如果不使用该关键字,图像边缘附近的像素仅仅是简单复制,而没有平滑。

图像平滑被应用在一种称作晕光蒙片的图像处理技术中。这种技术可用作定位图像上的棱边或者是像素值突然变化的地方。这种技术非常简单:从未平滑的图像中减去平滑的图像即可。键入:

IDL>TV, ((image-smoothed)+255)/2.0, 2*192, 0

图像显示应如图36所示。

用Smooth命令,赋给相邻的像素值相等的权值来计算平均值。有时会导致出现不希望的模糊图像。另一种方式是用称为卷积的过程来平滑图像。这种技术中,一个方形内核和图像一起参与卷积计算。例如,在3*3的情况下,Smooth命令使用的内核为:

1 1 1

1 1 1

1 1 1

如果给予中心像素值更大的权值,而它周围像素值的权值小一些,图像就不会那么模糊了。例如,可以创建如下的一个核心:

1 2 1

2 8 2

1 2 1

通过Convol命令用上述内核对图像进行卷积处理,键入:

IDL>kernel=[[1,2,1], [2,8,2], [1,2,1]]

扩展:idl / idl教程 / idl程序设计

IDL>TV, image, 0, 0

IDL>TV, Smooth(image, 3, /Edge_Truncate), 192, 0

IDL>TV, Convol(image, kernel, Total(kernel), $

/Edge_Truncate), 2*192, 0

66

IDL IDL入门教程

图36:左边为原始图像,中间为平滑处理过的图像,右边为经晕光蒙片处理后的图像。(www.loach.net.cn]

当然,可以创建任意大小的内核。如下是一个典型高斯分布的5*5内核:

1 2 3 2 1

2 7 11 7 2

3 11 17 11 3

2 7 11 7 2

1 2 3 2 1

可将上述内核应用于图像处理:

IDL>kernel=[[1,2,3,2,1], [2,7,11,7,2], [3,11,17,11,3], $

[2,7,11,7,2], [1,2,3,2,1]]

IDL>TV, Convol(image, kernel, Total(kernel), $

/Edge_Truncate), 192*2, 0

消除图像噪声

将图像上的噪声消除是一种常规的图像处理技术。噪声来自许多方面,它经常降低图像质量。噪声的一般表现形式是黑白点相间噪声,其中一些随机的像素有极端的像素值。要了解图像平滑是怎样剔除这种噪声的,首先需要创建一幅噪声图像。用以前的图像,并键入如下的命令,将10%的像素转换为黑白点相间噪声: IDL>noisy=image IDL>points=RandomU(seed, 1800)*192*192 IDL>noisy(points)=255 IDL>points=RandomU(seed, 1800)*192*192 IDL>noisy(points)=0 在原始图像的旁边创建一个窗口并显示噪声图像: IDL>Window,XSize=192*3,YSize=192 IDL>TV,image,0,0 IDL>TV, noisy, 192, 0 IDL中的Median命令是从图像上消除黑白点相间噪声的很好选择。Median命令与Smooth命令类似。不同之处是Median命令计算相邻像素的中间值,而不是平均值。这就有两个重要作用。第一,它能删除图像中的极端值。第二,它不使那些尺寸比邻域范围大的图像棱边或特征变模糊。要看是如何工作的,键入:

IDL>TV, Median(noisy, 3), 2*192, 0

67

IDL IDL入门教程

图形显示应如图37

所示。[www.loach.net.cn]

图37:左边为原始图像,中间为噪声图像,右边为用中值滤波器平滑处理后的噪声图像。 增强图像棱边

一个图像可以锐化或通过微分来增强图像棱边。IDL提供了两个做好的棱边增强函数:Roberts和

Sobel。还有一些其它方法也可用来增强图像棱边。例如,可以用拉普拉斯算子来和图像做卷积: 1 1 1

1 -7 1

1 1 1

因为改进了图像棱边的对比度,这也常常被称为拉普拉斯(Laplacian)锐化操作。需要了解这些方法是如何工作的,可键入:

IDL>TV, Sobel(image), 0

IDL>TV, Roberts(image), 1

IDL>kernel=[[1,1,1], [1,-7,1], [1,1,1]]

IDL>TV, Convol(image, kernel), 2

图形显示应如图38所示。

图38:三种增强图像棱边的方式。左边用的是Sobel方法。中间用的是Roberts方法。

右边是用拉普拉斯算子对图像做卷积。

图像的频域滤波

频域滤波是常规的图像和信号处理技术。它可以用来平滑处理图像,锐化图像,降低图像的模糊程度,和恢复图像。

68

IDL IDL入门教程

频率域滤波有如下三个基本步骤:

1. 用快速傅里叶变换(FFT)将图像从空间域转变为频域。(www.loach.net.cn]

2. 将转换后的图像乘以一个频率滤波器。

3. 将滤波后的图像返回为空间域。

这些步骤在IDL中是用快速傅里叶变换(FFT函数)完成的。(如果命令FFT的第二个定位参数为-1,则图像由空间域转变为频域。如果参数为1,则图像将相反转化)。频率滤波命令的一般形式如下:

filtered_image=FFT(FFT(image, -1)*filter, 1) 在这种情况中,image既可是一维矢量,也可以是一幅二维图像。滤波器是用来滤波图像中某些特定频率的一维矢量或二维数组。下面将详细介绍。

创建图像滤波器 在IDL中用基于数组的操作和函数很容易创建图像数字滤波器。许多普通的滤波器利用了所谓的频率图像或欧氏距离图的优点。一幅二维图像的欧氏距离图是一个与图像有同样大小的数组。距离图的每个像素被赋给一个值,这个值等于它到二维数组最近的角的距离。在IDL中的Dist命令用作创建欧氏距离图或频率图像。要观看一幅简单的距离图,可键入:

IDL>Surface,Dist(40)

在频域滤波中所用的滤波器一般为Butterworth频率滤波器。如下的方程给出一个低过Butterworth频率滤波器一般形式:

2nfilter=1/[1+C(R/R0)]

其中,常量C等于1.0或0.414[这个值将滤波器的幅度在R=R0时定义为50%或1/Sqrt(2)],R

,n是滤波器的阶数,通常为为频率图像,R0为给定的滤波器截止频率(实际中由像素宽度代替)

1。

高过Butterworth滤波器由如方程给出:

2nfilter=1/[1+C(R0/ R)]

要将频域滤波器应用到图像中,可用命令LoadData来打开图像Earth Mantle Convection。这是一个248*248的二维数组。

IDL>convec=LoadData(11)

键入如下命令来打开一个窗口,装入颜色表Standard Gramma II,并在左上角显示原始图像: IDL>Window,0,XSize=248*2,YSize=248*2

IDL>LoadCT,5

IDL>TV,convec,0,248

在频域滤波中的第一步是用函数FFT将图像由空间域转换为频率域,键入:

IDL>freqDomainImage=FFT(convec,-1)

通常,低频项代表一般的图像形状,高频项对图像增加细节。浏览频率域的图像通常是没有意义的,但有时对观察频域图像的功率谱有用。

功率谱是一幅频域图像中不同组成部分的幅度图。与源点(通常代表图像的中心)不同的距离代表不同的频率,相对源点不同的方向代表在原图像特征的不同方向。每个位置的功率表明该频率的大小和以在图像中的方向。功率谱对分离图像中的周期性结构或噪声是特别有用的。功率谱的幅度通常用对数座标表示,因为功率从某个频率到下一频率的变化非常大。

扩展:idl / idl教程 / idl程序设计

计算出这幅对流图像的功率谱,并将其显示在原始图的相邻位置上,可键入:

IDL>power=Shift(Alog(Abs(freqDomainImage)),124,124)

IDL>TV,power,248,248

69

IDL IDL入门教程

功率谱中的对称性表明这个图像上在越来越多的频率中包含了许多周期性结构。(www.loach.net.cn]输出应该类似于图39)。这个练习的目的是过滤掉图像中较高的频率。

接下来的一步是用频率滤波器转换图像。ButterWorth低通滤波器用于滤出图像内的高频成分。这些高频成分为图像提供详细信息,所以最终的结果是完成图像的平滑处理。创建低通频率滤波器可键入:

^IDL>filter = 1.0 / (1.0D + Dist(248)/15.0)2

注意,截止频率宽度是15个像素。这是足以删除高频的一半。在图39中功率谱可以看到。 使用该频率滤波器,再将图像由频率域转换回空间域,最后显示滤波后的图像。键入:

IDL>filtered = FFT(freqDomainImge * filter, 1) IDL>TV, filtered, 0, 0 为了让自己看到滤掉高频成分的图像,可以显示滤波后图像的功率谱,并在其旁显示滤波后的图像,键入:

IDL>filteredFreqImg = FFT(filtered, -1)

IDL>power = shift(Alog(Abs(filteredFreqImg)), 124, 124)

IDL>TV, power, 248,0

70

IDL IDL入门教程

图39:频域滤波器的图解。(www.loach.net.cn)在图的上半部分是没有滤波的图像,左边是它的功率谱。在图的下半部分是滤波后的图像,相邻的左边是它的功率谱。注意,大约一半高频成分在滤波后的图像中已经被消除,消除了很多图像细节信息,也平滑了图像。

71

IDL IDL入门教程

第三章 图像数据处理

本章概要

IDL最开始是一种处理图像的语言。[www.loach.net.cn)正因为此,世界各地的许多科学家和工程师仍在用IDL语言。这章阐述了图像处理的基本工作。将从中学到以下几点

1. 怎样读取和显示图像数据

2. 怎样缩放图像数据

3. 怎样在显示窗口中定位图像

4. 怎样改变图像的大小

5. 怎样从显示设备中读取图像

6. 怎样完成基本的图像处理任务

7. 怎样建立简单的图像滤波器

图像处理

事实上,任何类型的二维数据集都可认为是一幅图像。但是要在一个8位的显示设备上显示图像数据,就必须将图像数据调整为 0~255之间的字节型数值。(在一个24-bit的显示设备上,24位图像的RGB值必须调整成字节型数值。)因为图像总是以字节型数值显示,所以图像总是以字节型数组来存储。但是无论图像是怎样存储的,图像总是由两个显示图像的IDL命令:TV和TVScl以字节型数值来显示。

要了解是怎样工作的,需要有一些图像数据用于处理。用命令LoadData来打开图像数据集Ali and Dave。将要处理这两幅图像数据中的第二幅图像。键入

IDL>image=LoadData(10)

IDL>image=image[*,*,1]

显示图像

可用TV和TVScl两个IDL命令中的任一个来显示图像。这两个命令几乎在各个方面都是一样的,包括能与之一起使用的关键字。仅仅在一个方面不同:TVScl将图像数据调整为与IDL运行时所用颜色数目相适应的字节型数值。例如:如果在使用IDL时用220种颜色,则在图像显示之前TVScl将图像数据调整为0~219之间的字节型数值。

另一方面,TV命令取图像数据本身的值,仅仅将其作为字节型数值传送到显示设备上。如果有必要,图像数据将被截断以符合字节型数值。如果图像数据不被调整到0~255之间,图像将很可能显示不正确。

注意,与Plot,Surface和Contour命令不同,TV和TVScl命令在显示图像之前不删除已显示的内容。一般情况下这个问题不大,但有时候也会产生一些麻烦。如果想要一个干净的显示窗口来显示图像数据,无论当前窗口上的显示内容是什么,都可用一个简单的命令Erase来删除。 IDL>Erase

72

IDL IDL入门教程

这里有一个实例。(www.loach.net.cn)刚才读取的IDL的图像数据集已经调整在0~255之间。

可以键入如下内容来查看:

IDL>Print,Max(image),Min(image)

但是,如果在一个8位显示设备上工作,可能没有全部使用在显示器上可用的256种颜色。如果需要了解正在使用多少种颜色,可键入:

IDL>Print,!D.Table_Size

在一个8位显示器(这里指颜色表的大小)上,运行IDL时所用颜色的数目通常是在210~240之间,显然可用的颜色太少了。在一个24位的显示器上,可以获得1670000种颜色,但颜色表大小仍然是256。以后将会学到IDL是怎样选择它所用的颜色数目。

打开一个显示窗口,装上灰度颜色表, 用TV命令显示图像:

IDL>Window,0,XSize=192,YSize=192

IDL>LoadCT,0

IDL>TV,image

所得图像应如图30

所示。

图30:IDL和Research Systems公司的创始人--David Stern的图像。People.dat数据集中的另外一幅图像是Ali Bahrami,Research Systems公司的第一为员工。他们两个依然致力

于IDL的开发。

因为是用TV命令,所以数据没有经过调整就被传送到显示器中。尽管看不出来,但图像上所有大于IDL运行时的颜色数目的像素值都被设为同样的值。也就是说,比!D.Table_Size-1值大的像素被以相同的颜色显示。(在这种情况下,看到的颜色是灰色明暗图。)

如果用TVScl命令显示图像,也许能看出差别。打开另一个窗口并将其移到第一个窗口的旁边。用TVScl命令显示图像:

IDL>window,1,XSize=192,YSize=192

IDL>TVScl,image

可看到两个图像的明暗程度不同。因为这幅图像数据最大值为238,所以差别是很微弱的。

如果看不出差别,可先在0~255之间对数据进行调整:

IDL>west,0

IDL>image=Bytscl(image)

IDL>tv,image

IDL>west,1

IDL>tvscl,image

扩展:idl / idl教程 / idl程序设计

如果仍不能看到差别,可装入颜色表。Red Temperature颜色表可能起作用。键入: IDL>LoadCT,3

如果要了解TVScl作了些什么,可调整数据并用TV命令显示:

73

IDL IDL入门教程

IDL>Window,2,XSize=192,YSize=192

IDL>scaled=Bytscl(image,Top=!D.Table_Size-1)

IDL>TV,scaled

在窗口2中看到的图像应与窗口1中的图像一样。(www.loach.net.cn)这就是所说的,TVScl将数据调整为与IDL运行时所用颜色数目相适应的字节型数值。

注意:如果在显示窗口的图像不是用red_temperature颜色表显示的话,则可能是在一个16位或24位彩显上使用IDL。在这种情况下,为了下面的练习,确保关闭颜色分解器。键入如下命令:

IDL>Device,Decomposed=0

IDL>TV,scaled

如果用的是一个16位或24位显示器,为了看到新的颜色生效,在改变颜色表后,需要重新运行每个图形命令。在一个16位或24位显示器上,颜色表中的颜色没有直接被索引或连接到显示器上的色彩表。何况颜色表是图像用来查找每个像素该使用哪种颜色的一种方法。而像素的颜色是直接表示的。

一般来说,如果不知道数据是否被调整过,很可能想用TVScl命令,因为这将给图像像素值以最大可能的对比度。但是如果颜色对来说是重要的话(并且它几乎总是这样),那么可能从来不想用TVScl命令。相反,将愿意自己缩放图像数据,然后用TV命令来显示。

调整图像数据

假设正在测量大气压,并将测量数据在一色棒旁边以图像显示。可能想比较这个星期收集的图像数据和上个星期收集的图像数据。换句话说,想确定一种特定的颜色,比如红色,在这套数据中的红色和上个星期的数据中的红色表示相同的压力。

如果用TVScl命令显示这个星期和上个星期的图像数据,绝对不能保证特定的红色在两个数据组中能代表同一事情。

这些出入来自两个原因。第一,可能今天使用IDL时的颜色数目和上个星期使用IDL时不同。因为TVScl将图像数据调整到IDL运行时的颜色数目内,这可能会引起错误。第二,不能确保两组数据组间具有相同的数据范围。因而,用TVScl调整可能再次引起错误。

为解决这些问题,可用BytScl命令调整数据,并用TV命令显示。为确保IDL运行时所用的颜色数目不引起错误,可将数据调整到相同的颜色值内。并且,为确保数据集中数据的范围不引起错误,可以将数据调整到同样的数据范围。

可通过BytScl命令,应用关键字Top,Min和Max实现上述要求。例如,假设总是想以100种不同的灰度深浅或颜色深浅来显示数据,并且假设在任何数据集中希望最小数据值为15,而最大的有效值为245。可用如下BytScl命令实现:

IDL>scaledImage=BytScl(image,Min=15,Max=245,Top=99)

这个例子中, 数据调整之前在数据集中小于15的数值将设定为15。类似地,在数据调整之前,在数据集中任何大于245的数值将被设定为245。一旦数据被调整了,就可用TV命令显示。

IDL>TV,scaledImage

如果总是这样调整数据集(并且在IDL运行时总是有至少100个灰色级别或颜色数),那么上个星期的数据集就能直接与这个星期的数据集比较。一个特定的颜色,红色将总是表示一个特定的数据范围或压力。

可能在显示器上开了许多图像窗口。可用一个简单的命令删除所有开着的窗口。键入: IDL>WHILE !D.Window NE-1 DO WDelete,!D.Window

74

IDL IDL入门教程

将图像调整到颜色表的不同部位

需要知道如何调整图像数据的另一个理由,是要能在使用8位显示器时,将数据调整到颜色表的不同部位。(www.loach.net.cn]这使图像能用不同颜色显示出来,或者能将颜色表的特定色段用于特别的目的。例如,也许想将颜色表的一部分保留作为画图用的颜色。

注意:用24位彩显的一个很大的好处是能随时使用一个没有限制的颜色表。24位彩显的缺点是,在改变颜色表之后,为了看到新颜色生效,不得不重新运行图形命令(例如:TV命令)。在本书后面将看到如何编写程序,使得当一个新的颜色表装入后,能自动重新运行图形命令。

在大多数8位显示器上仅仅有一个物理颜色表,并且所有的IDL图形窗口都用它。但是通过操作颜色表可以让它看上去象是同时装入几个不同的颜色表。可以通过将不同的颜色表装入到一个物理颜色表的不同部位来实现这一点。也许实现这点的最简单的方式是在LoadCT或XLoadCT命令中用NColors和Bottom关键字。

例如,假设想用两个看上去不同的颜色表来显示同一幅图像。在用IDL打开一个图形窗口后,能通过测试系统变量!D.Table_Size的值知道在IDL运行时颜色表中有多少种颜色。如果将这个数目一分为二,就知道每个图像该用多少种颜色:

IDL>half=!D.Table_Size/2

为了在同一窗口用看上去不同的两个颜色表显示图像数据image,必须将图像数据调整为适应两个颜色空间范围的值。首先,用BytScl命令调整图像数据为适应第一个部分颜色表的值,生成一个新的图像image1:

IDL>image1=BytScl(image,Top=half-1)

现在,按如下做法将图像数据调整为适应第二个部分颜色表的值,生成第二个图像image2: IDL>image2=BytScl(image,TOP=half-1)+Byte(half)

按如下做法将两个已调整的图像肩并肩地放在同一个窗口。注意,在使用TV命令。明白这是为什么吗?

IDL>Window,XSize=192*2,YSize=192

IDL>TV,image1

IDL>TV,image2,192,0

现在需要用一个灰度颜色表(颜色表索引号为0)将左边的图像显示出来。必须将那些灰度级颜色装入颜色表中被第一个图像数据占用的部分。键入:

IDL>LoadCT,0,NColors=half,Bottom=0

如果用XLoadCT命令将颜色装入颜色表的第二部分,就能为右边的图像交互式地选择想要的任何颜色表。如下:

IDL>XLoadCT,NColors=half,Bottom=half

为了继续本章后面的例子,要恢复一个正常的颜色表,键入:

IDL>LoadCT,0

在24位显示器上用不同的颜色表显示图像

当在16位或24位显示器上运行时,使用不同的颜色表和装入颜色并显示图像一样简单。例如,如果正在一个16位或24位的显示器上运行时,可以试一试:

扩展:idl / idl教程 / idl程序设计

IDL>world=LoadData(7)

IDL>Window,1,Title=‘Gray Scale Image’

IDL>LoadCT,0

75

IDL IDL入门教程

IDL>TV,world

IDL>Window,2,Title=‘Color Image’

IDL>LoadCT,5

IDL>TV,world

显示24位图像

真彩色(或24位)图像也能用TV命令显示。(www.loach.net.cn]24位图像总是由一个3维数据集构成,它的3个维数中的一个值设为3。例如,数据集可以是一个m*n*3的数组,这种情况下,图像被认为是隔波段扫描(band-interleaved);如果图像是m*3*n则被认为是隔行扫描(row-interleaved);如果是3*m*n则被认为是隔像素扫描(pixel-interleaved)。

装载一幅24位图像,键入如下命令:

IDL>rose=LoadData(16)

这个数据组是一个按像素扫描的图像。通过键入如下命令可知道这点:

IDL>Help,rose

ROSE BYTE =Array[3,227,149]

要在一个8位显示器上显示一幅24位的图像,仅仅需要用关键字True来说明其用的是哪种扫描方式。True=1为隔像素扫描;True=2为隔行扫描;True=3为隔波段扫描。

IDL>Window,XSize=227,YSize=149

IDL>TV,rose,True=1 ;Pixel-interleaved

注意,24位图像在8位显示器上显示将表现为灰度级。要在这样的显示器上看到真彩色的图像,需要创建一幅2维图像以及伴随该24位图像或3维图像数据的红色、绿色、蓝色颜色表。这在IDL中可用命令Color_Quan来实现。如果使用8位显示器,键入如下命令:

IDL>image2d=Color_Quan(image24,1,r,g,b)

IDL>TVLCT,r,g,b

IDL>TV,image2d

现在可看到彩色图像了。

在24位显示器上显示24位图像

如果使用的是一台24位显示器,情形稍微复杂一点。为了正确显示一幅24位图像,必须打开颜色分解器。这在大多数真彩模式下的工作站上自动实现的,但在真彩模式Windows下,IDL5.2版本却不能自动实现。为确保以正确的图像颜色显示24位图像,应该在24位显示器上键入如下命令:

IDL>Device,Decomposed=1

IDL>TV,image24

注意,下载的本书配套的程序TVImage自动设置正确的颜色分解器,这取决于要显示的图像是24位还是8位。

在24位显示器上显示8位图像

在一台24位显示器上,8位图像在显示时遍历了整个颜色表。换句话说,一个8位图像的像 76

IDL IDL入门教程

素值被作为一个索引号,该索引号为给定的像素查找特定的红色,绿色和蓝色。[www.loach.net.cn)这意味着如果在使用IDL时改变了颜色表,必须重新显示该2维图像来看新的颜色是否生效。这是因为在24位显示器上颜色是在图像被显示时决定的,同时也因为正在用RGB颜色模式。并且特别要注意必须关上颜色分解器,否则将忽视颜色表矢量,并总是用灰度色彩来显示8位图像。如果用的是一个24位显示器,键入如下命令:

IDL>world=LoadData(7)

IDL>Window,XSize=360,YSize=360

IDL>LoadCT,5

IDL>Device,Decomposed=0

IDL>TV,world

为了以另一种颜色表显示图像,装入该颜色并重新运行TV命令,使图像像素值遍历整个颜色表矢量。注意当只运行LoadCT命令时,图像颜色不变。

IDL>LoadCT,3

IDL>TV,world

控制图像显示顺序

通常,当IDL显示一幅图像时,习惯上图像的第0列和第0行为图像的左下角。有些人喜欢将图像的第0列和第0行作为图像的左上角。如果喜欢第二种习惯方式,可以通过设置系统变量!Order让IDL使用习惯。在缺省时,!Order设为0。如果希望将所有图像的左上角都显示在第0列和第0行,可设置!Order=1。

如果只是希望用第二种方式显示某幅图像,可在使用TV或TVScl命令时,用关键字Order设置。例如,可以同时观看两种显示方式,键入:

IDL>Window,XSize=192*2,YSize=192

IDL>TVScl,image,Order=0

IDL>TVScl,image,Order=1,192,0

可能从别人那儿得到一个图像数据文件,显示时倒过来了。这大多是因为创建数据文件的人在排放第0列和第0行时用了不同的习惯。将关键字Order的值反过来,看是否纠正了错误.

改变图像尺寸

IDL提供了两个改变图像大小的命令:Rebin和Congrid 。

Rebin的限制为新建图像的尺寸必须是原始图像尺寸的整数倍或整数比例。例如,变量image可以在X方向或Y方向上变化为192/2和192*3个元素。但不应该是300或500个元素。图像大小也可以在一个方向减小,另一个方向增大。例如,可将变量image重新变化为384列和96行,键入如下命令。

IDL>Window,XSize=384,YSize=96

IDL>new=Rebin(image,384,96)

IDL>TVScl,new

输出图像应与图31类似。

77

IDL IDL入门教程

图31:用Rebin命令缩放的图像其大小必须与原始图像大小有整数倍关系。(www.loach.net.cn)

在缺省情况下,当放大一幅图像时Rebin采用双线性插值,当缩小一幅图像时则采用最邻近平均法。如果关键字Sample被设定后,在两个方向上都可用最邻近采样法。双线性插值更为精确,但需要更多的计算时间。

IDL>Window,XSize=192/2,YSize=192/2

IDL>new=Rebin(image,96,96,/Sample)

IDL>TVScl,new

除了下面两个方面外,Congrid与Rebin是相似的。第一,在新图像中的列数和行数可以设为任意值。第二,在缺省情况下,用的是最邻近采样法。如果想用双线性插值,必须设置关键字Interp:

IDL>Window,XSize=600,YSize=400

IDL>new=Congrid(image,600,400,/Interp)

IDL>TVScl,new

在PostScript设备上改变图像大小

象PostScript这种设备,其像素是可调节的(相对于固定像素的显示器来说),在调节图像尺寸时有所不同。(详细信息参考185页的“显示器与PostScript设备的差别”)。特别是,可不用Rebin或Congrid命令来改变图像的大小,而是用TV或TVScl命令通过关键字XSize和Ysize来改变图像的大小。

扩展:idl / idl教程 / idl程序设计

例如,当将图像输出到一个PostScript文件时,如果想将图像的显示比例定为6:4的话,也许愿意用如下代码,而不是上面的用Congrid命令将图像放大为600*400。

IDL>thisDevice=!D.Name

IDL>Set_Plot,'PS'

IDL>Device,XSize=6,YSize=4,/Inches

IDL>TVScl,image,XSize=6,YSize=4,/Inches

IDL>Set_Plot,this Device

如果图像大小和位置是用下面的归一化坐标来表示的,可以写出真正的独立于显示设备的图像显示代码。

在显示窗口中定位图像

通常显示一幅图像时,IDL将图像的左下角放在窗口的左下角。但是可通过TV或TVScl命令的附加参数来将图像移动到显示窗口中的其它位置。

例如,如果给出第二个参数,它则被视为图像在窗口中的位置。图像位置由显示窗口的尺寸和图像的尺寸计算出来的。详细算法可参阅TV命令的在线帮助。键入:

78

IDL IDL入门教程

IDL>? TV

位置可从显示器的左上角开始,一直到显示器的右下角。[www.loach.net.cn]例如,在384*384的显示窗口内,从显示器的左上角开始,对于192*192的图像来说有四种位置。键入:

IDL>Window,XSize=384,YSize=384

IDL>TVScl,image,0

IDL>TVScl,image,1

IDL>TVScl,image,2

IDL>TVScl,image,3

通过显式地指定图像左下角的象素位置来定位一幅图像是可以的。TV或TVScl命令中在图像数据名后给定两个附加参数即可以实现这点。例如,将一幅192*192名为image的图像定位于刚刚创建的显示窗口中间。可键入:

IDL>Erase,Color=!D.Table_Size-1

IDL>TVScl,image,96,96

这样,将图像的左下角放在像素点(96,96)处。当希望为附加图留下空间时,如色棒或其它的注释,这种定位图像的方法是很重要的。

例如,键入如下命令来在窗口的左边显示一个色棒,在窗口的右边显示图像。显示窗口将如图32所示。

IDL>Window,XSize=320,YSize=320

图32:此图像用颜色棒来显示其颜色梯度

IDL>ncolors=!D.Table_Size

IDL>TvLCT,255,255,0,ncolors-1

IDL>Erase,color=ncolors-1

IDL>colorbar=Replicate(1B,20)#BIndGen(256)

IDL>TV,BytScl(colorbar,Top=ncolors-2),32,36

IDL>TV,BytScl(image,Top=ncolors-2),92,64

79

IDL IDL入门教程

用归一化的坐标来定位图像

用归一化的坐标系来定位图像和确定图像大小是很方便的。(www.loach.net.cn]这与其它IDL图 象命令使用关键字Position的用法相似。(详细信息参考185页的“显示器与PostScript设备的差别”)。如果要在可变尺寸的窗口内显示图像,或者要在同一显示窗口内与其它IDL的图形程序共同使用图像,或者希望所编写的将图像传送到一个PostScript文件的IDL程序不遇到麻烦,在这些情况下,用归一化坐标定位图像和确定图像大小是非常方便的,特别是对最后一种情况。例如,刚才键入的命令是放一个色棒在图像旁。尽管这些命令在显示窗口内可以很好地工作,但如果想在PostScript设备上输出中得到类似的结果,上述命令是不可能的。(若有问题,参考185页的“显示器与PostScript设备的差别”)。

假设可以用关键字Position在窗口中确定图像的大小和位置,那么结果将如何呢?设想将图像放入一个任意的窗口内,并占满其比如80%的空间。相对于归一化坐标来说,可将图像在窗口的位置表达为:

position=[0.1,0.1,0.9,0.9]

但这是怎样被翻译成图像通常使用的设备坐标的呢?这自然要取决于显示窗口的大小。但能够知道显示窗口中可视部分的大小。这是由系统变量!D.X_VSize和!D.Y_Vsize以设备或像素单元来给定的。

通过像素坐标,可以按如下方法计算出图像所需的尺寸和在输出窗口起始的位置: xsize=(position[2]-position[0])*!D.X_VSize

ysize=(position[3]-position[1])*!D.Y_VSize

xstart= position[0]*!D.X_VSize

ystart= position[1]*!D.Y_VSize

将图像输出到显示设备和输出到PostScript文件的唯一区别是如何确定图像的尺寸。可以编写如下代码来显示图像:

IF !D.Name EQ 'PS' THEN $

TV, image, XSize=xsize, YSize=ysize, xstart, ystart $

ELSE $

TV, Congrid(image, xsize, ysize), xstart, ystart

无论是将图像输出到显示器还是输出到PostScript文件中, 上述代码都起作用。但这样做时图像的横纵比例不能得到保证。事实上,可以让图像适合窗口的形状。这对一些应用程序来说工作的很好,但对另一些却不是这样。无论在什么情况下该问题很容易解决,因为如果想保留图像的横纵比例时,只需固定好图像的一边,并以适当的方式调整图像另一边的坐标即可。

实现该项功能的代码已经写好,放在下载的程序TVImage中。无论是在显示终端,还是在PostScript文件中,TVImage都用关键字Position来定位图像和确定图像大小,。如果希望TVImage程序能完好地保持显示图像的横纵比例,可以使用关键字Keep_Aspect_Ratio。

可以用TVImage重新生成色棒位于图像左边的上述图像:

IDL>Erase,color=ncolors-1

IDL>barPosition=[32,32,52,292]/320.0

IDL>imagePosition=[92, 64, 284, 256]/320.0

IDL>colorbar=Replicate(1B, 20)#BIndGen(256)

IDL>TVImage,BytScl (colorbar,Top=ncolors-2),$

Position=barPosition

IDL>TVImage,BytScl (image,Top=ncolors-2),$

Position=imagePosition

80

IDL IDL入门教程

这样做的好处,不但因为图像可以在任何尺寸的窗口或PostScript文件中以及显示器上显示,而且因为它使得在显示窗口中轻易增加其它图形成为可能。(www.loach.net.cn)例如,可以非常容易地在色棒和图像周围放置外框或标记。键入:

IDL>TvLCT,255,255,255,ncolors-1

IDL>Plot,[0,!D.Table_Size,YRange=[0,!D.Table_Size],$

扩展:idl / idl教程 / idl程序设计

/NoData,Color=0,Position=barPosition,XTicks=1,$

/NoErase, XStyle=1, YStyle=1, XTickFormat='(A1)'?$

YTick=4

IDL>Plot, IndGen(192), IndGen(192), /NoData, $

Position=imagePosition, /NoErase, $

XStyle=1, YStyle=1, Color=0

输出结果应如图33所示。

图33:用命令TVImage不仅允许使用独立于设备的方法定位图像而且容易使用其它图形命令

从显示器中读取图像

有时花许多时间,运行许多命令,才得到了喜欢的图形显示。将图形显示读到一个图像变量中以便处理甚至硬拷贝输出是很方便的。因此,现在需要知道如何得到IDL图形窗口的拷屏。可以用TVRD命令将IDL图形窗口的内容读到一个2维IDL字节型数组中。

要在一个8位显示器上读取整个图形窗口,可键入如下命令:

IDL>Window,XSize=250,YSize=250

IDL>TVScl,image

IDL>new_image=TVRD()

IDL>Help,new_image

注意,新创建的变量现在是一个250*250字节的数组。

81

IDL IDL入门教程

在24位显示器上截屏

如果在16位或24位显示器上运行IDL,不能象上面那样使用TVRD命令。[www.loach.net.cn)16位或24位 显示器有3个颜色通道。如果象上面不用任何参数来使用TVRD命令,那么得到的2维数组的每个像素值为该像素三个通道中的最大像素值。除非装载一个灰度色彩表(这样,每个通道有同样的值),否则就不是所期望的。要想在24位显示器上截屏,只需在命令TVRD中设置关键字True即可。如果用的是16位或24位显示器则键入如下命令:

IDL>new_image=TVRD(True=1)

但是注意,得到的是一个24位图像,而不是一个2维8位图像。当显示该图像时就要用带关键字True 的TV命令:

IDL>Help,new_image

IDL>Erase

IDL>TV,new_image,True=1

读取显示图像的一部分

如果只想读显示窗口的某一部分,可指定想要的部分窗口的左下角的像素坐标和要读取的列和行的数目。换句话说,能指定矩形区域。例如,如果只想捕获上述头像的脸部,可键入:

IDL>new_image=TVRD(40,30,110,130)

在IDL中,从显示器读取的2维数组可以象处理其它图像那样进行处理。如果需要显示,可键入:

IDL>Erase

IDL>TV,new_image

IDL中基本的图像处理

IDL原是作为一种图像处理的工具,所以它有很强的图像处理能力。这节中描述的是一些IDL中基本的图像处理工具。

直方图均衡化

如果观察图像中的像素值分布,往往会发现分布趋向集中在一个狭窄的数值范围内。实际上,图像有一个非常窄的动态颜色范围。如果像素分布开,以致使像素值的每个子范围都与这些像素值一样拥有数目大约相同的像素,则该图像的信息内容就有可能增加。将像素分布到整个颜色范围的过程叫做直方图均衡化。

例如,用LoadData命令打开数据集CT Scan Thoracic Cavity。这是一幅CT扫描图像,该图像具有一个狭窄的动态颜色范围。

IDL>scan=LoadData(5)

要看变量scan的像素值分布的柱状图,键入如下命令。显示图像窗口应如图34所示。 82

IDL IDL入门教程

图34:正常图像具有狭窄的像素值分布。(www.loach.net.cn]这里的像素值集中在50-10之间。

IDL>LoadCT,0

IDL>Window,0,XSize=600,YSize=250

IDL>TV,scan

IDL>Plot,Histogram(scan),/NoErase,Max_Value=5000,$

Position=[0.5,0.15,0.95, 0.95]

可以看到大多数像素值落在50~100之间。将像素分布至整个颜色范围内,使得每种颜色值都有大致相同的像素个数,键入:

IDL>equalized=Hist_Equal(scan)

为查看新的像素分布柱状图 和 HISTOGRAM-EQUALIZED图像,键入:

IDL>Window, 1, XSize=600, YSize=250

IDL>TV, equalized

IDL>Plot, Histogram(equalized), Max_Value=5000, $

Position=[0.5, 0.15, 0.95, 0.95], /NoErase

直方图均衡化后的图像应如图35所示。

图35:一幅直方图均衡化后的图像。象素分布扩展到了整个颜色范围。

83

IDL IDL入门教程

第四章 图形显示技术

本章概要

在学会怎么显示线画图、曲面图和等值线图后,就可以用自己的想象力和创造力来显示数据了。(www.loach.net.cn)本章给出了许多特殊的可视化技术以增强数据显示能力。没有打算描述IDL中每一种可能的技术。本章将介绍一些更普遍的技术。本章的目的是为读者提供工具和概念,以便帮助读者创造自己独特的数据显示。

将学到:

1. IDL如何运用颜色

2. 怎样在IDL中创建和保存颜色谱表

3. 怎样按规范修改坐标轴的注记

4. 怎样用IDL处理坏的或残缺的数据

5. 怎样建立三维坐标系并在里面显示数据

6. 怎样组合简单图形显示

7. 怎样用动画显示图形

8. 怎样将XYZ数据格网化以便图形显示

IDL的颜色运用

IDL的颜色由三种特殊值组成。称这些数值为一个三色组,将其写成(R,G,B)即红、绿、蓝,其中红、绿、蓝代表红光、绿光、蓝光作用于该显示颜色时的数量,每个值的范围都在0到255之间。这样,一种颜色可由256级的红色,256级绿色和256级蓝色组成。这就是说IDL能显示256*256*256,或者说超过167,000,000种颜色。举例来说,黄色由亮红和亮绿组成,但没有蓝色。代表黄色的三色组写作(255,255,0)。

过去在IDL里常常用一种索引号通过查表来获得颜色三色值。现在,由于越来越多地使用24位图形卡,可直接表示三色值。如果使用索引,所查寻的这个表就被称作颜色转化表(简称为色谱表)。一个色谱表由三列数组成,一列代表红色值,一列代表绿色值,一列代表蓝色值。典型地,这些数列被称为矢量。当用IDL装载色谱表时,所做的就是选择正确的数值放进这些列或矢量之中。请看这个概念的图解(图40)。

扩展:idl / idl教程 / idl程序设计

使用索引颜色模式和RGB颜色模式

除了了解一个颜色号代表一种颜色的三色值和色谱表被用来决定三色值之外,必须意识到在IDL里有两种颜色模式。索引颜色模式用于8位显示器,RGB颜色模式用于24位显示器。(IDL在PC机和Macintosh计算机上同时使用了一种修改过的RGB颜色模式,这两种计算机支持16位颜色)。

两种模式都能用一个颜色转换表来决定用于显示的特定颜色。(当颜色分解器关闭时,RGB颜色模式就用颜色转换表。否则,RGB颜色模式就用三色值直接指定颜色)。索引颜色模式也将索引颜色号和色谱表中的特定位置联系起来,而RGB颜色模式直接指定颜色。被链接到特定色谱表某个位置的颜色被称作动态颜色显示。直接显示的颜色常被称为静态颜色显示。在大多数情况 84

IDL IDL入门教程

下(有例外),8位显示是动态显示,24位显示是静态显示。[www.loach.net.cn)

动态和静态颜色显示间最重要的区别就在于,如果用动态显示并且改变装在色谱表中特定位置的数字,索引指向那个位置的像素就会立即改变颜色。而用静态显示时,像素的颜色直接被确定,在某种程度上是永久不变的。它们不会受色谱表值中后来改变的影响。(这并不总是绝对的,阅读下面关于直接颜色视觉级的讨论。)

当这个问题好象很奇怪时,它真的很有价值。它意味着采用RGB颜色模式的系统能同时显示所有的167,000,000种颜色,而采用索引颜色模式的系统只能同时显示167,000,000色调色

板中的256种颜色。

图40:8位像素值的索引颜色模式。像素值作为索引号输入到色谱表。在色谱表中找到的红色、

绿色、蓝色值决定了与此像素值相关的或由此像素值所索引的特定三色值。

通过使用Device命令中的新关键字,可以知道正在使用的颜色模式的类型,这些新关键字是在IDL5.1中新增加的。这些关键字是Get_Visual_Depth和Get_Visual_Name。这些都是将值返回给指定IDL变量的输出关键字,键入:

IDL> Device, Get_Visual_Name=thisName, $

Get_Visual_Depth=thisDepth

IDL> Print, thisName, thisDepth

TrueColor 24

视觉名称通常为伪彩色,直接颜色或真彩色。视觉深度通常为8,16或24,它是指用于决定这个视觉级类中某个颜色的位数。

8位伪彩色视觉级表明正在使用索引颜色模式和动态颜色显示。24位真彩色或直接颜色视觉级表明正使用RGB颜色模式。直接颜色视觉级有时可能是动态颜色显示,但这是窗口管理的功能,且偶尔可由用户配置。直接色视觉级色通常用静态颜色显示。真彩色视觉级总是使用静态颜色显 85

IDL IDL入门教程

示。[www.loach.net.cn)(直到IDL5.1这个属性才保持了跨平台运行的一致性。)

静态与动态颜色视觉

伪彩色视觉级是一种动态颜色视觉。这意味着如果改变色查询表的某种颜色,显示器上任何使用该颜色索引号的像素都会立即改变颜色。一般来说,装载一种新色谱表将立即改变显示器上所有图形的颜色。

真彩色视觉级是一种静态颜色视觉。这意味着改变色谱表里的一种特定颜色决不会影响已经在显示设备上的图形或颜色,因为那些颜色是直接用RGB三色值表达的。

直接颜色视觉级最难论述。直接颜色视觉级仅用于UNIX系统的机器上。每个机器制造商对直接颜色视觉级的含义都有不同的看法。但在理论上直接颜色视觉级应该集两者的优点于一身:即24位颜色系统表现得仿佛是一个动态颜色视觉。在实践中,很少看到它运行得很好。最普遍的问题是直接颜色视觉级通常提供私有的颜色图,要求将图形窗口作为当前窗口,并在窗口中装载正确的颜色。当完成时,其它窗口会消失。这就是常见的“颜色闪烁问题”,它是X窗口管理器处理色谱表的方式造成的结果。近来硬软件的发展已消除了许多这样的问题,但它们仍常常会被碰到。通常,可用8位伪彩色视觉级或24位真彩色视觉级,因为这样能保证在多个平台上正确地工作。

当IDL启动时,其所用的视觉级正常情况下是按缺省值给定的,即可以从.Xdefaults文件中获取信息(当IDL运行在UNIX机器上),也可以按IDL的一般规则来给定视觉级。(这种规则要求IDL查询硬件支持何种视觉级,并指定可获得的“最高”视觉级和视觉深度)。这个指定的缺省值可在IDL中指定视觉级和视觉深度来替换。(IDL的微机版本是通过装在机器上的图形卡及其配置来给定这些参数的,这不能从IDL内部改变。)指定值必须在图形窗口打开前确定,并可作用于IDL运行期间。

下面是基于UNIX系统机器上的典型的视觉级赋值语句:

IDL> Device, PsuedoColor=8

IDL> Device, TrueColor=24

在8位显示器上指定颜色

如果正在使用索引颜色模式,可指定一种特定的颜色做为索引号进入色谱表。IDL在表里寻找此颜色索引号,并在色谱表内找出红、绿、蓝色列中的值作为确定该颜色的三色值。例如,假设将代表黄色的三色值(255,255,0)装载到色谱表的第10项。可用TvLCT命令来实现:

IDL>TvLCT, 255, 255, 0, 10

如果想用黄色画图,可以用Color关键字指定颜色索引号,如下:

IDL> data = LoadData(1)

IDL> Plot, data, Color=10

类似地,任何值(即索引)为10的图像像素都将用同样的黄颜色显示。

如果正在使用索引颜色模式,可以简单地装载新的三色值进入色谱表的第10入口,就可容易地更改图形颜色。例如,可以像这样装载绿色:

IDL> TvLCT, 0, 255, 0, 10

显示图形的颜色立即改变了,因为此颜色已被索引到色谱表。

86

IDL IDL入门教程

在24位显示器上指定分解后的颜色

如果用24位显示器,情况稍微复杂一些。[www.loach.net.cn)当IDL用RGB颜色模式时,在缺省值情况下,采用“分解后的”颜色。这就是说IDL不是将颜色索引作为单独的索引号传入色谱表,而是试图将索引分解为三个单独的索引号传入色谱表。它这样处理是假设该索引为一个24位长整型数。IDL使用最低的8位数作为红色索引,中间的8位作为绿色索引,最高的8位作为蓝色索引。那么上面命令里的数字10被看做在红色矢量的第10项,但在绿色矢量和蓝色矢量中却是第0项。将在图41里看到分解索引值的图解。

扩展:idl / idl教程 / idl程序设计

图41:RGB颜色模式用24位像素值来单独指定一种颜色的RGB分量。如果颜色矢量包括从0

到255的值,所有的16,700,000种颜色都能在调色板里同时获得。

当灰色度色谱表被装载时(如图41所示),所有的16,700,000种颜色可立即被IDL存取。例如,想在24位系统上用黄色画图,可选择24位整数,其中8个最低位设为1(全红),8个中间位设为1(全绿),8个最高位设为0(无蓝色)。这时,代表黄色的数字用长整数65535表示。为了在24位颜色显示设备上画出上述图形,可键入:

IDL> Plot, data, Color=65535L

因为大多数人都不能熟练地使用24位的字节数,此数字有时被表示成十六进制符号。那么用两个数字(0-F)就足以一次设置8位(也就是,256或2^8能由两个十六进制数设置)。例如用十六进制符号表达全红和绿, 但没有(不能表达)蓝:

IDL> Plot, data, Color=’00FFFF’xL

为了在碳灰色(70,70,70)背景上,画出含有绿色(0,255,0)标题的黄色(255,255,0)图形,可使用以下十六进制符号: IDL> Plot, data, Color=’00FFFF’xL, Background=’464646’xL

87

IDL IDL入门教程

IDL> XYOutS, 0.5, 0.95, Align=0.5, /Normal, ‘Plot Title’, $

Color=’00FF00’xL

如果对24位符号或十六进制符号都不熟悉,可能会想用程序Color24来获得24位整数值。(www.loach.net.cn)这个程序在已经下载的本书配套程序中,可以将任何RGB三色值(写成三个元素的IDL矢量)转换成等值的24位整数值。例如,在24位系统上,如果想用程序Color24来画黄色图形,命令如下:

IDL> Plot, data, Color=Color24([255,255,0])

如果想写出能运行在8位或24位显示设备上的代码,代码也许类似如下:

Device, Get_Visual_Depth=thisDepth

IF thisDepth GT 256 THEN BEGIN

Plot, data, Color=Color24([255,255,0])

ENDIF ELSE BEGIN

TvLCT, 255, 255, 0, 100

Plot, data, Color=100

ENDELSE

在24位显示设备上指定没有分解过的颜色

不必用分解过的颜色通过RGB颜色模式作索引。例如,可能希望以8位显示设备上装载色谱表的方式装载色谱表,并能在8位显示设备和24位显示设备上使用同样的代码。如果关闭颜色分解功能,这是可能实现的。可通过这样的设备命令完成:

IDL> Device, Decomposed=0

在这种情况下,IDL是以处理8位颜色索引那样的方式处理像素值或颜色索引。也就是说,索引被用来存取在红、绿、蓝色谱表矢量里相同的项。这样,在24位显示设备上,用没有分解的颜色,也可用下面的命令画出黄色图形:

IDL> TvLCT, 255, 255, 0,10

IDL> Plot, data, color=10

但图形的颜色是直接表达的,这一点极端重要。它不被索引到色谱表里的入口位置,如果在第10入口处改变颜色表的值,图形颜色将完全不受影响。

IDL> TvLCT, 0, 255, 0, 10

将不得不重新用线形命令(或者,通常重新显示图形)来看新颜色生效。

IDL> Plot, data, color=10

在Windows系统中,无论颜色分解怎么设置,IDL5.1以前的版本总是在24位显示设备上显示8位图像,就象正在使用没有分解过的颜色。也就是说,8位像素值被用来索引色谱表中全部三种矢量的相同项。这种特点(PC版本里的一种长期BUG)在IDL5.1里得到改变,从而使得这种特性和其它平台保持了一致性。然而,这种改变使得写出在8位和24位环境中同样工作的8位图像显示程序时稍微困难一点。

决定颜色分解的开与关

自从IDL5.1.1开始,没法保证在24位显示设备里的颜色分解是开还是关。这意味着如果想让分解颜色是关或者是开(例如显示8位图像时想要关闭颜色分解,在显示24位图像时想打开颜色分解),必须在调用图形显示命令前设置它:

88

IDL IDL入门教程

Device, Decomposed=0

TV, image8bit

Device, Decomposed=1

TV, image24bit

一个新的Get_Decomposed关键字被引进IDL5.2的Device命令,它能告诉目前的“分解”状态。[www.loach.net.cn)

IDL> Device, Get_Decomposed=usingDecomposed

IDL> Print, usingDecomposed

注意,关于24位颜色方面,微机版的IDL5.2仍有BUG出现。如果正在使用24位显示设备,且想用正确的图像颜色显示24位图像,必须装入色谱表0或将Decomposed关键字设为1。如果将Decomposed值设为0,那么即使是24位的图像颜色也将遍历色谱表矢量。注意,通过下载的本书附带程序TvImage可以根据图像是8位(Decomposed=0)或24位(Decomposed=1)来正确地设置Decomposed值。

在24位显示设备上装载色谱表

现在知道了当在8位显示设备上使用索引颜色模式时像素颜色被直接索引到色谱表。换句话说,如果通过在IDL里装载色谱表来改变色谱表里的值,那么与那些索引联系在一起的颜色也将被改变。如果想同时分别用不同的色谱表显示几个图像,必须将可获得的色谱表索引分成不同的区,每个区装载不同的颜色(参看67页的“将图像调整到颜色表的不同部位”的有关章节)。从实际来说,在用完索引数字前,可能至多有四或五幅具有不同色谱表的图像。

24位显示设备的突出优点之一就是可以一次在显示设备上实际显示很多的图像,每一个图像都可用不同的色谱表。(记住只有关闭颜色分解后,在24位显示设备上装载色谱表才有意义。记住当IDL启动时,它的缺省值是打开的。)

如果装载不同的色谱表会怎样呢?想改变很多图像的颜色吗?可能不,因为每幅图像都是用它自己一套颜色,它们都是直接指定的。

如果有一幅图像显示在24位监视器上,用LoadCT命令或XloadCT工具改变色谱表,那么新的颜色不会生效,除非重新显示图像,并将图像像素通过色谱表重新转换成特定的直接颜色。想了解如何写代码以改变24位显示设备上的色谱表以及让图形自动重新显示,请阅读274页 “在24位显示设备上改变色谱表”的有关章节。

获得色谱表的拷贝

有两种方法取得当前色谱表中红、绿、蓝值的拷贝。一个方法是声明公共块Colors,既可在想获得色谱表的IDL主程序中声明,也可在任何IDL程序或函数里声明。调用过程如下: COMMON Colors, r_orig, g_orig, b_orig, r_cur, g_cur, b_cur

扩展:idl / idl教程 / idl程序设计

注意,色谱表必须在IDL的运行时装载,以便定义公共块中的变量。

约定从前面的三个变量中获得当前色谱表的颜色。如果想修改这些颜色,可将修改后的颜色矢量放进最后三个变量。装载色谱表用TvLCT命令。如果想反转色谱表里的颜色,可键入:

IDL> COMMON Colors, r, g, b, rr, gg, bb

89

IDL IDL入门教程

IDL> rr = Reverse (r)

IDL> gg = Reverse (g)

IDL> bb = Reverse (b)

IDL> TvLCT, rr,gg, bb

另一个获得色谱表值的方法是用带Get关键字的TvLCT命令:

IDL> TvLCT, red, green, blue, /Get

在这个例子中,变量red, green和blue为输出变量,被赋予了色谱表中相应的值。(www.loach.net.cn]注意这些变量拥有与正在使用的IDL的颜色数目一样多的元素。通过第一个打开的IDL图形窗口,可以确定正在使用的颜色数,键入:

IDL> Print, !D.Table_Size

注意,Windows版的IDL在24位显示设备里面,这个数字可能不精确,因为它将取决于颜色分解是开还是关。详细信息参考89页的“决定颜色分解的开与关”。

修改和创建色谱表

使用色谱表颜色有两个基本命令:XloadCT和Xpalette。通过这两个命令,应能使色谱表颜色调整为所需要的。XloadCT允许用不同的方法扩展颜色。(进入Function模式并点击Add Control Point按钮几次。用鼠标移动控制点来看如何影响色谱表。)它也允许以交互方式进行Gamma矫正。(一的Gamma值是一个线性斜坡函数。小于或大于一的Gamma值是不同形状和陡峭的指数斜坡函数。)

Xpalette命令允许通过设置滚动条的端点色和插入干涉值来修改和创造自己的色谱表。单个颜色也可在这个程序里被修改。注意是在颜色系统而非RGB颜色系统里用Xpalette指定颜色。然而最后,无论是用什么颜色系统指定颜色,被装载进色谱表中的是红、绿、蓝色矢量。

创建自己的色谱表也很容易。下面就是一个能创造两种端点色之间的任意颜色数的色谱表,名为Make_CT的简单小程序(不带错误检查!)打开文本编辑器并键入:

FUNCTION MAKE_CT, begColor, endColor, ncolors

ScaleFactor = FindGen(ncolors) / (ncolors – 1)

Colors = BytArr(ncolors, 3)

FOR j=0,2 DO colors[*,j] = begColor[j] + (endColor [j] $

- begColor [j]) * scaleFactor

RETURN, colors

END

编辑此程序须键入:

IDL> .Compile make_ct

打开World Elevation Data图像,并显示在窗口中:

IDL> image = LoadData(7)

IDL> Window, Xsize=360, Ysize=360

IDL> LoadCT, 0

IDL> TVScl, image

假如想要从黄色(255,255,0)到蓝色(0,0,255)的色谱表。可以用程序Make.CT创造并如下装载:

IDL> yellow = [255, 255, 0]

IDL> blue = [0, 0, 255]

IDL> TvLCT, Make_CT(yellow, blue, !D.Table_Size)

90

IDL IDL入门教程

假如想用从黄到绿到蓝的150种颜色来显示此图。(www.loach.net.cn)可以这样做:

IDL> scaledImage = BytScl(image, Top=149)

IDL> green = [0, 255, 0]

IDL> TvLCT, Make_CT(yellow, green, 75)

IDL> TVLCT, Make_CT(green, blue, 75), 75

IDL> TV, scaledImage

注意在选择颜色的过程中可能产生很多混乱数据。要当心。可能想看看Bernice E. Rogowitz和Lloyd A. Treinish在Computers In Physics, 10(3):268,1996上所著的名为 “How Not to Lie with Visualization”的论文。如果真对颜色和数据显示感兴趣,读阅Edward Tufte的The Visual Display of Quantitative Information and Envisioning Information.这些书正好可能改变读者编写IDL程序的方法!

保存自己的色谱表

假如对刚刚创建的色谱表很满意并想保存它。可以用TvLCT命令得到颜色值:

IDL> TvLCT, r, g, b, /Get

查看矢量有多长可键入:

IDL> Help, r, g, b

可以看到它们和正在IDL里应用的颜色数目一样长。但上述色谱表的颜色只是在前150个值里。可用颜色值的数目重新限定矢量:

IDL> r = r(0:149)

IDL> g =g(0:149)

IDL> b = b(0:149)

现在如果愿意可以保存矢量,但大多数色谱表矢量在长度上有256个元素。很容易使这些矢量达到该长度:

IDL> r = Congrid(r, 256, /Interp)

IDL> g = Congrid(g, 256, /Interp)

IDL> b = Congrid(b, 256, /Interp)

如果愿意,可以将这些矢量写进文件,但用IDL的保存命令将它们保存在IDL的保存文件中则更容易:

IDL> Save, File=’mycolors.sav’, r, g, b

为了证明是否保存了这些矢量,可装载另一个色谱表并消除这三个变量:

IDL> LoadCT, 0

IDL> DelVar, r, g, b

当准备用这些矢量时,用Restore命令重新恢复它们。注意它们以保存时同样的变量名返回。如果在IDL定义了的同名变量(象在此处做的),此变量将被覆盖。这意味着需要给出一个仔细琢磨过的输出变量名:

IDL> Restore, ‘mycolors.sav’

IDL> Help, r, g, b

使用这些变量时,必须将这些变量重置为IDL运行时的颜色数目。命令如下:

IDL> r = Congrid(r, !D.Table_Size)

IDL> g = Congrid(g, !D.Table_Size)

IDL> b = Congrid(b, !D.Table_Size)

IDL> TvLCT, r, g, b

91

IDL IDL入门教程

与其每次想调入所保存的一个色谱表时都要键入这些命令,不如写个小程序来自动调用更容易。[www.loach.net.cn)如果总是用变量名r, g和b保存RGB矢量,可以编写名为CT_Load的程序。(在这个小程序里没有错误检查!)打开文本编辑器并键入:

PRO CT_Load, filename

IF N_Params( ) EQ 0 THEN filename = ‘mycolors.sav’

Restore, filename

r = Congrid(r, !D.Table_Size)

g = Congrid(g, !D.Table_Size)

b = Congrid(b, !D.Table_Size)

扩展:idl / idl教程 / idl程序设计

TvLCT, r, g, b

END

保存ct_load.pro文件并编译它:

IDL>.Compile ct_load

现在无论什么时候想调用这个色谱表,只须键入:

IDL> CT_Load

创建自己的轴标注

IDL缺省的基本轴标注将不足以满足显示要求。幸运地是IDL提供了许多方法来增加轴的基本注释特性。本节将讲述一些创造更复杂的轴标注的一些技巧。

调整轴刻度间隔

有时IDL内部的轴标注算法不会用最有利于数据方式来划分的。可以用[XYZ]Ticks关键字来控制主刻度间隔的数目。装入随本书附带的数据集Time Series Data。键入以下命令:

IDL> curve = LoadData(1)

IDL> LoadCT, 5

IDL> Plot, curve

注意X轴被划分为五个主刻度间隔。可以修改为十个主刻度间隔,键入:

IDL> Plot, curve, Xticks=10

输出结果见图42.

注意小刻度也随之增加导致轴上刻度有点凌乱。既然对这样精细间隔的轴刻度不感兴趣,可以改变小刻度的数目。可用[XYZ]Minor关键字设置小刻度的数目。例如,可以只在两个主刻度之间设一个小刻度。可能想将Xminor关键字设为1,以便得到每个主间隔之间有一个小刻度,但这是不正确的。如果Xminor关键字设为1,所有的小刻度都会消失。为了得到想要的小刻度的数目,须将Xminor关键字设为比想要的数目大1。键入:

IDL> Plot, curve, Xticks=10, Xminor=2

92

IDL IDL入门教程

图42:主刻度间隔的数目随Xticks关键字改变

格式化轴的标注

影响轴标注的另一个方法是改变轴的标注格式。(www.loach.net.cn)例如,X轴标现在被表示为整数。可能想用三位数的整数来表示。可以通过将XtickFormat关键字设定为想要的特定格式来达到这个目的。例如,可以键入:

IDL> Plot, curve, XtickFormat=’(I3.3)’

用小数点后面带两位小数的浮点值来写标注,键入:

IDL> Plot, curve, XtickFormat=’(F6.2)’

也可以用特殊字符串作为刻度。这可以用TickName关键字完成,最多可达30个字符串元素。例如,可以用星期几标识图形:

IDL> labels = [‘MON’,’TUE’,’WED’,’THU’,’FRI’,’SAT’]

IDL> Plot, curve, XTickName=labels

输出结果如图43所示。

通过将轴刻度格式设置为(A1),可隐藏轴标注:

IDL> Plot, curve, XtickFormat=’(A1)’

编写刻度格式函数

另一个格式化刻度的方法是编写一个函数,用所想要的格式来格式化刻度。如果传给

[XYZ]TickFormat关键字的参数是函数名,那么IDL将注释标注时将调用那个函数。

93

IDL IDL入门教程

例如,假设在这个图形上想要的X轴标是日期,写作25 MAR 97。[www.loach.net.cn]可以编写名为Date的函数来完成格式化。这个函数必须定义三个且只能有三个定位参数。它们是轴参数,索引数,和标注值。当需要格式化轴标注时,IDL会用这三个定位参数来调用该函数。函数的返回值必须是字符串变量。

图43:可以通过[XYZ]TickNames关键字用字符串标识轴。

轴参数为0,1或2分别表示X、Y或Z轴。索引数是特定轴标的个数。这个参数程序员很少在函数里用到。标注值是用于轴标的常规值。工作就是用标注值来计算或格式化该函数返回的新值。就是这个返回值用来为特定轴索引数标注轴的。

通过一个例子可更容易了解如何编写这个程序。打开文本编辑器键入这个简短的Date程序。

FUNCTION DATE, axis, index, value

MonthStr = [‘Jan’, ’Feb’, ’Mar’, ’Apr’, ’May’, ’Jan’, ’Jul’, $

‘Aug’, ‘Sep’, ‘Oct’, ‘Nov’, ‘Dec’]

CalDat, LONG(value), month, day, year

Year = StrMid(StrTrim(year,2), 2, 2)

RETURN, StrTrim(day, 2) + ‘ ‘ + monthStr (month-1) + ‘ ‘ $

+ year

END

编译程序Date,以便以下的代码用来格式化X轴刻度标识:

IDL> .Compile date

注意这个程序的CalDat命令。此程序接受代表某个日期的Julian数值,并返回与此Julian数值相关的正确的日,月,年数。这个信息可被用来正确地格式化标识。为了解它是如何工作的,可键入:

IDL> Window, XSize=500, YSize=350

IDL> startDate = Julday (1, 1, 1991)

94

IDL IDL入门教程

IDL> endDate = Julday(6, 23, 1995)

IDL> numTicks = 5

IDL> sizeCurve = N_Elements(curve)

IDL> steps = Findgen(sizeCurve) / (sizeCurve-1)

IDL> dates = startDate+ (endDate+1 – startDate) * steps

IDL> !P.Charsize = 0.8

IDL> Plot, dates, curve, XtickFormat=’Date’, $

Xstyle=1, Xticks=numTicks, $

Position= [0.15, 0.15, 0.85, 0.95]

输出结果见图44。(www.loach.net.cn)若想更多地了解用日期标识轴的情况,可参考IDL库函数Label_Date。此函数功能很象刚刚编写的程序Date的功能。

图44:通过用户编写的函数格式化刻度。

正如所见,这些标识很长,且有挤在一起的危险。可能想用另外的方法显示日期。例如,可能想将它们相对轴旋转45度。不幸的是,刚刚编写的刻度格式化函数不能奏效,将不得不借助更有效的方法,用XYOutS命令放置标识。

然而,仍可以用程序Date来格式化字符串。为完成这项工作,必须画一幅X轴不带标注的图,并需要一个有正确刻度值的矢量。可以通过将轴的刻度格式设为(A1)来隐藏轴标注。可用Xtick_Get关键字以矢量的形式得到刻度值。例如图形可以这样画(Position关键字用来给轴标留下空间):

IDL> Plot, dates, curve, XtickFormat=’(A1)’, Xstyle=1, $

Xticks=numTicks, Xtick_Get=tickValues, $

Position= [0.1, 0.2, 0.85, 0.95]

扩展:idl / idl教程 / idl程序设计

然后,用XYOutS命令将标注添上。可以用![XY].Window系统变量来找出X和Y轴端点的归一化坐标。这个信息对于正确定位标注是非常关键的。代码如下:

IDL> ypos = Replicate (!Y

.Window[0] – 0.04, numticks+1)

95

IDL IDL入门教程

IDL> xpos = !X.Window[0] + (!X.Window(1) - !X.Window[0] )* $

FindGen (numTicks+1) /numTicks

IDL> FOR j=0,numTicks DO XYOutS, xpos(j), ypos(j), $

Date (0, j, tickValues [j]), Alignment=0.0, $

Orientation= - 45, /Normal

输出结果如图45所示。(www.loach.net.cn]

图45:用XYOutS命令创建旋转的轴标。

用IDL处理残缺的数据

不幸的是,数据并不总是来源于性能良好的采集仪器。将原始数据处理为可用的形式通常是必要的。事实上,很多原始数据组是不完整的。许多事情都可能发生。例如,数据采集仪器关闭一小段时间;电流导致伪数据;操作者错误地操作;等等。如何用IDL来处理这种残缺的或坏的数据呢?

处理这种数据的一种方法是赋给它一个NaN值。NaN只是一种特殊的位模式,在每种机器结构上都是不同的。运行IDL的机器的位模式保存在系统变量!Value的F_NaN字段中。 看看它如何被使用,可用LoadData命令打开Elevation Data数据组:

IDL> data = LoadData (2)

IDL> !P.CharSize = 1.0

这个数据组是41*41的浮点数组。假设数据是不完整的。假设在采集数据中,在扫描此二维数据组的中间三行时,采集仪器暂时关闭。想将NaN值赋给这三行扫描数据,可键入:

IDL> badData = data

IDL> badData (*, 30:32) = !Values.F_NaN

现在,当显示曲面图时,IDL不将那些是NaN的值联接起来,键入:

IDL> Surface, badData

96

IDL IDL入门教程

输出结果如图46所示。(www.loach.net.cn)

图46:在此曲面图里,残缺的数据用NaN代替。

除了设置NaN位模式外,还有另一个方法可以处理残缺的或坏的数据。这就是用大多数IDL图形输出命令都有的关键字Min_Value和Max_Value。通过设置这些关键字,任何小于最小值或大于最大值的都可以被图形输出命令忽略。例如,在以上所用到的高程数据集里,可以只画特定范围里的那些等值线。以下是一些显示它如何工作的命令。这儿值在小于等于400和大于等于1000之间的等值线没有在图中画出:

IDL> Window, XSize=500, YSize=375

IDL> !P.Multi = [0, 2, 1, 0, 1]

IDL> Values = FindGen (10)*150 + 100

IDL> label = Replicate (1,10)

IDL> Contour, data, Levels=values, /Follow, C_Labels=label

IDL> Contour, data, levels=values, /Follow, C_Labels=label, $

Min_Value=400, Max_Value=1000

IDL> !P.Multi = 0

图形窗口的右边部分输出结果如图47所示。

97

IDL IDL入门教程

图47:等值线可用Contour命令的Min_Value和Max_Value关键字消除。[www.loach.net.cn)

用IDL建立三维坐标系

IDL用变换矩阵乘上三维空间的每个点,从而在二维显示上模拟三维坐标系。如果有这种变换矩阵,那么它将会存储在系统变量!P.T里。如果想用IDL在三维空间里画图,必须首先将正确的变换矩阵装入!P.T系统变量。接着必须确保在图形输出到显示设备之前,图形输出命令已经被该矩阵乘过。在实践中这是很容易做到的。

将三维变换矩阵装入!P.T系统变量有几个方法。如果想严格控制矩阵的建立,可以用T3D命令来建立想要的三维坐标系。但除非正在做某件复杂的或超出常规的事情,否则不会愿意使用T3D命令。可用以下两个方法之一来装载三维变换矩阵:(1)如果想在三维空间中显示坐标轴,可用带Save关键字的Surface命令;(2)如果仅仅希望创建三维空间,而不关心坐标轴的显示问题,可以用Scale3命令。

建立三维散点图

假设有随机分布的三维数据,并想将它在三维空间里显示为散点图。也可以通过计算机获得随机分布的三维数据,键入:

IDL> seed = 3L

IDL> x = RandomU (seed, 41)

IDL> y = RandomU (seed, 41)

IDL> z = Exp (- 4 * ((x – 0.5)^2 + (y – 0.5)^2))

看看随机分布的数据,键入:

IDL> Window, XSize=400, YSize=350

98

IDL IDL入门教程

IDL> plot, x, y, pSym=4, SymSize=2.0

在这种情况下,需要有一套坐标轴来定义三维空间。(www.loach.net.cn]对于建立三维变换矩阵来说,用带Save关键字的Surface命令将是一个好的选择。Save关键字将为Surface命令创造的三维变换矩阵保存在!P.T系统变量里,而不是丢掉。可以用常用的轴旋转关键字来取得想要的三维空间。NoData关键字只显示坐标轴。建立三维空间所必需的[XYZ]Range关键字反映了正确的真实数据范围,而不是Surface命令中的伪值范围。键入:

IDL> Surface, Dist (10), /Save, /NoData, CharSize=1.5, $

XRange=[0,1], YRange=[0,1], ZRange=[0,1]

上述命令建立了常规的三个轴。也可以增添附加轴。例如,需要突出XY平面。可以用Axis命令增添附加的X和Y轴:

IDL> Axis, YAxis=1, 1.0, 0.0, 0.0, /T3D, CharSize=1.5

IDL> Axis, Xaxis=1, 0.0, 1.0, 0.0, /T3D, CharSize=1.5

为了在这个三维空间内画点,每个三维点都必须和变换矩阵相乘。可以通过在图形输出命令中设置T3D关键字来实现此项功能。在这种情况下,可以用PlotS命令:

IDL> Plots, x, y, z, Psym=4, SymSize=2.0, /T3D

扩展:idl / idl教程 / idl程序设计

为了使图形的立体感更强,可以使用一条线将每个点连接到XY平面上。事实上,还可以根据Z值给线条赋色,从而提供给用户更多的信息。可以键入:

IDL> zcolors = BytScl (z, Top=99) + 1B

IDL> LoadCT, 22, Ncolors=100, Bottom=1

IDL> FOR j=0,40 DO Plots, [x[j], x[j]], [y[j], y[j]], $

[z[j], 0], Color=zcolors[j], /T3D

输出结果如图48所示。

图48:用Surface命令在三维空间里建立的散点图。

若无色棒指示颜色的含意,那么带有颜色的图形显示可以被认为是不完整的。可以用本书中 99

IDL IDL入门教程

的程序Colorbar来给图形添加颜色。(www.loach.net.cn]键入:

IDL> Colorbar, Position=[0.25, 0.9, 0.85, 0.95], $

Range=[Min(z), Max(z)], Ncolors=100, Bottom=1, $

Color=255, Title=’Z Values’

从图形原点定位3D坐标轴

这是另一个建立三维坐标系的例子。在这个例子中,有一批跨越原点的数据,希望定义数据的轴线能通过原点。因为不想画属于此数据的轴线,这时或许可用Scale3命令去建立三维空间。Scale3命令与Surface命令使用相同的旋转矩阵,但前者在缺省情况下不画轴线,而且将变换矩阵装入!P.T系统变量中。

为此例子可以生成一批数据,可用LoadData命令装载41* 41的 Elevation Data 数据集,如下所示:

IDL> data = LoadData (2)

IDL> data = data – (Max(data) / 2.0 )

IDL> x = FIndGen(41) – 20.0

IDL> y = FindGen(41)*2.0 – 41.0

用Scale3命令建立三维空间。为了使输出图形尽可能地大一些,可以关闭图形边缘。在此之前,将当前设置的系统变量保存,以便以后的恢复。键入:

IDL> Window, Xsize=500, Ysize=350

IDL> Save, /System_Variables, File=’system.sav’

IDL> !X.Margin = 0 & !Y.Margin =0 & !Z.Margin = 0

IDL> Scale3, Xrange=[Min(x), Max(x)], Ax=45, Az=45, Yrange=[Min(y), Max(y)], $

Zrange=[0, Max(data)]

现在可绘制数据的曲面图。务必用带有关键字[XYZ]Style的Surface 命令关闭坐标轴的显示,并强行要求Surface命令使用刚建立的3D变换矩阵,而不是Surface自己生成该矩阵。键入: IDL> Surface, data, x, y, /T3D, Xstyle=4, Ystyle=4, Zstyle=4

然后画过原点的坐标轴,键入:

IDL> TvLCT, 255, 255, 0, 1

IDL> Axis, 0, 0, 0, Color=1, /T3D, Charsize=2, Xaxis=1

IDL> Axis, 0, 0, 0, Color=1, /T3D, Charsize=2, Yaxis=1

IDL> Axis, 0, 0, 0, Color=1, /T3D, Charsize=2, Zaxis=1

输出图形如图49所示。

100

IDL IDL入门教程

图49:坐标轴穿过原点的曲面图。(www.loach.net.cn)三维空间用Scale3命令创建。

注意,在这幅图中,Y轴没有延伸至图形的边缘。这是因图形的旋转而产生的错觉。能说服自己这真的是错觉吗?

在进入下一节之前,一定要将系统变量恢复为初始值。键入:

IDL>Restore, ‘system.sav’

组合简单图形显示

利用所掌握的图形定位和创建三维坐标系的知识,可以很容易地用不同的方法组合图形显示。例如:通常将同一数据的图像和等值线图形显示为一幅图形是很有趣的。为说明做这是如何的容易,打开512*512的 Brain X-Ray数据集,键入:

IDL> brain = LoadData(9)

用下载的TVImage 命令可以让此数据显示在当前图形窗口80%的范围内。但是记住,如果当前图形窗口不是正方形的,图像可能会产生变形。为了保证图像的横纵比例,在将图像缩放到大约80%的图形窗口范围内时,设置关键字Keep_Aspect_Ratio,如下:

IDL> Window, Xsize=450, Ysize=350

IDL> LoadCT, 3

IDL> thisPosition =[ 0.2, 0.2, 0.8, 0.8]

IDL> TVImage, brain, position=thisposition, Keep_Aspect_Ratio=1

当TVImage命令设置了关键字 Keep_Aspect_Ratio, 关键字Position 就变成了一个输出参数。换句话说,变量thisPosition保存了图像在窗口中的规一化定位坐标。这些坐标不同于输入的坐标,因为为保证图像横纵比例不发生变化,定位坐标不得不改变。通过打印thisPosition变量,能看到新的定位坐标。

IDL> print, thisPosition

可以用这些新的定位坐标,正好在已存在于显示窗口内的图像上画一幅等值线图。务必用关 101

IDL IDL入门教程

键字NoErase, 以防止将等值线图抹去已存在的图形。[www.loach.net.cn)关键字XStyle和Ystyle对避免轴线比例尺的自动调整是必要的。键入:

IDL> Contour, brain, Xstyle=1, Ystyle=1, Nlevels=8, Position=thisPosition, /NoErase 输出结果如图50中的图片所示。

表50:在IDL中很容易将图像与等值线图进行组合

曲面图与等值线图的组合也是很简单的。用LoadData命令打开41 * 41 Elevation Data数组: IDL> peak = LoadData(2)

也许希望在看到等值线图的同时,也能够看到由该数据产生的阴影曲面图。使用

Shade_Surface命令,在IDL中可以非常轻松地创建三维坐标系,这个坐标系可用来定位等值线图。输入如下代码:

IDL> Window , Xsize=400, Ysize=400

IDL> TVLct, [70, 0], [70, 255], [70, 0], 0

IDL> LoadCT, 5, Ncolors=!D.Table_Size-2, Bottom=2

IDL> !P.Charsize=1.5

IDL> Shade_Surf, peak, /Save, Background=0, Color=1, Shades=BytScl(peak,

Top=!D.Table_Size-2) + 2B

也许想在这个图形的右侧添加一个Z轴,键入:

IDL> Axis, Zaxis=0, 40, 0, 0, /T3D, Color=1

最后,做好了添加等值线图的准备。务必使用关键字NoErase, 以防止将已经显示的内容抹去。关键字Zvalue在Z轴上定位等值线图。赋给关键字Zvalue的值是归一化坐标。Zvalue的值为1.0时,将会把等值线图定位到已创建的三维坐标系空间的顶部。

扩展:idl / idl教程 / idl程序设计

IDL> Contour, peak, Nlevels=12, Color=1, Zvalue=1.0, /T3D, /NoErase, /Follow

注意,有时当图形位于三维坐标空间的边缘时,有些线条会被裁掉。这往往是将图形置于三维空间时,由计算时的舍入误差所导致。如果某些等值线图中的等值线看起来不完整,就将关键字NoClip加到contour命令中,如下所示:

102

IDL IDL入门教程

IDL> Contour, peak, NLevels=12, Color=1, Zvalue=1.0, /T3D, /NoErase, /Follow, /NoClip 输出结果如图51所示。[www.loach.net.cn)

图51:一个阴影曲面图与等值线图的组合

其它图形输出命令的组合一样是可能的。限制在于想象力。

IDL中的动画数据

另一种强大的可视化图形技术是数据动画。多数情况下,在动画中得到的信息,是难以或不可能通过其它途径观察数据得到的。倘若还未打开一套3D数据集,可以用LoadData命令启动80*100*57的 MRI Head 数据集,如下所示:

IDL> head = LoadData(8)

在IDL中动画是用XInterAnimate命令来完成的。这个命令实际上调用了一个作为IDL主要动画工具的组件程序。XInterAnimate必须调用三次:(1)一次用来建立动画工具,特别是用来确定动画帧缓冲区的大小;(2)一次用来装入动画工具;(3)最后一次用来播放动画序列。

在制作好3D动画数组后,设置和运行XInterAnimate就非常容易了。例如,可以在Z方向上动画显示MRI Head 数据。那就是说,动画将从头像的底部顺着Z轴移动到头像的顶部。此时,在Z方向的每一帧图像都是100*80的数组。对于一个好的动画来说这有点小,但不久就会处理好这个问题。

103

IDL IDL入门教程

建立动画工具

首先,建立动画工具。[www.loach.net.cn]在此动画中将会有57帧,每帧的规格是80*100的图像。帧缓冲区应设置为80* 100* 57。注意,使用关键字Showload,就可以观察到动画的装入过程。若不愿意,没有必要显示这些过程。键入:

IDL> XInterAnimate, Set= [80, 100, 57], /Showload

装载动画缓冲区

下一步是将数据载装到动画缓冲区。这里,将已经打开的3D图像数组装入到动画工具缓冲区中。这个命令总是存在于循环中,如下所示:

IDL> FOR j=0, 56, DO XInterAnimate, Frame=j, Image=head[*,*,J]

当执行这个命令时,可以看到每帧被装入到动画缓冲区的过程。但这不是动画。 运行动画工具

最后一次键入XInterAnimate命令,动画就开始运行,如下所示:

IDL> XInterAnimate

动画工具应该看起来如图52所示。

图52 已装入MRI Head数据的XInterAnimate动画工具。

动画的控制

可用某些动画控制来做实验。可以决定动画帧如何循环,动画的速度,甚至可在动画运行时改变色谱表。注意,倘若按了End Animation按钮,

那就得再一次输入所有的三个动画命令以使动 104

IDL IDL入门教程

画得以运行。(www.loach.net.cn]在使用滑杆指定动画帧之前,必须让动画停下来。

通常,动画以机器所允许的最快速度运行。(一个组件定时器事件负责取得序列中的下一个动画帧。)动画速度按钮使此定时器事件增加延迟。如果希望动画一开始就以低速运行,可以给最后的XInterAnimate命令赋一个速度参数。这个值可以在0到100之间。例如,要使动画以中等速度运行,可以输入:

IDL> XInterAnimate, Set=(80,100,57), /Showload

IDL> FOR j=0, 56 DO XInterAnimate, Frame=j, image=head[*,*,j]

IDL> XInterAnimate, 50

存储动画的像素映射图

动画工具运行得如此快,原因之一是它使用了像素映射图和设备拷贝技术去实现动画。(关于这种技术的详细技术参考120页的“擦除注释的‘设备拷贝’方法”。)通常当点击End Animation按钮时,这些像素映射图就被删掉了。倘若将这些像素图保存在存储器中,只须键入XInterAnimate,就可以在任何时候立即启动一个新的动画。将像素映射图保存到存贮器中的方法是当启动动画时,使用关键字Keep_Pixmaps,如下所示:

IDL> XInterAnimate, Set=(80,100,57), /Showload

IDL> FOR j=0, 56 DO XInterAnimate, Frame=j, image=head[*, *, j]

IDL> XInterAnimate, 50, /Keep_Pixmaps

可以退出动画程序,然后再一次启动,输入:

IDL>XInterAnimate

当完成任务后,确保已将像素映射图删除,输入关键字Close即可完成,如下所示:

DL>XInterAnimate , /close

其它类型图形数据的动画

三维数数据并不是唯一一种能够在动画工具中制作成动画的数据。事实上,多数情况下,想制作动画的内容就是输入到IDL图形窗口中的内容。XInterAnimate工具能够快速拍下IDL的图形窗口,然后将它们作为动画帧存储到动画缓冲区中。完成此项任务可用关键字Window, 而不是Image。

下面可以看到这一步是怎样通过已经打开的数据来完成。显示的80*100图像非常小,也许想使它变大一点,但又不想再创建一个更大的数组来存储数据。这样做不是有效使用IDL存储器的方法。然而,可以在每个图像被显示时,用重置的方法使它们变大。这不会占用额外的IDL 存储空间。

假设要求每个动画帧的尺寸是240*300。创建如下的动画代码,严格按下面输入代码。倘若在FOR循环中出现输入错误,从循环的起点再次输入:

IDL> XInterAnimate, Set=[240, 300, 57], /Showload

IDL> FOR j=0,56 DO BEGIN $

TV, Rebin(head[*, *], 240, 300) $

& XInterAnimate, Frame=j ,Window=!D.Window & ENDFOR

IDL> XInterAnimate, 50

可以在此循环中输入任何IDL图形命令。这给了读者相当大的灵活性,让读者能够对各种各 105

扩展:idl / idl教程 / idl程序设计

IDL IDL入门教程

样的数据进行动画处理。[www.loach.net.cn]

网格化数据以便图形显示

许多IDL图形显示程序(如:Surface, Contour, Shade_Surf等)要求数据按2D网格数组排列。(通常,数据不一定要按规则网格排列。)但是,偶尔也会出现得不到这种数据的情况。例如,“点位数据”就是来自于随机定位的采集点。此类数据必须在显示之前进行网格化处理。

装载此类随机分布的XYZ数据来作练习,可用LoadData命令安装Randomly Distributed (XYZ)数据集即可。此数据集包含了3个含41个元素的矢量,这些矢量代表站点的纬度和经度坐标以及采样值。输入:

IDL> data = LoadData(14)

IDL> lon = data[0, *]

IDL> lat = data[1,*]

IDL> value= data[2,*]

可以在一个网格上画出纬度和经度矢量,就能看出它们实际上是随机分布的。输入: IDL> LoadCT, 0

IDL> TvLCT, [70, 255, 0], [70, 255, 255], [70, 0, 0], 1

IDL> Window, Xsize=400, Ysize=400

IDL> plot, lon, lat, /YnoZero, /NoData, Background=1

IDL> plots, lon, lat, PSym=5, Color=2, SymSize=1.5

输出结果如图53所示。

图53:随机分布的纬度和经度坐标

106

IDL IDL入门教程

德洛内三角形法网格化

IDL所用的网格化数据的方法叫做德洛内(Delaunay)三角法。[www.loach.net.cn]这种算法并非是对数据进行网格化的最好方法。但是作为一种可接受的网格化方法,它具有速度快,相对简单和被人们广泛承认的优点。它需要从X和Y坐标上建立一组德洛内三角形,即任意一个三角形的边界范围内都不含其它三角形的顶点。德洛内法就是用这些三角形顶点上的值插出规则网格点的值。

Triangulate命令用来建立德洛内三角形组。此命令的输入参数是离散点的XY坐标。此命令形式如下,其中angles是输出变量,保存了计算返回的德洛内三角形组。

IDL> Triangulate, lon, lat, angles

注意,也可以用Triangulate命令返回这些点的凸形外络线。只须在上述命令中增加第4个变量参数,这组点的凸形外络点就会被返回到此参数中。凸形外络线在许多算法操作中都是很有用的。

使这组三角形可视化(此数组中有70个三角形),输入命令:

IDL> FOR j=0,69 DO BEGIN t= [angles[*, j], angles[0, j]] $

& plots, lon[t], lat[t], Color=3 & ENDFOR

输出结果如54所示。

图54:用Triangulate命令返回的德洛内三角形组

为了网格化数据,只需将从Triangulate返回的三角形组数据传递到TriGrid程序即可。可以设置网格的端点或边界,以及网格间隔。还可以用关键字Missing来为位于三角形外部的数据指定值。此外,可用输出关键字XGrid和YGrid中获得数据网格化后的新纬度和经度矢量。例如,输入:

IDL> latMax = 50.0

IDL> latMin =

20.0

107

IDL IDL入门教程

IDL> lonMax = -70.0

IDL> lonMin = -130.

IDL> mapBounds = [lonMin, latMin, lonMax, latMax]

IDL> mapSpacing = [ 0.5, 0.25 ]

IDL> gridData = Trigrid( lon, lat, value, angles, mapSpacing, mapBounds, $

Missing=Min(value), XGrid=gridlon, YGrid=gridlat)

现在可以在需要的IDL命令中使用网格数据了。(www.loach.net.cn)

IDL> Contour, gridData, gridlon, gridlat, / Follow, $

NLevels=10, XStyle=1, YStyle=1, Background=1, color=2

输出结果如图55所示。

图55: 用TriGrid命令生成的结果显示的等值线图

注意,可用关键字Quintic 或Smooth来光滑曲面(两者是同义的)。键入:

IDL> gridData = Trigrid( lon, lat, value, angles, mapSpacing, mapBounds, $

Missing=Min(value), XGrid=gridlon, YGrid=gridlat, /Quintic)

IDL> Contour, gridData, gridlon, gridlat, / Follow, NLevels=10, XStyle=1, $

YStyle=1, Background=1, color=2

有时用关键字Extrapolate对三角形边缘外部的值进行推断,可得到更好的结果。键入: IDL> Triangulate, lon, lat, angles, hull

IDL> gridData = Trigrid( lon, lat, value, angles, mapSpacing, mapBounds, $

Missing=Min(value), Extrapolate=hull)

IDL> Contour, gridData, gridlon, gridlat, / Follow, NLevels=10, $

XStyle=1, YStyle=1, Background=1, color=2

108

IDL IDL入门教程

输出结果类似于图56所示。[www.loach.net.cn]

图56:使用关键字Extrapolation 和Smooth后的TriGrid结果图

数据的球形网格化

如果这是真实的纬度和经度数据,不会愿意在平面上对它进行网格化。地球的表面更象球形。网格化程序Triangulate和TriGrid同样允许对数据进行球形网格化。这样,生成出来的三角形就成了球形三角形。

将此数据放到地图投影上,可输入:

IDL> Map_Set, /Orthographic, /Grid, /Continents, /Label, /Isotropic, 35, -100, Color=1 IDL> Plots, lon , lat, PSym=4, Color=2

尽管这一次在命令中使用了不同的关键字,但在球形网格化中,前面的许多相同参数仍可用来数据进行网格化。确保设置了关键字Degrees,否则这些命令将用弧度来计算球形三角形。命令如下所示:

IDL> Triangulate, lon, lat, Sphere=angles, FValue=value, /Degrees

IDL> gridData = Trigrid( value,Sphere= angles, mapSpacing, mapBounds, $

Missing=Min(value), /Extrapolate, /Degrees)

扩展:idl / idl教程 / idl程序设计

IDL> Map_Set, /Orthographic, /Grid, /Continents, /Label, /Isotropic, 35, -100, Color=1 IDL> Contour, gridData, gridlon, gridlat, / Follow, NLevels=10, XStyle=1, YStyle=1,$

Color=2, /Overplot

109

IDL IDL入门教程

输出结果应与图57中的图片相似。[www.loach.net.cn]

图57:在地图投影顶部以等值线图形显示的球形栅格数据

110

IDL IDL入门教程

第五章 图形显示技巧

本章概要

在上一章节了学习了一些图形显示技术。[www.loach.net.cn]在这一章节将学习几个新的图形显示技巧,以便让图形显示具有专业的感观效果。

具体来说,将学会:

1. 怎样让鼠标交互作用于图形显示

2. 怎样从图形显示中删除注释

3. 怎样在图形显示上画“橡皮条”

4. 怎样在图形显示技巧中使用Z图形缓冲区

将光标用于图形显示

数据可视化显示的原因之一是用户可用不同的方式对数据进行交互式的操作。用户喜欢的一种对数据交互式操作的方式是使用光标去选择或者标注部分数据。这种交互作用在IDL中用Cursor命令很容易完成。

用LoadData命令装入Time Series数据集,可看到Cursor命令是如何工作的。 IDL> curve = LoadData (1)

输入下述命令,显示曲线:

IDL> Window, Xsize = 400, Ysize = 400

IDL> LoadCT, 0

IDL> TvLCT, 255,255,0,1

IDL> Plot, curve

Cursor命令接受两个参数。这些参数必须是记录鼠标键按下时光标位置的变量。Cursor命令要求光标位于当前图形窗口中。(即被!D.Window系统变量指向的窗口。)例如,如果输入这个命令,IDL将会等待光标被移动到当前图形窗口(如果输入的是上述命令,就是0号索引窗口)并单击鼠标键。当执行上述动作后,IDL将光标位置返回到变量xLocation和yLocation中。输入: IDL> Cursor, xLocation, yLocation

如果打印出这些变量的值,将发现这些值被赋予的是数据坐标空间。xLocation的数值从0到100,yLocation的数值从0到30。(如果是在图形边界内点击的鼠标,它们至少是这么多。如果不是在图形边界内点击的鼠标会怎么样?)缺省时,Cursor命令返回数据坐标位置。 IDL> Print, xLocation, yLocation

什么时候返回的光标位置?

从上面的命令看,似乎鼠标键被按下时返回光标位置,但并非总是这样。事实上,Cursor命令什么时候报告光标的位置是由Cursor命令的关键字所决定的。这些关键字是: Change 当光标位置发生改变或用户移动光标时,返回光标位置。

Down 当鼠标键被按下时,返回光标位置。

NoWait 当Cursor命令执行时,光标位置被立即返回。没有任何延迟或等待鼠标的按键。这 111

IDL IDL入门教程

个关键字有时用于当对象正在显示窗口中被移动时的循环中。(www.loach.net.cn]

UP 不是在鼠标键被按下时,而是放开或释放后返回光标位置。

Wait Cursor命令等待鼠标键被按下后返回光标的位置。只要鼠标键被按下,此关键字对Cursor命令的作用就类似于用NoWait关键字调用Cursor命令。此关键字是Cursor命令的缺省状态。

在Cursor命令中,小心使用合适的关键字,特别是在循环过程中使用Cursor命令。用户有时习惯地认为Cursor命令的缺省属性是只有鼠标键被按下时才返回光标的位置。其实不然,缺省属性只是等待一个单击动作,以后的行为就和NoWait关键字一样。在循环中这个区别是至关重要的。

哪一个鼠标键和光标共同作用呢?

除了设置光标属性外,有时还想知道哪个鼠标键用于对Cursor命令作出反应。例如,想要用鼠标左键做某件事,而做另外不同的事情要用鼠标右键Cursor命令作出的反应。可以检查系统变量!Mouse中的Button字段,来判断哪一个鼠标键在和Cursor命令共同作用。(老版本的IDL是用系统变量!Err的值来判断的。)这个字段是一个整型位映象。Button这个字段的有效值及其意义如下:

!Mouse.Button = 0 当前没有按键被使用

!Mouse.Button = 1 左键用于Cursor命令

!Mouse.Button = 2 中间键用于Cursor命令

!Mouse.Button = 4 右键用于Cursor命令

用光标标注图形输出

使用Cursor命令的一种方法是允许用户交互地在线画图上放置符号标记。例如,正确无误地输入下列命令。当输完最后一个回车键后,在当前的图形窗口上单击鼠标五次。五个符号将放置在窗口中。(如果在输入下列代码时出现打字错误,必须从头开始重新输入。)输入:

IDL> For j = 0, 4 DO BEGIN $

IDL> Cursor, xloc, yloc, /DOWN & $

IDL> Plots, xloc, yloc, Psym = 4, SymSize = 2, Color = 1 & ENDFOR

画方框

有时可能为了选取图形显示中的某部分,而在它的周围画上方框。这里有些命令可用来选择由Cursor命令产生的方框的对角,画出该方框,并将图形缩放到该方框坐标范围。首先画图: IDL> Plot, curve

接着,使用光标选择想画的方框的一角。要确保在当前图形窗口上点击光标。为确定哪个是当前窗口,并让它不被隐藏,可输入:

IDL> WShow

现在键入第一个Cursor命令。在图形轴的范围内某处点击:

IDL> Cursor, x1,y1, /DOWN; Select one corner of box.

接着输入第二个Cursor命令。在图形轴的范围内某处点击:

IDL> Cursor, x2,y2, /DOWN; Select diagonal corner of box.

112

IDL IDL入门教程

上述Cursor命令返回的坐标是数据空间坐标。(www.loach.net.cn]按如下画方框:

IDL> Plots, [x1,x1,x2,x2,x1],[y1,y2,y2,y1,y1], color = 1

输出结果应类似于图58中所示,尽管实际的图形上方框取决于在窗口中点击的位置。

为了放大这部分图形,必须保证方框坐标的正确顺序。这是非常必要的,因为可能先选择的是方框的右下角,然后是左上角,这样x1将大于x2。还可以想象其它的假设。为了适应所有的

扩展:idl / idl教程 / idl程序设计

情况,键入:

图58:在部分数据周围画上方框的线画图。用Cursor命令选择方框的坐标,用PlotS命令画方

框。

IDL> Xmin = Min([x1,x2], Max = xmax)

IDL> Ymin = Min([y1,y2], Max = ymax)

最后,已经为放大对方框内的数据做好了准备。除了正确地设置数据范围外,还必须设置<XY>Style关键字。知道为什么吗?如果不知道,可在不使用这两个关键字的情况下试试下面的命令。将会发生什么呢?

IDL> Plot, curve, XRange = [xmin, xmax], Yrange = [ymin, ymax], $

Xstyle = 1, Ystyle = 1

在图像上使用Cursor命令

通常当在处理图像数据时使用Cursor命令,希望用设备坐标而不是数据坐标返回光标位置。这是因为设备坐标和图像中对应的位置之间通常存在一种简单的关系(大多数是一对一的关系)。为了解如何工作的,可用LoadData命令打开360*360的World Elevation数据集,键入: IDL> image = LoadData(7)

显示图像,并装入某些颜色。如下:

IDL> topColor = !D.Table_Size-1

IDL> LoadCT, 3, Ncolors = !D.Table_Size-1

113

IDL IDL入门教程

IDL>TvLCT, 255,255,0, TopColor

IDL> Window, XSize = 360, YSize = 360

IDL> TV, BytScl (image, Top = !D.Table_Size-2)

利用光标在图像中选择某一特定行和列。[www.loach.net.cn]注意Cursor和PlotS命令中的Device关键字。这是确保返回的坐标是设备坐标而不是数据坐标。在该位置上画一个十字线。(确保在输入Cursor命令后,在图像窗口中点击一下。)键入:

IDL> S = Size(image)

IDL> Cursor, col, row, / Device; Click in the window!

IDL> Plots, [col, col], [0,s (2) ], / Device, Color = topColor

IDL> Plots, [0,s (1) ], [row, row ], / Device, Color = topColor

注意,在图像中某一特定的行和列上获得图像数据是多么的容易。例如,可以轻易地绘制出图像中行和列的数据剖面,键入:

IDL> Window, 1, Xsize = 500, Ysize = 300

IDL> !P.Multi = [0, 2, 1]

IDL> Plot, image [*, row], Title =’ Row Profile’

IDL> Plot, image [col, *], Title =’ Column Profile’

IDL> !P.Multi = 0

IDL> Wset, 0

输出结果类似于图59所示。

在循环中使用Cursor命令

有时想在循环中使用Cursor命令。例如,当用光标选择图像上的单个像素时,可能想知道它的像素值。下面是个简单的循环程序,它将一直执行下去,直到单击右键或中键退出。打开文本编辑器,准确无误地输入如下代码。

TopColor = !D.Table_Size-1

LoadCT, 3, Ncolors = !D.Table_Size-1

TvLCT, 255, 255, 0, topColor

TV, BytScl (image, Top =!D.Table_Size-2)

!Mouse.Button = 1

REPEAT BEGIN

Cursor, col, row, /Down, /Device

Print, ‘Pixel Value:’, image[col, row]

ENDREP UNTIL !Mouse.Button NE 1

END

114

IDL IDL入门教程

图59:用Cursor命令在图像中选择的行和列的剖面。(www.loach.net.cn)

保存到loop1.pro文件中(此文件已经存在于下载的本书配套程序之中)。编译,并运行这个小主程序,输入:

IDL> .RUN loop1

移动光标进入图像窗口,开始单击左键。图像像素值就会出现在日志窗口中,直到点击其它键,而不是左键。

如果在Cursor命令中使用除了Down之外的其它关键字会发生什么呢?实验一下,找出答案。 从显示中删除注释

当使用光标在图形显示上按照刚才使用的方法来添加注释时,也许会问:“怎样才能删除刚放置在那儿的注释呢?”有两种较好的方法删除注释。称之为异或法和设备拷贝法。笔者认为在两者之中设备拷贝法更能给出专业的感观效果。两种方法都列举出来,但重点将集中在设备拷贝法上。

删除注释的异或法

异或法是在图形函数的基础上起作用的。图形函数是两个数的位操作。这两个数分别与已经显示出来的像素(称作所谓的目标像素)以及希望放置在同一位置的像素(称作所谓的源像素)相关联。

通常,IDL使用的图形函数称作源。在这种图形函数里,IDL忽略了目标像素的值,仅仅在该像素位置上放置源像素的值。但如果这种图形函数变成XOR(异或法),IDL将目标像素和源像素进行逐位比较。这会产生反向目标像素的效果。换句话说,如果像素的二进制表示为01100101,那么执行XOR命令后,像素的二进制表示为10011010。

(实际的XOR过程远比这复杂,因为只有IDL在颜色索引表中的邻近位置上有256种颜色时,它才按这种方式运行,而这是一种少见的情形。大多数人只是认为XOR法是用“相反的”颜色画,并保留原来的。在实际的XOR法中,可能预测将会使用哪种颜色来画,但在大多数情形下并不是这样。这就是为什么绝大多数IDL专业程序员宁愿采用设备拷贝法。)

115

IDL IDL入门教程

在任何时侯,图形函数的作用效果都是由Device命令和Set_Graphics_Function关键字设置的。[www.loach.net.cn]源模式下图形函数为3。XOR模式下图形函数为6。此时IDL处于系统缺省的源模式下。当处于这种模式时,重新显示图像窗口中的图像。输入:

IDL> TV, BytScl (image, Top =!D.Table_Size-2)

现在,选择XOR模式:

IDL> Device, Set_Graphics_Function = 6

在图像中画一个方框:

IDL> Plots, [0.2, 0.2, 0.8, 0.8, 0.2], Color = topColor,$

[0.2, 0.8, 0.8, 0.2, 0.2], /Normal

注意,方框线的颜色不是预测的黄色。取而代之是多彩的,尽管它显得也很合理。在这个模式下线下的像素已经被翻转了。

要删除方框,只须将底下的像素值翻转回它们的原值。再次用PlotS命令很容易完成。 IDL> Plots, [0.2, 0.2, 0.8, 0.8, 0.2], Color = topColor,$

扩展:idl / idl教程 / idl程序设计

[0.2, 0.8, 0.8, 0.2, 0.2], /Normal

可以反复执行上面的命令,让方框随心所欲地出现或消失。在继续下面的练习前,确保将图形函数设回到源模式。键入:

IDL> Device, Set_Graphics_Function = 3

在IDL程序中可容易地利用图形函数。例如,打开以前写的loop1.pro主程序,按如下修改它。这个程序将在图像窗口中每次点击的位置上画一个大的十字线。以loop2.pro命名保存程序。键入: topColor = !D.Table_Size-1

LoadCT, 3, NColors=!D.Table_Size-1

TvLCT, 255, 255, 0, topColor

TV, BytScl(image, Top=!D.Table_Size-2)

!Mouse.Button = 1

; Go into XOR mode.

Device, Set_Graphics_Function=6

; Get initial cursor location. Draw cross-hair.

Cursor, col, row, /Device, /Down

PlotS, [col,col], [0,360], /Device, Color=topColor

PlotS, [0,360], [row,row], /Device, Color=topColor

Print, 'Pixel Value: ', image[col, row]

; Loop.

REPEAT BEGIN

; Get new cursor location.

Cursor, colnew, rownew, /Down, /Device

; Erase old cross-hair.

PlotS, [col,col], [0,360], /Device, Color=topColor

PlotS, [0,360], [row,row], /Device, Color=topColor

Print, 'Pixel Value: ', image(colnew, rownew)

; Draw new cross-hair.

PlotS, [colnew,colnew], [0,360], /Device, Color=topColor

PlotS, [0,360], [rownew,rownew], /Device, Color=topColor

; Update coordinates.

116

IDL IDL入门教程

col = colnew

row = rownew

ENDREP UNTIL !Mouse.Button NE 1

;Erase the final cross-hair.

PlotS, [col,col], [0,360], /Device, Color=topColor

PlotS, [0,360], [row,row], /Device, Color=topColor

; Restore normal graphics function.

Device, Set_Graphics_Function=3

END

保存到文件loop2.pro中。(www.loach.net.cn)(此文件已经存在于下载的本书配套程序之中。)编译,并执行这个主程序,键入:

IDL> .RUN loop2

将光标放在图像窗口中,点击左键数次。在每个光标位置上应该有一个十字线。按右键或中间键终止程序。

删除注释的设备拷贝法

设备拷贝法利用像素映射窗口来删除已显示屏幕上的注释。像素映射窗口和其它IDL图形窗口完全一样,除了它不显示在显示器上外。事实上,它存在于显示设备的视频随机存储器中。换句话说,它存在于存储器中。从其它任何方面来说,它就象一个正常的IDL图形窗口一样:可用Window命令创建,用WSet命令激活,用WDelete命令删除,等等。在像素映射窗口内画图和在IDL正常的图形窗口内画图是完全一样的(例如,用Plot、Surface、TV和其它图形输出命令)。

设备拷贝技术就是从一个窗口(称为源窗口)拷贝一个矩形区,然后将矩形区传入另一个窗口(称为目的窗口)。源窗口和目的窗口有时可以是同一个窗口,这点等会会看到。图60是设备拷贝技术的图解。

实际的拷贝是通过Device命令和Copy关键字(设备拷贝技术的名字由此而来)完成。命令的一般形式如下:

Device, Copy = [sx, sy, col, row, dx, dy, sourceWindowID]

在这个命令中,Copy关键字的组成元素是:

sx, sy 源窗口矩形区左下角的设备坐标(矩形区是从源窗口拷贝的) col 从源窗口中拷贝的列数。这是矩形区的宽度。

row 从源窗口中拷贝的行数。这是矩形区的高度。

dx, dy 目标窗口中矩形区左下角的设备坐标。(目标窗口是要将矩形区拷贝到的

窗口。目标窗口总是当前图形窗口。)

sourceWindowID 源窗口的窗口索引号。矩形区从此窗口拷贝到当前图形窗口(由!D.Window系统变量指定)。源窗口可以是当前图形窗口,但多数情况下它是一个非当前图形窗口的窗口。它通常是一个像素映射窗口。

要看其是如何工作的,先创建一个像素映射窗口,在里面显示一幅图像。用带Pixmap关键字的Window命令来创建像素映射窗口,键入:

IDL> Window, 1, /Pixmap, XSize = 360, YSize = 360

117

IDL IDL入门教程

IDL> TV, BytScl (image, Top =!D.Table_Size-2)

图60:设备拷贝技术是将源窗口的一个矩形区拷贝到目标窗口的某个位置。[www.loach.net.cn]实际上可以拷贝

整个窗口,而且源窗口和目标窗口可以是同一个窗口。

注意,当输入这些命令时没有任何可见的线索表明发生了什么。这是因为像素映射窗口仅存在于视频随机存储器中,而不是在显示屏上。为证明这个窗口内存有的内容,可打开第三个窗口,并将像素映射窗口的内容拷贝到第三个窗口中。如果第三个窗口看起来象原来的图像窗口,那么已经输入的命令是正确的。键入:

IDL> Window, 2, Xsize = 360, Ysize = 360

IDL> Device, Copy =[0, 0, 360, 360, 0, 0, 1]

注意,已将像素映射窗口的全部内容拷贝到这个新的窗口中。这种操作除了在速度上快几个数量级之外,其它方面与在此新窗口中重新显示图像类似了。将像素映射窗口中的全部内容拷贝到了新的显示窗口是一种常见的做法,即使只是修改部分的显示窗口。

删除最后创建的两个窗口(包括像素映射窗口),如下:

IDL> Wdelete, 1, 2

当对像素映射窗口操作完成后,删除它们是很重要的。它们会占用存储空间,这些存储空间可用于其它用途。某些窗口管理器为这些像素映射窗口分配一个定量的存储空间。如果像素映射窗口超过了视频存储器的容量,可以使用虚拟内存。X终端给像素映射窗口留有非常小的存储空间。

要了解设备拷贝技术在实际中是如何运用的,修改主程序以前写的主程序文件loop2.pro。可以拷贝到另一个文件,以loop3.pro命名。修改代码如下:

TopColor = !D.Table_Size-1

LoadCT, 3, Ncolors = !D.Table_Size-1

TvLCT, 255, 255, 0, topColor

扩展:idl / idl教程 / idl程序设计

TV, BytScl (image, Top =!D.Table_Size-2)

!Mouse.Button = 1

; Create a pixmap window and display image in it.

Window, 1, /Pixmap, Xsize = 360, Ysize = 360

TV, BytScl (image, Top =!D.Table_Size-2)

; Make the display window the current graphics window.

Wset, 0

; Get initial cursor location. Draw cross-hair.

Cursor, col, row, /Device, /Down

118

IDL IDL入门教程

Plots, [col, col], [0, 360 ], / Device, Color = topColor

Plots, [0, 360 ], [row, row ], / Device, Color = topColor

Print, ‘Pixel Value:’, image[col, row]

; Loop.

REPEAT BEGIN

; Get new cursor location.

Cursor, colnew, rownew, /Device, /Down

; Erase old cross-hair

Device, Copy = [0, 0, 360, 360, 0, 0, 1]

Print, ‘Pixel Value:’, image[colnew, rownew]

; Draw new cross-hair.

Plots, [colnew, colnew], [0, 360 ], / Device, Color = topColor

Plots, [0, 360 ], [rownew, rownew ], / Device, Color = topColor

ENDREP UNTIL !Mouse.Button NE 1

; Erase the final cross-hair.

Device, Copy = [0, 0, 360, 360, 0, 0, 1]

END

以loop3.pro命名存档。[www.loach.net.cn](此文件已经存在于下载的本书配套程序之中)编辑,并运行主程序,输入:

IDL> .RUN loop3

放置光标于图像窗口中,点击左键数次。单击右或中间键终止程序。注意十字线是用黄色绘制。

在继续下一次练习之前,删除像素映射窗口。输入:

IDL> Wdelete, 1

画一个橡皮筋方框

设备拷贝技术非常适用于在屏幕上画橡皮条选择方框和其它形状。(橡皮条方框是指一角固定,另一角随着光标的变化而变化的方框)。事实上,修改程序loop3.pro可以很容易实现。将loop3.pro拷贝到文件rubberbox.pro。(loop3.pro文件已经存在于下载的本书配套程序之中。)作如下修改,看看创建一个橡皮条方框是多么容易。

topColor = !D.Table_Size-1

LoadCT, 3, NColors=!D.Table_Size-1

TvLCT, 255, 255, 0, topColor

TV, BytScl(image, Top=!D.Table_Size-2)

!Mouse.Button = 1

; Create a pixmap window and display image in it.

Window, 1, /Pixmap, XSize=360, YSize=360

TV, BytScl(image, Top=!D.Table_Size-2)

;Make the display window the current graphics window.

WSet, 0

; Get initial cursor location (static corner of box).

119

IDL IDL入门教程

Cursor, sx, sy, /Device, /Down

; Loop.

REPEAT BEGIN

; Get new cursor location (dynamic corner of box).

Cursor, dx, dy, /Wait, /Device

; Erase the old box.

Device, Copy=[0, 0, 360, 360, 0, 0, 1]

; Draw the new box.

PlotS, [sx,sx,dx,dx,sx], [sy,dy,dy,sy,sy], /Device, $

Color=topColor

ENDREP UNTIL !Mouse.Button NE 1

;Erase the final box.

Device, Copy=[0, 0, 360, 360, 0, 0, 1]

END

运行程序,输入:

IDL> .RUN rubberbox

在继续下一次练习之前,删除像素映射窗口。(www.loach.net.cn)输入:

IDL> Wdelete,1

图形窗口的滚动

设备拷贝技术的另一个有效应用是实现图形窗口的滚动。在这个示例中将运用设备拷贝技术让图形显示窗口中的图像滚动起来。图像从左至右每次滚动四列。使用的算法如下:(1)将窗口右边最后四列拷贝到一个仅有四列宽和360行高的小像素映射窗口。(2)将整个显示窗口的内容(减去已经拷贝的四列)在同一窗口(也就是说,源窗口和目标窗口是同一个窗口)内向右边移动四列。(3)将像素映射窗口的内容拷贝到显示窗口左边头四列。打开文本编辑器,输入下列命令。以scroll.pro命名(此文件已经存在于下载的本书配套程序之中)。

; Open a pixmap window 4 columns wide.

Window, 1, /Pixmap, XSize=4, YSize=360

FOR j=0,360/4 DO BEGIN

; Copy four columns on right of display into pixmap.

Device, Copy=[356, 0, 4, 360, 0, 0, 0]

; Make the display window the active window.

WSet, 0

; Move window contents over 4 columns.

Device, Copy=[0, 0, 356, 360, 4, 0, 0]

; Copy pixmap contents into display window on left.

Device, Copy=[0, 0, 4, 360, 0, 0, 1]

ENDFOR

END

运行程序,输入:

IDL> .RUN scroll

120

IDL IDL入门教程

程序每次滚动一次。[www.loach.net.cn)再次运行程序,输入:

IDL> .Go

能修改程序让它一直滚动,直到让它停止吗?

在继续下一次练习之前删除像素映射窗口。输入:

IDL> Wdelete,1

Z图形缓冲区中的图形显示技巧

可以把IDL中的Z图形缓冲区想象成一个三维盒子,3D对象可以被堆积在里面而不用关心它们的“实体”性质。这个盒子能用16位深度缓冲区来记录每个对象的深度。盒子的一边是投影平面。可以想象光线通过投影平面的每一个像素最终遇到盒子内实体对象。光线遇到的像素值就是投影到投影平面的值。用这种方法,Z图形缓冲区就可以处理曲面和线条的自动消隐。图61就是此概念的图解说明。

Z图形缓冲区

图61:Z图形缓冲区可以被想象作一个能保留深度信息的3D盒子。光线射到Z图形缓冲

区中的物体时,它们的像素值反过来被投影到投影平面上。

其概念是,当物体被装入三维盒子里时,就可以得到投影平面的一幅快照或一幅图像。这是盒子中三维物体的二维投影。处在其它物体后面的物体就不被显示。(这种属性可以在一些IDL图形命令中用Transparent关键字来改变,以后将会看到。)快照实际上就是利用TVRD命令对投影平面的屏幕转储。

扩展:idl / idl教程 / idl程序设计

Z图形缓冲区的实现

在IDL中,Z图形缓冲区是用软件方式作为另一中图形输出设备实现的,类似于PostScript 121

IDL IDL入门教程

设备或X窗口,Win或Mac设备。(www.loach.net.cn)因此,要在Z图形缓冲区中画图形,必须用Sep_Plot命令将其变成当前的图形输出设备。和其它图形输出设备一样,Z图形缓冲区可用Device命令和适当的关键字来配置。

常用于Z图形缓冲区的两个关键字是Set_Colors和Set_Resolution。这两个关键字定义如下:

Set_Colors Z图形缓冲区中的颜色数。在缺省情况下,Z图形缓冲区使用

256种颜色。在IDL运行中,这是个非常少的颜色数目。如果

希望Z图形缓冲区的输出具有和显示设备相同的颜色数目,就

须设置这个关键字。

Set_Resolution Z图形缓冲区投影平面通常设置为640像素宽和480

像素高。如果要在图形窗口显示Z图形缓冲区的输出结果,就

应该将Z图形缓冲区的大小设置成图形窗口的大小。例如:

DEVICE, Set_Resolution=[400,400]

一个Z图形缓冲区实例:两个曲面

要了解Z图形缓冲区是怎样工作的,先按如下创建两个名为peak和saddle的物体。(完成

此例的命令可以在下载的本书配套程序中的文件twosurf.pro中找到。)

IDL>peak=shift(dist(20,16),10,8)

IDL>peak=Exp(-(peak/5)^2)

IDL>saddle=shift(peak,6,0)+shift(peak,-6,0)/2B

要在Z图形缓冲区中组合两个3D物体,首先看看这两个物体的各自形状。可以让它们在两个

窗口中以不同的颜色表显示。首先,在颜色查询表中的不同部位装入蓝色和红色色谱表:

IDL>colors=!D.Table_Size/2

IDL>LoadCT, 1, ncolors=colors

IDL>LoadCT, 3, ncolors=colors, Bottom=colors-1

创建一个窗口,显示第一个物体的阴影曲面图。要注意的是,Set_shading 命令是用来将阴影处理的值限制在颜色查询表中特定部位。键入:

IDL>window, 1, xsize=300, ysize=300

IDL>set_shading, value=[0,coloe-1]

IDL>shade_surf, peak, zrange=[0.0,1.2]

接着将第二物体显示在它自己的显示窗口中。用颜色查询表的不同部位作为阴影处理参数。键入:

IDL>window, 2, xsize=300,ysize=300

IDL>set_shading, value=[colors, 2*colors-1]

IDL>shade_surf, saddle, zrange=[0.0,1.2]

使Z图形缓冲区成为当前设备

为了在Z图形缓冲区中组合两个物体,必须使Z图形缓冲区成为当前的图形显示设备。这可

用Set_Plot命令来实现。Copy这个关键字可将当前的颜色表复制到Z缓冲区中。保存当前图形显示设备的名字,以便能方便地返回。键入:

IDL>thisDevice=!D.Name

122

IDL IDL入门教程

IDL>Set_Plot,’z’,/Copy

配置Z图形缓冲区

接下来,必须将Z图形设备配置成具体的规格。(www.loach.net.cn]在这个例子中,要限制颜色的数目,还要使缓冲区的分辨率与当前图形显示窗口尺寸相等。键入:

IDL>Device,Set_colors=2*colors, Set_Resolution=[300,300]

将物体装入到Z图形缓冲区中

现在,将两个物体装入到Z图形缓冲区中。要注意的是,键入这些命令时,将看不到任何事发生。因为输出已进入内存中的Z图形缓冲区里面,而不是显示设备。

IDL>Set_shading,values=[0,color-1]

IDL>Shade_surf,peak,zrange=[0.0,1.2]

IDL>Set_shading,values=[colors,2*colors-1]

IDL>shade_surf,saddle,zrange=[0.0,1.2],/noerase

对投影面进行拍照

接着,对投影表面进行快照。可通过TVRD命令实现。

IDL>picture=TVRD ()

在显示设备上显示结果

最后,返回到显示设备。打开一个新的窗口来显示“图像”,如下:

IDL>Set_Plot, thisdevice

IDL>Window, 3, xsize=300,ysize=300

IDL>TV, picture

输出结果应如图62所示。

123

IDL IDL入门教程

图62:Z图形缓冲区可以通过曲面的自动消隐来组合3D物体。(www.loach.net.cn]

Z图形缓冲区的一些奇怪特点

仔细查看窗口1和3的输出。特别注意观察那些坐标轴的标记。可以注意到窗口3(也就是来自Z图形缓冲区的输出)的轴的标记要稍微大些。由于某种原因,Z图形缓冲区使用了一种缺省的不同于IDL在显示窗口中显示图形时所用的字符尺寸。

这种简单的事实,如果不意识到的话,会导致在Z图形缓冲区中建立一个3D坐标空间以及组合IDL图形命令时,造成大量的时间浪费。这主要是因为图形边缘的建立是基于缺省的字符尺寸,并且图形边缘在显示设备上和在Z图形缓冲区中是有差异的。它们差不多是一样的。但就是 “差不多”将使读者焦头烂额。

一种笨规则非常有用,就是,当要在Z图形缓冲区中画图时,总是设置!P.Charsize系统变量。例如:

IDL>!P.Charsize=1.0

这里只是提供一个例子,查看图62中的图示。这幅图上的轴不是在Z图形缓冲区中创建的,因为那时它们将以屏幕分辨率被着色处理(也就是说,作为一幅图像)而且可用PostScript的分辨率对它们进行着色处理。如果!P.Charsize关键字没有在对阴影曲面进行着色处理和随后坐标轴的添加之前被设置的话,就不可能在最后的输出中使坐标轴线对应在正确的位置上。

用Z图形缓冲区使图像变形

使用Z图形缓冲区另一种更强大的技术是用于三维数据的切片显示。这是可行的,因为Z图形缓冲区具有将图像变形到一个多边形平面上的能力。要了解是如何工作的,先用LoadData命令打开80*100*57 的3D MRI Head Scan数据集。如下:

IDL>head=LoadData(8)

也许希望能在输入代码时开一个日志文件去截获这些命令,因为这些命令数量很多,而且必须正确无误地找到它们。日志文件将使读者很轻松地改变并重新运行这些命令。(这种日志文件已经创建好了,可以下载的本书配套程序中找到warping.pro文件。)

扩展:idl / idl教程 / idl程序设计

IDL>Journal, ’warping.pro’

124

IDL IDL入门教程

一般来说,在IDL中用Size命令可以获得变量的维数和大小。[www.loach.net.cn)为定义正确的图像平面,需要知道三维的尺寸。在这个例子中,“尺寸值”将比真实的维数值小1,因为要用这个数值作为数组的索引号。IDL的索引是号从零开始的。

IDL>s=Size(head)

IDL>xs=s[1]-1

IDL>ys=s[2]-1

IDL>zs=s[3]-1

假若想在这个数据集的中心显示三个正交切片,也就是说通过三维点(40,50,27)。可以用下述方法定义这些点:

IDL>xpt=40

IDL>ypt=50

IDL>zpt=27

接着,可以构建用来描绘这三个图像切片或平面的各个多边形。此例子中,每个多边形将是有四个点(矩形的四个角)的简单矩形。矩形的每点都要用一个(x,y,z)三位值描述。换句话说,每个平面都将是3*4的多边形。键入:

IDL>xplane=[ [xpt,0,0],[xpt,0,zs],[xpt,ys,zs],$

[xpt,ys,0] ]

IDL>yplane=[ [0,ypt,0],[0,ypt,zs],[xs,ypt,zs],$

[xs,ypt,0] ]

IDL>zplane=[ [0,0,zpt],[xs,0,zpt],[xs,ys,zpt],$

[0,ys,zpt]]

下一步是获得与每个图像平面相对应的图像数据。这在IDL中用数组下标很容易做到。 IDL>ximage=head[xpt,*,*]

IDL>yimage=head[*,ypt,*]

IDL>zimage=head[*,*,zpt]

要注意的是这些图形都是3D图像(其中有一维是1)。所关心的是与每个图像平面相关的2D图像,因此必须用Reform命令将这些3D图像转换成为2D图像格式。在此例中,Reform命令将图像重新格式化成80*100*1的图像。当最后的一维为1时,IDL将会舍弃它。这里的结果是一个80*100的图像。

IDL>ximage=reform(ximage)

IDL>yimage=reform(yimage)

IDL>zimage=reform(zimage)

要正确显示这些图像,需要确保图像数据已正确地缩放到与显示颜色数相匹配的范围内。相对于整个数据集的范围来调整数据是非常重要的。调整数据并装入颜色:

IDL>mindata=min(head,max=maxdata)

IDL>topcolor=!D.table_size-2

IDL>Loadct, 5, ncolors=!D.table_size-1

IDL>Tvlct, 255, 255,255,topcolor+1

IDL>ximage=bytscl(ximage,top=topcolor,max=maxdata,$

min=mindata)

IDL>yimage=byscl(yimage,top=topcolor,max=maxdata,$

min=mindata)

IDL>zimage=byscl(zimage,top=topcolor,max=maxdata,$

min=mindata)

125

IDL IDL入门教程

下一步要准备建立Z图形缓冲区。(www.loach.net.cn) Erase命令将删去先前留在缓冲区内的任何内容。此例子中,用白色擦除,以使显示更加清晰。

IDL>thisdevice=!d.name

IDL>Set_Plot,’z’

IDL>device, Set_colors=topcolor, set_resolution=[400,400]

IDL>erase, color=topcolor+1

用Scale3命令创建3D坐标空间。这里的坐标轴是用每个维度的大小标记的。

IDL>Scale3, XRange=[0,xs], YRange=[0,ys], ZRange=[0,zs]

最后将要在Z图形缓冲区中对这些切片进行着色处理。为此需要用Polyfill命令。Pattern关键字将用于设置想显示的图像切片。Image_Coord关键字包含一系列与多边形的每个顶点相关联的图像坐标。Image_Interp用于当图像被包裹到多边形上时,指定用双线性插值,而不是在最近邻域内重采样。T3D关键字通过将三维变换矩阵应用到最后输出中,从而确保多边形出现在3D空间内。键入:

IDL>polyfill,xplane,/t3d,pattern=ximage,/image_interp,$

image_coord=[ [0,0],[0,zs],[ys,zs],[ys,0] ]

IDL>polyfill,yplane,/t3d,pattern=yimage,/image_interp,$

mage_coord=[ [0,0],[0,zs],[xs,zs],[xs,0] ]

IDL>polyfill,zplane,/t3d,pattern=zimage,/image_interp,$

image_coord=[ [0,0],[xs,0],[xs,ys],[0,ys] ]

最后,将投影平面快速拍照,并显示结果。如下:

IDL>picture=tvrd()

IDL>set_Plot,thisdevice

IDL>window,xsize=400,ysize=400

IDL>tv,picture

如果已打开了日志文件,现在将其关闭:

IDL>Journal

输出结果应类似于图63所示。如果不是,可用文本编辑器修改日志文件中的代码来解决问题。要重新运行代码,保存文件并键入:

IDL>@warping

126

IDL IDL入门教程

图63:在Z图形缓冲区将图像数据变形到平面中的一个实例

Z图形缓冲区中的透明效果

注意图63中的每个切片的外部边缘有许多黑块。(www.loach.net.cn] 这不是图像的部分,而是黑色背景噪音 。 Z图形缓冲区的一个优点是可以在其中运用透明效果。例如,如果将命令PolyFill中的Transparent关键字设置为大约20或25,那么图像中低于这个值的像素将是透明的。可以键入试试看看什么效果。(也许想启动另一个日志文件。如果已经关闭了上次的日志文件,应给这次赋一个新名。不幸的是,还不能添加日志文件。给新的日志文件命名为transparent.pro。这个日志文件的备份可以在下载的本书配套程序中找到。)

IDL>journal, ’transparent’

IDL>Set_plot, ’z’

IDL>erase, color=topcolor+1

IDL>polyfill, xplane,/t3d,pattern=ximage,/image_interp,$

Image_coord=[[0,0],[0,zs],[ys,zs],[xs,0]],$

Transparent=25

IDL>polyfill, yplane,/t3d, pattern=yimage,/image_interp,$

Image_coord=[[0,0], [0,zs],[xs,zs],[xs,0]],$

Transparent=25

IDL> polyfill, zplane,/t3d, pattern=zimage,/image_interp,$

Image_coord=[[0,0], [xs,0],[xs,ys],[0,ys]],$

Transparent=25

IDL>picture=tvrd()

IDL>set_plot,thisdevice

IDL>window,/free,xsize=400,ysize=400

扩展:idl / idl教程 / idl程序设计

IDL>erase,color=topcolor+1

127

IDL IDL入门教程

IDL>tv,picture

IDL>journal

如果输入出了差错并且最后的输出图像看起来不正确,可对日志文件中的错误进行更改,然后重新启动日志文件,键入:

IDL>@transparent

将Z图形缓冲区效果与体数据着色相结合

Z图形缓冲区效果经常和体数据的着色技术相结合,从而使数据的可视化更生动。[www.loach.net.cn]例如,假设想创建这套数据集的等值面(一个等值面是一个在任何地方都有相同值的曲面。它就象数据的一个三维等值线图。)启动一个名为isosurface.pro日志文件。(此日志文件的备份可以在下载的本书配套程序中找到。)

IDL>Journal,’isosurface.pro’

要创建一个等值面,首先用Shade_Volume命令创建一组描述曲面的顶点和多边形。在下面的命令中,设置Low关键字以便所有比此等值面值大的值将被此等值面包围。变量vertices和polygons都是输出变量。它们将用在随后的PolyShade命令中来对曲面着色。看一下值为50的头部数据的直方图,是一个很适合等值级别。键入:

IDL>plot, histogram(head), Max_value=5000

IDL>shade_volume, head, 50,vertices, polygons , /low

等值面是用PolyShade命令来进行着色处理的。必须确保使用前面建立的3D变换,键入: IDL>scale3, xrange=[0,xs],yrange=[0,ys],zrange=[0,zs]

IDL>isosurface=polyshade(vertices,polygons,/t3d)

IDL>loadct, 0, ncolors=topcolor+1

IDL>tv,isosurface

现在,将这个等值面和穿过数据的Z切片组合起来,这个Z切片就是前面在Z图形缓冲区中获得的。注意现在在Z方向上截掉头部数据。键入:

IDL>shade_volume,head(*,*,0:zpt),50,vertices,$

polygons,/low

IDL>isosurface=polyshade(vertices,polygons,/t3d)

IDL>isosurface(where(isosurface eq 0))=topcolor+1

IDL>tvlct, 70, 70, 70, topcolor+1

IDL>set_plot,’z’, /copy

IDL>tv,isosurface

IDL> scale3, xrange=[0,xs],yrange=[0,ys],zrange=[0,zs]

IDL>polyfill, zplane,/t3d,pattern=zimage,/image_interp,$

Image_coord=[[0,0],[xs,0],[xs,ys],[0,ys]],$

Transparent=25

IDL>picture=tvrd()

IDL>set_plot,thisdevice

IDL>tv,picture

输出结果应类似于图64所示。如果不是,修改日志文件并重新运行该文件,如下: IDL>@isosurface

128

IDL IDL入门教程

图64:等值面和数据切片在Z图形缓冲区中的组合。[www.loach.net.cn]

第六章 在IDL中读写数据

本章概要

本章旨在介绍IDL中的常用的输入和输出程序。IDL中的基本原则是:“只要有数据,就可以将其读进IDL”。IDL没有格式要求,也没有特别要求在将数据带入IDL时对数据进行准备。这使得IDL成为目前功能最强、最灵活的科学可视化分析语言。

具体来说,将学习:

1. 如何打开文件进行读写

2. 如何查找文件

3. 如何获得文件I/O的逻辑设备号

4. 如何获得机器的独立文件名

5. 如何读写ASCII或格式化的数据

6. 如何读写非格式化的或二进制数据

7. 如何处理大型数据文件

8. 如何读写通用的文件格式,如GIF和JEPG文件

129

IDL IDL入门教程

打开文件进行读写

IDL中的所有输入和输出都是通过逻辑设备号完成的。[www.loach.net.cn)可以把一个逻辑设备设想为一个管道,这个管道连接着IDL和要读写的数据文件。要从一个文件中读写数据,必须首先把一个逻辑设备号连接到一个特定的文件。这就是IDL中三个Open命令的作用:

openr 打开文件进行读。

openw 打开文件进行写。

openu 打开文件进行更新(也就是说,读和/或写)。

这三个命令的语法结构是完全相同的。首先是命令名,后面是一个逻辑设备号和要与该逻辑设备号相连的文件名。例如,将文件名temp596.dat和逻辑设备号20相连以便可以在此文件里面写入内容。如下:

OpenW, 20,’temp596.dat’

将会看到Open命令更常用的书写方式。例如,可能会看到类似于如下的IDL代码:

OpenR, lun, filename

此例中,变量lun保存了一个有效的逻辑设备号,变量filename代表一个机器特定的文件名,这个文件名将和此逻辑设备号联系起来。

注意,变量filename是一种机器特定的格式。这意味着如果它含有特定的目录信息,它必须用本地机器的语法来表达。而且它在某些机器(比如,UNIX机器)上具有大小写敏感性,因为在这些机器上文件名有大小写敏感性。

查找和选择数据文件

IDL被广泛使用的原因之一,是IDL可以在许多不同的计算机操作系统中运行。但由于不同的操作系统有不同的文件命名习惯(而且,特别用确定子目录的不同方式),这在以独立于机器的方式指定文件名方面提出了挑战。幸好,IDL提供了一些工具可让这项工作变得容易些。 选择文件名

也许获得机器独立文件名最容易的方法是用Pickfile对话框。IDL命令允许用机器上自身的选择文件的图形对话框来交互式地从文件名列表中选择一个文件名。例如,从本地目录.pro文件列表中选择一个文件名,可以键入如下命令:

IDL>filename=Dialog_Pickfile(Filter=’*.pro’,/Read)

注意,这个命令在IDL5.0以前的版本中命名为Pickfile。

IDL5.2版通过关键字Multiple,赋予Dialog_Pickfile选择多个文件名(若它们存在于同一个目录下)的能力。使用了正常的依赖于平台的选择文件方式。例如,在用WinDOws操作系统的计算机上,通常先选择第一个文件,接着用Shift键和鼠标点击来选择在第一个文件和第二个文件之间的所有文件,或者用Control键和鼠标点击来选择一个额外的文件。

IDL>filename=Dialog_Pickfile(Filter=’*.pro’,/Read,/Muitiple)

扩展:idl / idl教程 / idl程序设计

如果要打开文件来写而不是去读,在对话框中,可用Write关键字代替Read关键字。甚至可以推荐一个缺省的文件名,键入:

IDL>outfile=Dialog_Pickfile(File=’default.dat’,/Write)

从这个对话框中返回的是带绝对路径的文件名,其形式与运行IDL的机器有关。也就是说,它 130

IDL IDL入门教程

使用机器自身的文件命名语法。[www.loach.net.cn]键入以下命令就可以看到:

IDL>Help, filename, outfile

注意,Dialog_Pickfile对话框中有一个“取消”按钮。若选择“取消”按钮,对话框会返回一个空字符串。所以在打开文件读写之前,总是希望检查返回的名字是否为空。

IDL>IF outfile EQ ‘’ THEN Print, ’Whoops!’

选择目录名

在IDL5.2中,Dialog_pickfile得到改进,因而它也能用于选择目录名而不仅是一个文件名。设置Directory关键字,在选择窗口内只列出目录而没有文件。

IDL>directory=Dialog_Pickfile(/Directory)

寻找文件

另一个有用的命令是FindFile命令。此命令返回一个包含所有符合给定文件要求的文件名的字符串数组。这在IDL程序中用于自动匹配并打开文件的任务中非常有用,或者是在任何时候不知道一个目录下有多少个文件的情况下,用于在该目录下创建一批文件的任务中也是非常有用的。 例如,要打印出当前目录下所有数据文件的长度(按字节计),可键入IDL代码:

Files=findfile(‘*.dat’,count=numfiles)

If numfiles eq 0 then message,‘no data files here!’

FOR j=0,numfiles-1 DO BEGIN

Openr, 10, files(j)

fileinfo=fstat(10)

print,fileinfo.size

Close, 10

ENDFOR

需要重点指出的是:FindFile命令中文件说明(’*.dat’)是以相对路径名给出,而不是绝对路径名。因此,命令返回也是相对路径名,而不是绝对路径名。这不同于Pickfile对话框,Pickfile总是返回绝对路径名。

构造文件名

获得机器独立文件名的第三个很有用的IDL命令是Filepath命令。例如,假设要打开文件galaxy.dat,它在IDL主目录下的 examples/data子目录中。可以为此文件构造一个机器独立的绝对路径名,键入:

IDL>galaxy=filepath(‘galaxy.dat’,Subdirectory=[‘examples’,’data’]

如果想从其它的目录开始而不是IDL主目录,可以用Root_Dir关键字指定开始的目录名。例如,要在当前目录的子目录coyote中构造此文件的一个路径名,可以键入:

IDL>cd,current=thisdir

IDL>galaxy=filepath(‘galaxy.dat’,root_dir=thisdir,$

Subdirectory=’coyote’)

注意:Filepath命令并没有实际找到此文件,它只是构造了一个文件路径名。构造的文件名甚 131

IDL IDL入门教程

至在机器中可以不存在。(www.loach.net.cn]

获取逻辑设备号

在IDL中所有文件输出和输入都是在一个逻辑设备号上完成的。一个Open命令的作用是将一个特定的文件(通过其文件名来指定)和一个逻辑设备号相关联。有128个逻辑设备号可供使用。它们被分成两类。 逻辑设备号

1-99

100-128 用途 这些号可以在Open命令中直接使用 这些号通过Get_Lun和Free_Lun命令获取和管理

表8:128种逻辑设备号被分成两类。一类可以直接使用,另一类用Get_Lun和Free_Lun命令

获取和管理

直接使用逻辑设备号

要直接使用逻辑设备号,只能在1-99中选择一个号,并用Open命令使用它。要做的是选择一个当前没有使用的号。例如,可用逻辑设备号5来打开当前目录的子目录coyote中的galaxy.dat文件,键入:

IDL>CD,current=thiedir

IDL>filename=Filepath(Root_Dir=thisdir,$

Subdirectory=’coyote’,’galaxy.dat’)

IDL>Openr, 5, filename

一旦1-99中的某个逻辑设备号分配给一个文件后,它就不能再分配了,直到该逻辑设备号被关闭或退出IDL(这将自动关闭所有打开的逻辑设备号)。

当完成了对逻辑设备号的操作(也就是说,不想再对文件进行读写),可用Close命令关闭它,并使其可以重新使用:

IDL>Close, 5

让IDL管理逻辑设备号

多数情况下(特别在IDL程序中),最好让IDL管理逻辑设备号。可使用Get_Lun和Free_Lun命令完成此项功能。有两种方法让IDL返回一个逻辑设备号。可以直接使用Get_Lun命令。如:

IDL>Get_Lun,lun

IDL>OpenR,lun,filename

或者,可以用带关键字Get_Lun的Open命令来间接完成:

IDL>OpenR,lun,filename,/Get_Lun

这个命令运用隐含的Get_Lun命令将作为结果的逻辑设备号存入变量Lun中。这是逻辑设备号最常用的获取方法,特别是在IDL程序中。(注意,变量名不一定是Lun。可以给出喜欢的名字。如果打开了几个文件,就需要几个不同的名字。)

当完成了对逻辑设备号的操作(也就是说,不想再对文件进行读写),可用Free_Lun命令关闭它。如:

IDL>Free_Lun,Lun

132

IDL IDL入门教程

使用Get_Lun和Free_Lun命令的好处在于不必记住哪个逻辑设备号是可用的或没被使用的。[www.loach.net.cn)Get_Lun程序保证返回一个有效的逻辑设备号(假设同时已经打开的文件少于28个)。如果是在过程和函数中打开文件,一般最好使用Get_Lun命令。因为如果直接选择某个特定的逻辑设备号,不能保证它是可用的或没有被使用的。

判断哪些文件和哪些逻辑设备号相连

使用带Files关键字的Help命令,可以很容易判断哪些文件和哪些逻辑设备号相连。 IDL>Help,/Files

读写格式化数据

IDL在读写格式化数据方面有两种格式化文件之区分:自由文件格式和确定的文件格式。格式化文件有时叫做ASCII文件或者纯文本文件。

自由文件格式 自由格式文件用逗号或空白(tab键和空格键)分开文件中的每个

元素,这没有确定的文件格式正规。

确定的文件格式 确定的格式文件是用格式说明按照给定的规范进行编排的。IDL格

式说明和FORTRAN或C程序中的格式说明类似。

扩展:idl / idl教程 / idl程序设计

写自由格式文件

在IDL中,写一个自由格式文件极其容易。只要用PrintF命令将变量写入文件即可。这和在显示窗口上用Print命令打印变量的形式几乎相同。IDL在写文件时自动在数据的元素间加入空格。 例如,键入下面这些命令,创建数据并写入文件:

IDL>array=FIndGin(25)

IDL>vector=[33.6,77.2]

IDL>scalar=5

IDL>text=[‘array’, ’vector’, ’scalar’]

IDL>header=’Test data file.’

IDL>created=’created: ’ + SysTime()

接着,将一个逻辑设备号(让IDL自己选择一个并放到变量lun中)和特定文件相连来打开文件写入,键入:

IDL>OpenW , lun, ’test.dat’,/Get_Lun

最后,将数据写入文件,并关闭数据文件,键入:

IDL>PrintF, lun, header

IDL>PrintF, lun, created

IDL>PrintF, lun, array, vector, scalar, text

IDL>Free_Lun, lun

用一个文本编辑器打开文件检查。类似于如下所示:

133

IDL IDL入门教程

Test data file.

created: Tue Nov 28 15:50:58 2000

0.000000 1.00000 2.00000 3.00000 4.00000 5.00000 6.00000 7.00000 8.00000 9.00000 10.0000 11.0000 12.0000 13.0000 14.0000 15.0000 16.0000 17.0000 18.0000 19.0000 20.0000 21.0000 22.0000 23.0000 24.0000

33.6000 77.2000

5

array vector scalar

注意:IDL 在数组变量的每个元素间设置空白区,并使每个新变量另起一行开头。(www.loach.net.cn)IDL在缺省情况下使用80列宽度。如果需要不同的列宽度,可以用带Width关键字的OpenW命令设置。

读自由格式文件

许多ASCII文件是自由格式文件。例如,从电子数字表程序中保存的文件大多是自由格式文件。一种特殊的自由格式数据是从键盘或标准输入读取的数据。

在IDL中,有两种命令可以读取自由格式文件。

Read 读取从标准输入或键盘上读入自由格式数据

ReadF 从文件中读入自由格式数据

读取自由格式文件的规则

无论是从键盘上还是从文件中读取自由格式的数据,IDL遵循下列7种规则:

1. 如果读入到字符串变量中,那么,在当前行剩下的所有字符都将读入该变量中。

观察上面数据文件的第一行,此行内有三个单词。此项规则意味着,一旦IDL开始为字符串变量读入数据,那么IDL将一直读到行尾,否则不会停止。其原因是在IDL的所有数据类型中,字符串变量可以是任意尺寸的,而且空格符也是ASCII字符。

打开该数据来读入,试着只读入一个单词,如下:

IDL>Open, lun, ‘test.dat’, /Get_Lun

IDL>word = ‘’

IDL>ReadF,lun,word

IDL>Print, word

所看到的是整个第一行都被读入到word变量中。

Test data file.

(如果想将第一行分解成单个的单词,可以用IDL的字符串处理程序来完成。)这种可以读到行尾的能力是IDL一个好的特色。为什么这样说呢?首先,将文件指针重置到文件的开头。可用Point_Lun命令实现此项功能。键入:

IDL>Point_Lun, lun, 0

现在可将数据文件的前二行读入到header变量中。键入:

IDL>header = StArr(2)

134

IDL IDL入门教程

IDL>ReadF, lun, header

IDL>Print, header

这些命令将读出文件的前二行,并将文件指针定位到数组的起始处。(www.loach.net.cn)可以在同一行上同时输出了头两行的内容。

Test data file. created: Tue Nov 28 15:50:58 2000

2. 输入数据必须用逗号或空白分隔(空格键或tab键)。

在test.dat数据文件中所用到的就是这种格式的数据。IDL在数组变量中的每个元素间插入5个空格。

3. 输入通过数字变量完成。数组和结构都可作为数字变量的集合。

这意味着,如果正在读入的变量含,比如说,10个元素,IDL将从数据文件中读入10个分开的数值。它将采用以下的两条规则来确定这些数据存在于文件的何处。

4. 如果当前读入行是空的,并且还有变量要求输入,则读取另一行。

5. 如果当前读入行不是空的,但是没有变量要求输入,则忽略此行剩下的数。

为了解其含意,可键入:

IDL>data = FltArr(8)

IDL>ReadF, lun, data

IDL>Print, data

将看到:

0.00000 1.00000 2.00000 3.00000 4.00000 5.00000

6.00000 7.00000

在此例中,IDL从文件中读入8个分开的数据值。当读取到第一行数据的末端时,它自动进入到第二行(规则4),因为还有更多的数据要求读入。当数据读入到第二行的中部时,规则5起作用了。如果现在还要读入更多的数据,那将从数据的第三行开始,因为第二行的其它部分被忽略了。键入:

IDL>data = FltArr(3)

IDL>ReadF, lun, vector3

IDL>Print, vector3

将看到:

12.0000 13.0000 14.0000

变量vector3包含的数值为12.0、13.0和14.0。现在,文件指针定位在文件中的第四数据行上(规则5)。

6. 尽量将数据转换为变量所希望的数据类型。

要了解什么意思,将第四、第五行数据读入到一个字符串数组,以便让文件指针定位在文件中的第六数据行(即起始数值为33.6000的那行)。键入:

IDL> dummy = StrArr(2)

IDL> ReadF, lun, dummy

135

IDL IDL入门教程

假设想读入两个整型值。[www.loach.net.cn)IDL尽量将数据(在此情况下,为浮点数)转换为整型。

键入:

IDL> ints = IntArr(2)

IDL> ReadF, lun, ints

IDL> Print, ints

将看到:

33 77

注意,浮点数被简单的截取成整数。在转换处理过程中并没有采用四舍五入的规则来保证最接近的整数值。

7. 复数数据必须有实数和虚数两部分,用逗号分隔,并用括号括起来。如果仅仅提供了单个数值,它将被认为是实数部分,而虚数设置为0。例如,可通过键盘读入复数数据:

扩展:idl / idl教程 / idl程序设计

IDL>value = ComplexArr(2)

IDL> Read, value

: (3, 4)

: (4.4, 25.5)

IDL> print, value

在结束这部分学习之前,确保关闭test.dat文件。键入:

IDL>Free_Lun, lun

读写自由格式文件的实例

学会用IDL读写数据最容易的方法是看一些实例。下面实例说明在读入文件头、处理以列排列的数据以及处理已读入IDL的数据这些方面的常用IDL技巧。

读一个简单数据文件

从读刚创建的test.dat文件中的数据开始。首先,创建用于读文件中数据的变量。

IDL>header=strarr(2) ; two header lines.

IDL>data=fltarr(5,5) ; floating point array.

IDL>vector=intarr(2) ; two-element integer vector.

IDL>scalar=0.0 ; floating-point scalar.

IDL>string_var=’’ ; a string variable.

注意,此时数据变量将是5*5的数组。而在文件中是以25个元素的矢量保存的。用这种方法读入数据相当于读入25个元素的矢量,并将其重新格式化为5*5的数组。记住IDL中的数据是按行存储的。

根据规则一,不能将文件末端的一行文本读到三元素的字符串数组中。不得不将其读入单个字符串变量,然后用IDL的字符串处理命令将该文本字符串分解到后续的变量中。

可立即从文件中读出所有数据,键入:

IDL>openr, lun, ’test.dat’, /Get_Lun

IDL>ReadF, lun, header, data, vector, scalar, string_var

IDL>free_Lun, lun

要将包含文件末行文本的字符串变量转换成三个元素的字符串数组,首先要将变量前后两头 136

IDL IDL入门教程

的空白字符除去。[www.loach.net.cn)键入:

IDL>thisstring=strtrim(string_var,2)

有时若将字符串转换成字节型数组,字符串的处理就会容易些。可以先处理字节型数组,最后再将其转换成字符串。当然也可使用其它方法,但是这种方法在这里更好一些。

IDL>thisarray=Byte(thisstring)

IDL>help,thisarray

这是19个元素的字节型数组。要知道空格的ASCII码字符,可用IDL查出:

IDL>blank=Byte(“ ”)

IDL>print,blank

IDL>help,blank

注意,Byte命令处理一个字符串后的返回值总是一个数组。在此例中,是一个元素的数组。为了以后不至于混淆,将其转换成一个数值。

IDL>blank=Blank[0]

输出空白字符的ASCII码值是32。

可用Where命令显示字节型数组中的空白字符。

IDL>vals=Where(thisarray EQ blank)

最后,将字符串转换成三个元素的数组。

s=strarr(3)

s[0]=string(thisarray[0:vals[0]-1])

s[1]=string(thisarray[vals[0]+1:vals[1]-1]

s[2]=string(thisarray[vals[1]+1:*]

写列格式数据文件

在文件中数据按列储存是不稀奇。需要了解如何用IDL读写这种数据。IDL将会给粗心的程序员一个惊奇。要知道究竟是怎么回事,可以写一个列格式数据文件。用下列命令将数据读入IDL:

IDL>data=LoadData(15)

这个数据是一个有三个字段的结构:lat, lon和temp。每个字段都是41个元素的浮点矢量。可用如下命令从结构中提取矢量:

IDL>lat=data.lat

IDL>lon=data.lon

IDL>temp=data.temp

接着,打开一个写入数据文件,键入:

IDL>OpenW,lun,’column.dat’, /Get_Lun

需要将三列数据写入这个文件,通过自由格式输出。这可以在一个循环中完成。

IDL>printf, lun, ’column data: lat, lon, temp’

IDL>FOR j=0, 40 DO printf, lun,lat[j], lon[j], temp[j]

IDL>free_lun, lun

column.dat文件的前四行应如下:

Column data: lat, lon, temp

33.9840 –86.9405 36.9465

26.2072 –121.615 20.1868

42.1539 –103.733 231.604

137

IDL IDL入门教程

读列格式数据文件

到目前为止一切正常。(www.loach.net.cn)当试着将列格式数据读入 IDL时问题就出来了。可能会按如下做。首先,创建要读入数据的变量。

IDL>header=’’

IDL>thislat=fltarr(41)

IDL>thislon=fltarr(41)

IDL>thistemp=fltarr(41)

打开column.dat文件读首行:

IDL>OpenR, lun, ’column.dat’ ,/Get_Lun

IDL>ReadF, lun, header

由于是用一个循环将数据放进文件的,所以也可能会用一个循环从文件中将数据读出。

IDL>FOR j=0, 40 DO ReadF, lun, thislat[j], thislon[j], thistemp[j]

但这不奏效。虽然上面的命令没有错误,但没有数据读入变量(如打印变量值,它们将是零)。

其原因是IDL中有一个严格的规则,即不能带下标的变量来读入内容。原因是IDL 将带下标的变量作为值而不是作为变量的引用传递给象ReadF这样的IDL 程序。以数值传递的数据不能在被调用的子程序中改变,因为被调用的子程序只是获得该数据的备份,而不是获得该数据的指针。要改变这种属性需要对IDL进行大改,然而这是不可能的。

有两种方法解决这个问题。第一种是将数据读入一个循环中的临时变量。这种方法最好是运用文本编辑器将命令输入文件中来完成,因为很难在IDL命令行上编写多行循环。可用Point_Lun命令将文件指针返回到数据文件的起始处。

IDL>Point_Lun, lun, 0

在文本文件loopread.pro中输入下列命令。

temp1=0.0

temp2=0.0

temp3=0.0

ReadF, lun, header

FOR j=0, 40 DO BEGIN

ReadF,lun, temp1, temp2, temp3

Thislat[j]=temp1

Thislon[j]=temp2

Thistemp[j]=temp3

ENDFOR

END

执行文本文件中的代码:

IDL>.Run loopread

将原始矢量的值和刚读入矢量的值打印出来,将发现它们是相同的。例如:

DL>Print, lat, thislat

虽然这个方法奏效,但它不是最好的方法。主要是因为它用了一个循环,循环在IDL中很慢。41次循环的速度也许无所谓,但如果是41,000次循环,执行速度将是个麻烦问题。

扩展:idl / idl教程 / idl程序设计

较好的方法是将数据一次性读到3*41的数组内,然后在用IDL提供的数组处理命令将矢量从这个较大数组中提出。要知道这是如何实现的,可用下面命令将数据文件指针返回到头:

IDL>Point_Lun, lun, 0

接下来,将数据一次性读到3*41的浮点数组:

138

IDL IDL入门教程

IDL>header=’’

IDL>array=fltarr(3,41)

IDL>ReadF, lun, header, array

用数组下标将大数组的矢量分离出来:

IDL>thislat=array[0,*]

IDL>thislon=array[1,*]

IDL>thistemp=aarry[2,*]

IDL>Free_lun, lun

注意,这些新的矢量是列矢量(也就是说,是1*41的二维数组。[www.loach.net.cn])键入:

IDL>Help, thislat, thislon, thistemp

要使这些列矢量转换成更熟悉的行矢量,可用Reform命令将1*41 的数组转换成41*1的数组。当多维数组的最后维为1时,IDL 便舍弃这个维。键入:

IDL>thislat=ReFORm(thislat)

Idl>thislon=ReFORm(thislon)

Idl>thistemp=ReFORm(thistemp)

Idl>Help, thislat, thislon, thistemp

创建读列格式数据的模板

由于许多人都用有列格式数据文件,IDL5引进了一个新的程序以便更容易读此类数据文件。提供帮助的是两个新命令:ASCII_Template和Read_ASCII命令。ASCII_Template命令是个组件程序,可以引导读者按步骤定义自己的列数据。可以给每列数据命一个名字,告诉IDL数据类型,甚至可以跳过一些列。运行该程序的结果是用结构变两的形式生成了数据文件的模板。这个模板可以传给Read_ASCII命令,数据就会按模板规范来读取。其结果是一个根据模板的规范读出来的带有字段名的IDL结构变量。要知道它如何运用到上面文件中,可键入:

IDL>fileTemplate=ASCII_Template(‘column.dat”)

按照出现在显示器上的组件对话框模式指导进行操作。其形式有三页,在第一页上可以看到数据文件的样本行,左边有它们的行号。在标有Data Starts at Line的文本组件中:输入2。这允许在文件中跳过一行的文件头。在组件的右下角选择Next按钮进入下一页。

注意在这页里,每行的字段数列出的是三,并可选择White Space按钮作为数据分隔符。若信息正确,就点击Next 按钮进入最后一页。

在这页中,数据组可以被命名并且可以被指定数据类型。如果想在数据中跳过一列或多列,可在界面的右上角的下拉式列表框Type中将其设为Skip Field类型。通过界面右上角的Name文本组件,将三个字段分别命名为Latitude,Longitude和Temperature。这些字段都是浮点类型。

当完成命名和选择数据列的数据类型后,选择界面上的Finish按钮。运行结果是一个用于描述文件中数据的IDL结构变量,它们可被用作Read_ASCII命令的输入。

IDL>help, fileTemplate, /structure

要读文件中的数据,可使用Read_ASCII命令。

IDL>data = Read_ASCII (‘column.dat’, Template= fileTemplate)

数据立刻被读取,结果是包含三个字段latitude, longitude, temperature的IDL结构变量。

IDL>Help, data, structure

如果要提出结构中的矢量,可键入:

IDL>thislat=data.latitude

IDL>thislon=data.longitude

139

IDL IDL入门教程

DL>thistemp=data.temperature

ASCII_Template和Read_ASCII命令既可用于自由格式的数据文件,也可用于下面将要讨论的确定格式的数据文件。(www.loach.net.cn]

用确定的文件格式写入

读写确定文件格式可同样用ReadF和PrintF命令,它们刚才已用于自由格式文件,但现在文件格式已由Format关键字明确声明。(在读写标准输入和输出时,也可将Format关键字用于Read 和Print命令)。

Format关键字的语法和在FORTRAN程序中使用的格式规则类似。尽管格式规则很复杂,这儿却有许多共同之处。如果使用过FORTRAN代码来读写数据的话,便会很快地熟悉它们。

一些共有的格式说明符

在IDL中有许多格式说明符,有一些是共有的。如矢量数据的定义:

IDL>data=Findgen(20)

I 修饰整型数据。将数据按整数输出,以每个两位,每行 5个,每个之间有两个空格的形式打出:

IDL>thisFormat=’(5(I2,2x),/)’

IDL>Print, data, Format=thisFormat

F 修饰浮点数据。将数据按浮点数输出,以小数点后两位,每行一个的形式打出。(这个数要为小数点留出一位)

IDL>thisFormat=’(f5.2)‘

IDL>Print,data,Format=thisFormat

D 修饰双精确数据。将数据按双精度输出,按每行写5个,每个数之间有4个空格,小数点右边有10位的形式打出。

IDL>thisFormat=’(5(D13.10, 4x))’

IDL>Print, data*3.2959382, Format=thisFormat

E 用科学记数法描述浮点数据(如:116.36E4)

IDL>thisFormat=’(E10.3)’

IDL>Print, data*10E3, Format=thisFormat

A 描述字符型数据。将数字转换成字符串,并以四个字符长的字符串形式写出,每个字符串用两个空格分开,每行4个字符串:

IDL>thisFormat=’(4(A4, 2x))’

IDL>Print,StrTrim(data,2), Format=thisFormat

NX 跳过n个空格字符。

写用逗号分隔的确定格式数据文件

有时数据文件必须用确定格式书写,以方便它们被其它软件读取。用逗号分隔的数据文件就是这类文件的典型代表。例如,要把上面读到的数据写成此类文件。下面就是如何实现。可用Format.dat作为写入的文件名,键入:

IDL>OpenW, lun, ’Format.dat’, /Get_Lun

140

IDL IDL入门教程

创建一个逗号字符串变量,如:

IDL>comm.=’ , ‘

以确定格式写出文件,用10位宽并有三位小数的浮点数,后接一个逗号和两个空格,键入:

IDL>thisFormat=’(F10.3, A1, 2x, F10.3, A1, 2x, F10.3)’

IDL>FOR j=0,40 DO PrintF,lun, thisLat[j],comma,$

thisLon[j],comma,thisTemp[j],Format=thisFormat

扩展:idl / idl教程 / idl程序设计

IDL>Free_lun,Lun

数据文件前三行如下所示:

48.000, -121.128, 36.946

44.843, -108.133, 163.027

29.865, -109.668, 89.870

读出用逗号分隔的确定格式文件

要读取刚创建的确定格式数据文件,键入:

IDL>OpenW, lun, ’Format.dat’, /Get_Lun

IDL>thisFormat=’(2(F10.3, 3X), F10.3)’

IDL>array=Fltarr(3, 41)

IDL>ReadF, lun, array, Format=thisFormat

IDL>Free_lun, lun

这很简单,注意只要按上面的格式把逗号当作空格跳过即可。(www.loach.net.cn]

从字符串中读取格式数据

ReadS是一个有用的IDL命令,可以从字符串变量而不是从文件中为自由格式或确定格式读取数据。ReadS运用了和命令Read和ReadF相同的读取格式数据规则。也就是说,使用ReadS 就象从数据文件中读取一样,所不同的是所读的对象是一个字符串变量。

当大量信息需从文件头部读取时,此命令特别有用。例如,假设ASCII 数据文件的第一行说明了数据文件的行数和列数,随后是采集数据的时间。例如:

10 24500 12 June 1996

此文件头可以从文件中读取,并且可创建一个大小正确的数组来读取数据。如下:

firstLine=’’

ReadF,lun,firstLine

columns=0

rows=0

date=’’

ReadS,firstLine,columns,rows,date

dataArray=FltArr(columns,rows)

读写非格式化数据

迟早,数据会越来越多。若这样,就要开始思考更好的数据储存方法。非格式化数据(有时叫二进 141

IDL IDL入门教程

制数据)比格式化数据紧凑得多,经常用于大数据文件。[www.loach.net.cn]有两种命令读写非格式化数据,它们与早期用来读取格式数据文件的ReadF和 Print 命令等效。它们是ReadU和WriteU命令。

非格式化数据文件基本是以一长串的二进制字节存在文件中。这些字节的含义(也就是说,这些字节如何翻译成特定数据类型和结构的)很艰难的描述的,除非刚开始就知道文件写入的是什么内容。在文件里面,各字节都很相似。将字节读入正确类型和结构的变量中就可理解了。原则上这很容易做到,因为多数数据类型有给定的字节长度。例如,每个浮点值有4个字节。IDL整数有两个字节,等等。

要读取非格式数据文件,简单定义变量,打开文件读取,并用ReadU命令将字节一个接一个地读入变量中。如果给定了变量的数据类型和组织结构,每个变量按其要求从文件中读出相应的字节数。例如,一个5个元素的浮点矢量将从文件中读取五(元素数)乘四(一个浮点值的字节数)共二十个字节。

读取非格式化图像数据文件

例如,假设想读取储存在coyote子目录下的几个非被格式化图像数据文件中的一个。这些文件碰巧包含的是字节型数据,但它们也能容易地包含整数与浮点数。下面的一些命令被用来打开其中之一,galaxy.dat文件。星系图像的字节已被组织成一个256*256字节的数组。(这个代码已假定coyote子目录存在于IDL主目录中。)

IDL>filename=Filepath(Root_Dir=!Dir, $

Subdirectory=’coyote’,’galaxy.dat’)

IDL>OpenR, lun, filename, /Get_Lun

此幅图像的结构为一个256*256的数组。如果事先不知道这些,为正确地读取这个数据,将会花费很大的精力。因为在数据文件里没有信息提示这些字节是如何被组织起来的。

在研究非格式化数据文件之前,如果不知道它有多大,那么只有一件事情要做。这也就是它包含多少字节。例如,FStat(文件的状态)命令能告知文件的字节数。

IDL>fileInfo=Fstat(lun)

IDL>Print,fileInfo.size

65536

在这个文件里有65536个字节,但这没有告知这个数据的结构是怎样组织的。例如,这些字节能被组成128*512的二维数组,也可以被组成64*64*16的三维数组。没有办法知道这些。绝望的程序员有时尝试用各种数组组合来显示数据。如果这个看起来不对,就试着用另外一个,等等。

在此例子中,这些数据被组成256*256字节型数组。因此图像变量能够这样设定:

IDL>image=BytArr(256,256)

现在,从文件里读取数据,然后关掉文件,如下:

IDL>ReadU, lun, image

IDL>Free_Lun, lun

显示数据,键入:

IDL>Window,Xsize=256,Ysize=256

IDL>Tvscl, image

写非格式化图像数据文件

假设在图像上进行了一些处理,想将结果保存在另一个数据文件里。例如,在图像上进行了一个Sobel边界增强操作,如下:

IDL>edge=Sobel(image)

142

IDL IDL入门教程

Sobel操作不仅可以帮助图像增强它的边缘,而且被返回的图像不再是字节类型。[www.loach.net.cn]事实上,它是整数数据,键入:

IDL>Help, image, edge

在IDL中整数占两个字节,因此如果这个数据被写入一个数据文件,这个文件的大小将会是原来的字节型数据文件的两倍。这些对于那些试图读取处理后的图像数据文件的人来说可能有些混淆。所以可以考虑在文件里给出一些信息,告诉用户关于这种数据的相关类型以及怎样组织的。这个文件信息可以按如下定义:

IDL>fileInfo =’Sobel Edge Enhanced, 256 by 256 INTEGERS’

这些字符串可以在文件里写在图像数据的前面。

但是,字符串fileInfo也是一串字节。在此例中,它是一个有31个字节的字符串。如果不知道关于此非格式化文件的这一点,以后从文件里读取数据将会非常困难。例如,设或许认为信息字符串是30个字节长。这样,在读完文件信息字符串之后,继续读出的每一个整数(两个字节)将会完全是错误的。

为避免这么多的限制,大多数非格式化数据文件的文件头有着固定的大小(通常是256的倍数)。例如,假设决定所有的图像文件都有一个512个字节的头文件。可用IDL 里的Replicate和String命令创造一个有512个字节长的空格字符串。

IDL>header = String (Replicate (32B, 512))

字节值32是一个空格字符的ASCII值。把文件信息字符串插入这个长一点的文件头字符串中,从而建立具有一个正确尺寸的头文件。可以用IDL里的StrPut (把一个字符串放进另外一个字符串)命令。如下:

扩展:idl / idl教程 / idl程序设计

IDL>strPut, header, fileInfo, 0

最后,将文件头和数据写入一个新的非格式化数据文件中。如下:

IDL>OpenW, lun, ‘process.dat’

IDL>WriteU, lun, header, edge

IDL>Free_Lun, lun

当字符串被写进非格式化的文件之时,也就相当于,字符串含多少个字符,就有多少个字节被写进文件。

读取带有文件头的非格式化数据文件

假设想读取上面刚建立的文件,里面有512个字节的头文件信息,紧接着是256*256*2个字节的图像数据。可以将头信息看成是一个字符串,因为它含有关于文件里保存的数据类型的文本信息。

对于格式化的数据,文件头的变量可以被创建成空一个字符串或一个空字符串数组,以便数据文件的全部行能一次读入到文件头变量。对于非格式化的文件这是不可能的。事实上,非格式化字符串数据的规则是当从文件里读取字符串时,仅仅只是读取确定个数的字节去填满该字符串目前的长度。这样,一个文件头被定义为一个空字符串,将不会从非格式化的文件里读出什么。

这意味着在从文件中读取字符串之前,必须知道正在读的字符串长度。在刚创建的process.dat文件里,文件头有512个字节长。可以用String和Replicate命令建立一个合适长度的空白字符串去读入,象前面那样。但是更容易的方法是把文件头读进一个字节型数组变量,然后把它转变成一个字符串。键入:

IDL>OpenR, lun, ‘process.dat’, /Get_Lun

IDL>header =BytArr(512)

IDL>ReadU, lun, header

143

IDL IDL入门教程

IDL>Print, String(header)

Sobel Edge Enhanced,256 by 256 INTEGERS

从这条信息里面,就能够建立正确的数据数组,并从文件里读出图像数据并显示它们。[www.loach.net.cn)如下:

IDL>edgeImage =IntArr(256,256)

IDL>ReadU, lun, edgeImage

IDL>Free_Lun, lun

IDL>Window, XSize=256,YSize=256

IDL> TV, edgeImage

非格式化数据文件的一些问题

不幸的是,虽然处理非格式化的数据较方便,但同时在使用这种数据是也有一些相关的问题。首先,非格式化的数据与机器类型有很大关系。在SUN计算机上写的数据,如果不做任何处理的话,在SGI或者HP计算机上经常读不出来,而在PC或者Macintosh计算机上是肯定读不出来的。(ByteOrder命令能够用来解决许多这类问题。IDL5.1版将新的Swap_If_Big_Endian和Swap_If_Little_Endian关键字引入到Open命令中,可用于在各种各样的机器结构上编写代码来读取二进制数据。)

为了能在不同的机器结构上传递非格式化数据,IDL支持XDR(eXternal Data Representation,外部数据表示)文件格式。XDR格式是Sun Microsystems创建的公用的数据格式。在几乎所有的现代化计算机上都可用。它在二进制文件里存储了少量的元数据(数据本身的一些附加信息)。但是XDR文件仍然很简洁。

如果文件是用XDR非格式化的形式写的,数据文件在计算机之间很容易传递。换句话说,XDR非格式化文件成为跨机器结构的文件格式。

要读写XDR格式的文件,必须用XDR关键字打开。例如:把上面的process.dat文件写成XDR文件,可以键入:

IDL>OpenW, lun, ‘process.dat’, /Get_Lun, /XDR

常规的WriteU命令用来把数据写进文件:

IDL>WriteU, lun, header, edge

IDL> Free_Lun, lun

在XDR文件里字符串的长度被存储起来,并随着字符串本身一起被恢复。这意味着不必要象一般的非格式化文件那样,每次都初始化一个正确长度的字符串变量。例如,打开读取XDR文件里的信息,可以键入:

IDL>OpenR, lun, ‘process.dat’,/XDR

IDL> thisHeader = ‘’

IDL> thisData =IntArr(256,256)

IDL> ReadU, lun, thisHeader, thisData

IDL> Free_Lun, lun

用关联变量存取非格式化数据文件

大型的非格式化数据文件通常都有一系列的重复单元组成。例如,一个卫星每隔半小时就拍摄一幅512*600像素的浮点图像,并将这些图像一个接一个地存储在一个数据文件里,这个文件 144

IDL IDL入门教程

每隔一定的时间被下载一次。(www.loach.net.cn)在数据文件里包含50-100M的数据是很寻常的。一个IDL关联变量通常是处理这种数据形式的最好方式(有时候是唯一的方式)。

IDL关联变量是把一个IDL数组或结构变量的组织结构映射到数据文件的内容上。文件被看作是这些重复单元的一个数组。 第一个单元的索引号是0,第二个单元的索引号1等等。关联变量不象常规变量那样将整个数据组都存储在内存里。而是当一关联变量被引用时,IDL仅对需要的部分数据执行相关的输入或输出请求,这部分数据就是要读入内存的。

关联变量的一些优点

关联变量有以下几个优点:

1. 当该变量被用于表达式时,才产生文件的输入和输出动作。不需要单独的读或

写命令。

2. 数据集的大小不受内存容量的限制,因为有时它可处理大型的数据集。对于物

理存储器来说是太大的数据,通过把此数据分成块就能很容易地处理。

3. 不必提前声明用于映射该数据的数组或结构的数量。

4. 关联变量是效率最高的I/O形式。

定义关联变量

定义和使用关联变量,可按通常的方式打开数据文件,然后用Assoc命令创建关联变量。例如,打开位于IDL主目录下的coyote 子目录中的abnorm.dat文件。

IDL> filename = Filepath(Subdir=’coyote’, ‘abnorm.dat’)

IDL> OpenR, lun, filename, /Get_Lun

这个文件里含有16幅图像或16帧画面,每幅都是64*64个字节型数组。为这些数组创建关联变量:

IDL> image = Assoc(lun, BytArr(64,64))

Assoc命令的第一个参数是与image变量相关联的文件的逻辑设备号。第二个参数是文件中被重复的单元的描述。

这些文件在重复文件单元前通常有文件头信息,尽管此文件没有。如果这样的话,Assoc命令的第三个定位参数是给定文件头的大小或文件头在文件中的偏移量。例如,假设abnorm.dat文件的前4096个字节是文件头信息,并且希望跳过文件头,那么Assoc命令可以被写成这样:

扩展:idl / idl教程 / idl程序设计

IDL> image2 = Assoc(lun, BytArr(64,64), 4096)

注意,现在有两个变量,image和image2,与同一个数据文件关联。这在IDL中完全合法,事实上,这对于存取重复单元不一致的非格式化数据文件是一种好办法。通过改变在文件中的偏移量,可以用关联变量来实现随机读写数据。

显示上面image变量里的第五幅图像或画面,键入:

IDL> TvScl, image (4)

从数据文件里把数据读进一个临时变量,在其被显示后,被IDL删除。没有显式的ReadU命令,也不需要常规情况下IDL处理这幅图像所需要的永久内存。如果想从关联变量中创建一个变量,可按通常的方式建立这个变量。例如,可以键入:

IDL> image5 = image (4)

IDL> TV, Rebin (image5, 256,256)

数据文件里重复单元的形式没有必要只是一个简单的二维图像数组。它可以是一个复杂的结 145

IDL IDL入门教程

构。[www.loach.net.cn]例如,每一个重复单元可以包含128个字节的文件头,两个100个元素的浮点型矢量和一个100*100的整型数组。如果是这样,可以建立一个文件关联变量。键入:

OpenR, 10, ‘example.dat’

Info = BytArr (128)

xvector = FltArr (100)

yvector = FlatArr(100)

data = IntArr (100,100)

struct = {header: info, x: xvector, y: yvector, image: data}

repeatingUnit = Assoc (10, struct)

因为映射到此数据文件相的变量是一个结构变量,因此在它的引用被删除之前,必须对此结构变量进行临时拷贝。例如,显示文件第三个重复单元的图像部分,可以键入:

TempVariable = repearingUnit (2)

TvScl, temPvariable.image

按照通常的方式,可以用Free_Lun或Close命令将关联变量和文件之间的联系关闭。如下:

Free_Lun, lun

Close, 10

文件格式

CDF 读此类文件的IDL程序 参考CDF库 写此类文件的IDL程序 参考CDF库 对象对象

对象对象

HDF

HDF-EOS 参考HDF库 参考HDF库 参考HDF库 参考HDF库

无 netCDF 参考netCDF库 参考netCDF库

PostScript

Sun Rasterfiles 无或打印设备 Read_SRF Write_SRF

WAWrite_WA无

表9:IDL能够读写许多常用的数据文件格式。一般情况下通过用IDL语言写的库程序或动态连接模块(DLM)来完成的,DLM在运行时可以添加到IDL中。CDF、netCDF和HDF文件格式是

著名的科学数据格式,有它们自己的IDL接口和库程序。

146

IDL IDL入门教程

读写常用文件格式的文件

到目前为止,本章已经介绍了IDL读写数据文件的一般方法。(www.loach.net.cn]这种底层的能力已经可以用IDL读写许多数据文件了。但是可能还想知道许多其它文件格式如何读写。这些文件格式中就有GIF和JPEG文件格式,它们常常在不同办公室之间或全球范围内被用来共享数据。当想在硬拷贝中出版图形输出时,可能想知道也必须知道如何建立PostScript文件。

IDL可以读写许多常用文件格式,这些文件格式已在表9中列出。

创建彩色GIF文件

GIF文件常被用来在万维网上发布图形信息。如果想和同事共享图形结果,迟早要读写GIF文件。

要看其是如何完成的,可装入一些数据,然后在图形窗口中显示这些数据。键入: IDL> Window, XSize=300, YSize=300

IDL> data= LoadData (1)

IDL> TVLCT, [100,255,0], [100,255,255], [100,0,0], 0

IDL> Plot, data, /NoData, Color=2, Background=0

IDL> OPlot, data, Color=1

写GIF文件

下面的命令生成一个大小为300*300像素的GIF文件。首先,将图形窗口的内容复制到一个2D字节型图像变量中。如果在使用8位显示器,TVRD命令可用来实现这个目的。

IDL> image = TVRD ()

如果正在16位或24位彩色显示器运行IDL,那么需要用TVRD命令获取一幅24位的图像,然后用Color_Quan命令将它压缩成一幅带有正确色彩表矢量的2D图像,命令如下。只有是在16位或24位显示器上运行IDL,才用下面的命令代替上面的命令:

IDL> image = TVRD (True=1)

IDL> image = Color_Quan (image24, l, r, g, b)

假如已经有一个2D字节型数组,就没有必要再拷贝图形窗口。

GIF文件格式要求将色彩表随图像数据一起存储到GIF文件内。如果使用8位显示器,那么用TVCL命令和Get关键字就可以得到由红、绿、蓝三种颜色矢量组成的当前色彩表:

IDL> TVCL, r, g, b, /Get

假如正在16位或24位的显示器上运行作,没有必要键入上面的命令。可以在上面Color_Quan命令里得到相关图像的色彩表矢量。

色彩矢量必须是256个元素。如果在8位显示器上运行IDL,这些矢量可能就没有这么长,但是不必担心。如果在写GIF文件时这些色彩矢量不够长的话, IDL将会加长色彩矢量。如果想充分利用256种颜色,可考虑在Z图形缓冲区装载色彩表,然后得到颜色矢量,缺省情况下可获得256种颜色。参考125页的“Z图形缓冲区中的图形显示技巧”。

最后,用Write_GIF命令将图像和颜色矢量写进名为test.gif 的GIF文件:

IDL> Write_GIF, ‘test.gif’, image, r, g, b

147

IDL IDL入门教程

以上就是所有要做的。(www.loach.net.cn]没有必要获取逻辑设备号或其它东西。所有这些细节都是IDL库程序Write_GIF命令来处理的。如果对具体如何实现感到好奇的话,可以检查源代码。

如果读者有某个应用程序能打开和读取GIF文件,试着读一下刚建立的文件。许多万维网的浏览器都支持读取GIF文件。看一下,如果浏览器有一个Open File按钮,用它试试看能否读取这个GIF文件。

读GIF文件

要读刚建立的GIF文件,可按下面简单地用Read_GIF命令读GIF文件里的图像和颜色矢量。

IDL> Read_GIF, ‘test.gif’,thisImage, rr, gg, bb

清除图形窗口,装载一个灰色级调色板,可以看到将发生什么。键入:

IDL> Erase

IDL> LoadCT, 0

现在,显示刚从文件里读取的图像,如下:

IDL> TV,thisImage

扩展:idl / idl教程 / idl程序设计

有时候在图形窗口里什么都看不到,这是因为GIF图像使用的颜色还没有装入。必须装载和GIF图像相关的色彩表,以便这个图像能够正确地显示。键入:

IDL> TVCT, rr, gg, bb

将在显示窗口里看到原始图像。

假设在16位或24位上的显示器上,必须关掉颜色分解。为了看到正确的颜色,必须在装入颜色表矢量之后重新显示这个图像。

IDL> Device, Decomposed=0

IDL> TV, thisImage

创建彩色JPEG文件

另外一个常被用在万维网上共享图形结果的文件格式是JPEG格式。这个JPEG格式被称为有损压缩格式。也就是说,当图像数据被压缩报存到文件时,数据的一些信息内容会被丢失,并且不能被恢复。压缩比例,丢失的信息量以及输出图像的质量通常可用质量索引值来设定,质量索引值的范围从0(丢失许多信息内容的,质量差)到100(很少或根本就没有信息丢失,质量好)。

通常,质量索引值被设置为75,即保证一个适当的压缩比,没有丢失很多信息且图像质量损失不大。

一幅彩色JPEG图像一般是24位的图像。也就是说,这个图像是3D字节型数组。在这个数组中,维数之一将为3。这个维数的位置将决定图像是隔像素扫描(3, m, n),隔行扫描(m, 3, n),还是隔波段扫描(m, n, 3)。在很多情况下,所拥有的图像是8位的图像,而不是24位的图像,希望将其转变成一幅JPEG文件。例如,图像可能是图形窗口的屏幕转储,象上面的GIF例子一样。下面是如何从8位图像创建隔像素扫描的24位图像的实例。

首先,打开一个图形窗口,在色棒旁显示一幅图像。代码中的TVImage和Colorbar命令在已下载的本书配套程序中。键入:

IDL> Window, Size=400,Ysize=300

IDL> LoadCT, 3

IDL> image = LoadData(7)

IDL> TVImage, image, Position=[0.1, 0.1, 0.75, 0.9]

148

IDL IDL入门教程

IDL> Colorbar, Position=[0.8, 0.1, 0.86, 0.9], /Right, $

/Vertical, Division=5, Format=’(F5.1)’

写JPEG文件

接下来,对图形窗口进行拍照,获得图形输出的一幅二维图像,并用这幅图像创建隔像素扫描的24位图像。(www.loach.net.cn)24位图像是通过用颜色表矢量建立的,实质上是将显示图像的颜色进行分离。IDL代码如下:

IDL> image = TVRD()

IDL> image = BytArr(3, 400, 300)

IDL> TVLCT, r, g, b, /Get

IDL> image3D[0, *,*]=r[image]

IDL> image3D1, *,*] =g [image]

IDL> image3D[2, *, *] = b[image]

注意,如果是在16位或24位显示器上,用一个简单的命令就可以得到24位的图像。不需要键入上述的命令,只需键入:

IDL> image3D=TVRD(true=1)

最后,用Write_JPEG命令,将此幅24位的图像用较好的图像质量和适当的压缩比输出到JPEG文件。键入:

IDL> Write_JPEG, ‘test.jpg’, image3D, true=1, quality=75

如果读者有某个应用程序能打开和读取JPEG文件的话,试着读一下刚建立的文件。许多万维网的浏览器都支持读取JPEG文件。看一下,如果浏览器有一个Open File按钮,用它试试看能否读取这个JPEG文件。

读取JPEG文件

用Read_JPEG命令就可以读取并显示一个JPEG文件。例如,如果打算在8位显示器上显示24位图像,可以用下面这个命令:

IDL> Read_JPEG, ‘test,jpg’, thisImage, colortable,$

Colors=!D.Table_Size, Dither=1, /Two_Pass_Quantize

关键字Colors指明24位图像应该量化到多少种颜色,它的值应该是从8到256。关键字Dither 选择Floyd-Steinbeig抖动法,它把颜色量化时的错误分散到旁边的周围的像素中去,从而获得高质量的图像。关键字Two_Pass_Quantize将颜色量化分为两步进行处理,同样也可以获得更好的颜色量化效果和更高的图像质量。

显示数据,键入:

IDL> Erase

IDL> Tv, thisImage

所看到的可能有些奇怪。这是因为这幅图像的颜色量化方法。为了看到输出结果到底是什么,必须装载与图像相关的颜色表。这个颜色表返回在变量colortable中。装载颜色表,键入:

IDL> TVLCT,colortable

假设正在8位显示器上显示24位的图像,应该使用TV命令里的关键字True:

IDL> TV, thisimage, true=1

149

IDL IDL入门教程

查询图像文件信息

常用图像文件格式的查询程序已在IDL5.2版中提供。[www.loach.net.cn]这些程序允许在没有真正读取其数据的情况下,就可以查询图像文件。这些程序可以存取随着图像数据文件一起存储在文件里的元数据(关于数据的一些信息)。

下面是新的图像查寻程序列表:

z Query_BMP

z Query_DICOM

z Query_GIF

z Query_JPEG

z Query_PICT

z Query_PNG

z Query_PPM

z Query_SRF

z Query_TIFF

所有这些查询命令都是以同样的方式工作。它们都是返回0或1的函数,通过返回值确定是否成功地(返回值为1)读取了图像文件里的元数据。如果它们成功地读取了文件,将保存文件信息的IDL结构变量作为输出命令返回给用户。用户通过存取这个结构里面的字段从而获取文件的有关信息。

例如,查询刚创建的JPEG文件,将文件的返回信息返回到变量fileinfo,可以键入: IDL> ok=Query_JPEG(‘test, jpg’, fileinfo)

看看返回的是什么信息,键入:

IDL> Help, fileinfo, /Structure

可以看到打印输出的信息:

**Structure<1364998>, 7 tags, length=36, refs=1:

CHANNELS LONG 3

DIMENSIONS LONG Array[2]

HAS_PALETTE INT 0

IMAGE_INDEX LONG 0

NUM_IMAGES LONG 1

PIXEL_TYPE INT 1

TYPE STRING ‘JPEG’

能够看到此文件(Num_Image=1)里有一幅图像,它是字节型数据(Pixel_Type=1),是一个24位的图像(Channels=3)。这个图像的大小能够通过打印维数字段可以看到,如下:

扩展:idl / idl教程 / idl程序设计

IDL> Print, fileinfo.dimensions

400 300

其它的图像查询程序在返回结构里含有类似的字段。

150

IDL IDL入门教程

第七章 图形硬拷贝输出

本章概要

在使用IDL的时候,如何以硬拷贝形式再现屏幕中的图形是最复杂,也是最难理解的问题。(www.loach.net.cn]然而,这是大多数献身科学的人的需求,但很少有令人完全满意的方法来和同事共享科学结果。 本章将集中于PostScript输出,因为PostScript是普遍接收的一种输出媒介,大部分使用IDL的程序员都能使用PostScript打印机。所有关于PostScript的内容同样适用于其它输出设备,比如HP绘图仪和PCL打印机。

具体来说,将学习:

1. 如何选择硬拷贝输出设备

2. 如何配置硬拷贝输出设备

3. 如何将图形输出直接传送到打印机

4. 如何将图形输出传送到一个文件中

5. 如何为硬拷贝输出设备产生图形输出

6. PostScript输出与显示器的输出有什么不同

7. 如何在PostScript页面上定位图形和图像

8. 如何产生能包含在其它文档中的图形输出

9. 如何编写能很容易地转化为硬拷贝输出的程序

10. 如何在PostScript种使用颜色

选择图形硬拷贝输出设备

与设置其它图形显示设备一样,在IDL中,仍然使用Set_plot命令来设置图形硬拷贝输出设备:

Set_Plot, 'option'

其中的option是下列的任何一种,注意option总是一个字符串,因此要使用单引号括起来。与IDL其它大多数字符串不一样,option对大小写不敏感。

CGM 输出写入CGM(计算机图形元文件)格式的文件中,CGM也是一种独立于设备

的文件格式,用于交换图形信息。CGM文件能以三种形式之一编码:(1)文本,

(2)二进制数据,(3)NCAR二进制数据。

HP 输出以惠普图形语言(HP-GL)格式写入一个文件,它适用于各种各样的HP-GL

笔式绘图仪。

PCL 输出以惠普打印机控制语言(PCL)格式写入一个文件,它适用于各式激光和喷

墨打印机。

PRINTER 输出以任何适合于默认打印机的方式直接传送到该打印机。 PS 输出以PostScript格式写入一个文件中。

Z 输出被写入Z图形缓冲区。

在打印完毕后,应再次使用Set_plot命令将输出设备改回为图形显示设备的类型,以下是一些常用的显示设备:

151

IDL IDL入门教程

WIN 使用微软Windows或NT操作系统的个人计算机。(www.loach.net.cn]

MAC 使用MacOS操作系统的计算机

X 使用X Window系统的计算机。

只有一种设备能成为当前图形设备,可以通过检查!D.Name系统变量来确定当前的设备是哪种,如下:

IDL>Print,!D.Name

注意,当设定设备名时,设备名对大小写不敏感,但当在代码中使用该名字时,就不一定不敏感了。存储在!D.Name系统变量中的图形设备名是以大写字母形式存储的。这在下面的字符串比较语句中尤为重要:

IDL>IF !D.Name EQ 'PS' THEN Print,'Using PostScript…'

配置图形硬拷贝输出设备

一旦选定了图形输出设备,所有设备具体的配置参数都用Device命令通过关键字来控制。Device命令可用的关键字主要取决于当前的设备。但打印设备(总是和默认的打印机相连)的设置也可以使用Dialog_PrinterSetup命令来设置(详见201页的“配置和使用打印设备”)。

测定当前的设备配置

使用Help命令,可以知道当前硬拷贝输出设备所设定的配置参数,如下:

IDL>Help,/Device

将能看到一系列的有关当前图形设备的当前设置参数及其参数值。这些信息可以用来配置设备。关于设备可用的颜色数,IDL使用的是哪种图形函数以及当前选择的硬件字体等等信息,都取决于所设定的当前设备是何种设备。

注意,这些信息的显示随着每个硬拷贝输出选项的不同而不同。例如,键入下面这些命令来看PostScript输出设备缺省配置如何:

IDL>thisDevice=!D,Name

IDL>Set_Plot,'PS'

IDL>Help, /Device

IDL>Set_Plot, thisDevice

以下为Help命令的显示结果(在Windows NT机器上):

Available Graphics Devices: CGM HP NULL PCL PRINTER PS WIN Z

Current graphics device: PS

File: <none>

Mode: Portrait, Non-Encapsulated, EPSI Preview Disabled, Color Disabled Offset (X,Y): (1.905,12.7) cm., (0.75,5) in.

Size (X,Y): (17.78,12.7) cm., (7,5) in.

Scale Factor: 1

Font Size: 12

Font Encoding: AdobeStandard

Font: Helvetica TrueType Font: <default>

# bits per image pixel: 4

Font Mapping:

152

IDL IDL入门教程

(!3) Helvetica (!4) Helvetica-Bold

(!5) Helvetica-Narrow (!6) Helvetica-Narrow-BoldOblique

(!7) Times-Roman (!8) Times-BoldItalic

(!9) Symbol (!10) ZapfDingbats

(!11) Courier (!12) Courier-Oblique

(!13) Palatino-Roman (!14) Palatino-Italic

(!15) Palatino-Bold (!16) Palatino-BoldItalic

(!17) AvantGarde-Book (!18) NewCenturySchlbk-Roman

(!19) NewCenturySchlbk-Bold (!20) <Undefined-User-Font>

常用的Device命令关键字

大部分输出设备能允许以下关键字被用于Device命令(Z 设备例外)。(www.loach.net.cn]以下为想要知道的关键字。对于某个特定输出设备所使用的其它关键字可以查阅IDL在线文档资料。例如,PS设备能接受将近50种不同的关键字。

Close_Document 这个关键字在刷新了输出缓冲区后关闭图形文档。它被用于从打印机中

排出打印页(使用Printer设备时)。

Close_File 这个关键字在刷新了缓冲区之后关闭该图形输出文件。

扩展:idl / idl教程 / idl程序设计

Filename 图形输出设备如果是将输出写入一个文件时有一个缺省文件名。如果没

有指定文件名时就使用该文件。一般情况下,该文件名为idl.option,

option是所选择的硬拷贝输出设备类型。但也可以使用此关键字指定一

个文件名来更改它。例如:

IDL>Device,Filename='surface.eps'

Inches 如果设置了这个关键字,那么关键字XSize、YSize、XOffSet和YoffSet

及其设置都被认为是以英寸为单位而不是以缺省的厘米为单位来给定

的。

IDL>Device,XSize=4.0,/Inches

若要回到以厘米为单位来设定尺寸和偏移量,用:

IDL>Device,Inches=0

Landscape 该关键字表示在纸的横向上输出。

Portrait 该关键字表示在纸的纵向上输出。这是缺省值。

XOffSet 该关键字确定输出的显示窗口的左下角在纸上的X方向上位置(在纵向

模式下)。关于横向模式下的位置详见199页的“计算 PostScript

在横向模式下的偏移量”。

XSize 该关键字确定输出显示窗口在纸上的宽度。

YOffSet 该关键字确定输出的显示窗口的左下角在纸上的Y方向上位置(在纵向

模式下)。关于横向模式下的位置详见199页的“计算 PostScript

在横向模式下的偏移量”。

YSize 该关键字确定输出显示窗口在纸上的高度。

IDL>Device,XSize=4.0,YSize=7.0,/Inches

注意一旦在图形输出设备上设定了某个关键字的值,该参数将一直有效,直到显式地更改它或退出IDL。

XSize,YSize,XOffSet和YOffSet这些关键字一般是用于在输出页面上定位“图形窗口”。 IDL命令使用图形窗口的方式和图形输出使用位于显示设备上的图形窗口的方式完全一样。详细细节 153

IDL IDL入门教程

参考184页的“显示设备与PostScript设备的相似之处”。(www.loach.net.cn)

创建PostScript文件

当前的图形设备总是储存在系统变量!D.Name中,所有图形命令将指向它。因此,实际上,尤其在IDL的程序中,选择一个硬拷贝输出设备的代码一般都类似于下面这个例子,在这个例子中,先创建数据然后送到名为output.ps的一个PostScript文件中。注意,设备如何选择,如何配置,如何在图形命令写入文件后关闭。

如果在关闭被打印机处理的文档时失败,它就不能将该页从打印机中排出,因为该文档缺乏PostScript的 Showpage命令。该命令用于将该页排出。如果使用的是一台慢速打印机,这点就可不必关心。可以在打印机工作的时候,站起来喝一杯咖啡。

IDL> data= LoadData(1)

IDL> thisDeivce=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, Filename=’output.ps’, XSize=4, Ysize=4,$

/Inches, Xoffset=2.25, Yoffset=3.5

IDL> Plot, data

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

将图形送到硬拷贝设备中

对于制作硬拷贝输出的一般概念是,用Set_plot命令选择硬拷贝设备,,用Device命令并按要求设置该设备(或者在使用打印机的情况下,有时可用Dialog_PrinterSetup),执行和用于输出到显示设备相同的IDl命令。然而这些命令将输出到文件或打印机,而不是输出到显示设备上。当完成调用IDL的图形命令后,关闭输出文件或打印任务,并以便把文件传送指向所选择的打印机或绘图仪。如果是使用打印机,路由传送是自动完成的。(详见181页的“打印PostScript文件”的关于将IDL产生的PostScript文件传送到打印机的章节)。

例如,要创建一个PostScript文件,可以键入如下命令:

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, Filename=’plot.ps’, XSize=4, Ysize=4, $

/Inches, Xoffset=2.25, Yoffset=3.5

IDL> Plot, LoadData(1)

IDL> Device, /Close_File

如果在UNIX机器上产生这个PostScript文件,可以调用一个简单的lpr命令来将文件导向打印机(或者,机器上任何的等效命令)。例如,从IDL中,可以键入这条命令:

IDL> Spawn, ‘lpr plot.ps’

广义上讲,在显示设备上的输出和PostScript文件中的输出并非完全不同。(麻烦在于细节)。也就是说,Plot,Surface,Contour以及其它IDL图形命令无论是在显示设备上还是在PostScript文件中操作几乎一样。

一方面,它们在如何进入文件这方面相似。例如,如果调用Plot或Surface命令,并且显示设备为当前工作的图形设备,当前窗口的内容将被擦除,建立一个新的图形显示。在PostScript 154

IDL IDL入门教程

设备为当前图形设备时,类似的情况也会发生。(www.loach.net.cn]每个命令将擦除显示设备上的窗口,启动一个新的PostScript输出页面。

相应地,每个图形命令,例如Oplot或XoutS,将在当前显示窗口中被执行,修改当前PostScript输出页面。例如,要显示一幅用XYOutS命令创建的带标题的图和一幅曲面图,可按如下键入: IDL> Plot, LoadData(1), Position=[0.1,0.1,0.9,0.8]

IDL> XYOutS,0.5,0.9,’Simple Plot’,Align=05,/Normal

IDL> Surface,Dist(41)

如果这些命令被用到PostScript设备上而不是显示设备,将得到有两页输出的一个文件。要将这些命令的结果送到一个PostScript文件,可以按如下键入命令:

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=3, Ysize=3, /Inches

IDL> Plot, LoadData(1), Position=[0.1,0.1,0.9.0.8]

IDL> XYOuts,0.5,0.9,’Simple Plot’, Align=0.5,/Normal

IDL> Surface, Dist(41)

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

注意,无论是在显示窗口还是在PostScript窗口,都可以用关键字Position和Normal来放置图形显示单元。这是一种用于创建在显示窗口的输出和在PostScript窗口的输出相同的好方法。关于此一会将学到更多的东西。

扩展:idl / idl教程 / idl程序设计

有个技巧可以用于强制让PostScript文件前进一页,这就是使用Erase命令。如果要将几幅图像放在一个PostScript文件中,这也很方便。正常情况下,TV或TVScl命令不擦除前面窗口的内容,所以,多个TV命令将简单地在前一幅图像上面叠放下一幅。Erase命令可以将多幅图像放在同一文件中的不同页面。如下:

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> TV, LoadData(5)

IDL> Erase

IDL> TV, LoadData(7)

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

打印PostScript文件

从UNIX机器上打印PostScript文件的最简单方法是调用lpr命令(或机器上其它任何的等效命令)。但很可能犯一个的错误,企图在IDL中用类似如下的命令来打印PostScript文件: Set_Plot, ‘PS’

Device, Filename=’new_plot.ps’

Plot, my_data, Title=’Awful Nice Plot’

Spawn, ‘lpr new_plot.ps’

不幸的是,这样行不通。原因是在试图用UNIX命令lpx打印PostScript文件之前,忘了关闭它。正确的顺序是:

Set_Plot, ‘PS’

Device, Filename=’new_plot.ps’

155

IDL IDL入门教程

Plot, my_data, Title=’Awful Nice Plot’

Device, /Close_File

Spawn, ‘lpr new_plot.ps’

上面第一组命令失败的原因是,PostScript文件需要一个PostScript Showpage 命令来从打印机中排出该页面。(www.loach.net.cn)然而,只有使用Close_File命令或退出IDL,Showpage 命令才被插入到PostScript中。

在个人电脑上,在IDL中打印用PostScript设备产生的PostScript文件则有一点难度。事实上,大部分人都不介意,因为现在已有无数适合这种机器的工具来打印PostScript文件。

在运行MacOS系统的计算机上打印PostScript文件

在一个Macintosh机器上或是运行MacOS操作系统的计算机上,将一个PostScript文件送到打印机的最好方法是从Bare Bones 软件公司下载一个免费的小程序Drop·PS。

可以从一般的Macintosh匿名的ftp软件网站下载。Macintosh打印机总是附带一些打印工具,也能直接将PostScript文件传送到打印机上。

在Windows计算机上打印PostScript文件

在Windows 95和Windows NT操作系统上,将一个PostScript文件送到打印机的最好方法是下载一个免费的软件 GhostView或GhostScript。这些工具是PostScript文件浏览器,可以在打印前预览输出。GhostView有一个漂亮的图形化界面,可以把PostScript文件直接传入打印机。详细资料可用通过WWW浏览器访问下列网址:

生成封装的PostScript文件输出

要生成能包含在其它文件(比如杂志文章和书等)中的PostScript输出,在输出图形到PostScript文件前,必须在选择封装选项:

Set_Plot, ‘PS’

Device, /Encapsulated

IDL的封装PostScript文件可以成功地放到LaTeX,Microsoft Word,FrameMaker和其它一些文字处理文档中。

注意,PostScript封装文件不能在PostScript打印机上通过自己打印,尽管打印机在不停地动。因为,封装文件缺少PostScript的Showpage命令,此命令用于从打印机上排出该页面。这些文件必须被包含或‘封装’在其它文件中进行打印。

同时也要注意,当将封装文件输入另一个文件中时,可能无法看到该图形,直到打印出来。除非有一个PostScript预览器或者用下面将要描述的Preview关键字。

156

IDL IDL入门教程

关闭封装PostScript文件,可以将Encapsulated设置为0,如下;

Device, Encapsulated=0

封装PostScript图形的预览

在正常情况下,封装的PostScript文件不能在包含它的文档中显示。(www.loach.net.cn]也就是说,拥有此输入文件的方框总是白色或灰色。然而,PostScript图形在整个文档被送到PostScript打印机时,可以正确的打印。

如果想让文档中的该图形能看见,必须设定Preview关键字。此关键字能让PostScript驱动程序包含一幅该图形的位图和PostScript描述。这幅位图将显示在文档中的方框内,在文档被打印时就使用PostScript描述。

Device, /Encapsulated, /Preview

注意并非所有的文字处理程序都能显示位图预览图像。例如,预览图像在Microsoft Word 5.1或Macintosh机器上的FrameMaker 4.0上不能很好地显示。但在Windows NT机器上的FrameMaker

5.1就能很好地显示。用自己的文字处理软件试一下,看看显示如何。

要将预览关闭,将关键字Preview设为0即可。如下:

Device, Preview=0

注意,在IDL5.2中预览功能在Macintosh和Windows中有很大的提高。把Preview关键字值设为2,便可以创建一个带有一幅TIFF预览图像的封装PostScript交换文件。文件的PostScript部分用于在PostScript打印机上打印。

生成彩色的PostScript输出

IDL中支持彩色的PostScript输出。要输出彩色的输出,在PostScript设备上使用Color关键字:

Set_Plot, ‘PS’

Device, Color=1

颜色关键字的设置自动地将当前色彩表复制到PostScript文件中。(类似于下面Set_Plot命令中的Copy关键字。)注意PostScript设备几乎总是支持256色,通常多于在显示设备上使用的颜色数。这将影响输出。详见191页的“问题:PostScript设备拥有比显示设备更多的颜色”

另一个自动装载色彩表的方法是在将图形设备设置为PostScript时,使用带Copy关键字的Set_Plot命令:

IDL> Set_Plot, ‘PS’, /Copy

这个命令在文件被打开的第一次操作时,自动地将当前的颜色矢量复制到PostScript文件中。注意,是显示色彩表被拷贝到PostScript文件中。通常这些色彩表的颜色数目和PostScript文件的色彩表的数目不同。详见189页的“问题:PostScript设备使用背景与绘图颜色的区别”。

扩展:idl / idl教程 / idl程序设计

一旦设定PostScript设备为当前图形设备,可以用归一化的色彩表装载命令来装载色彩表。例如,可以键入如下命令:

IDL> LoadCT, 5, Ncolors=200

IDL> TVLCT, [70,255],[70,255],[70,0],200

要将颜色选项关闭,可将Color关键字设为0,如下:

Device, Color=0

157

IDL IDL入门教程

PostScript中的彩色图像与灰度图像

缺省情况下,PostScript设备为每一图像像素保存4位的信息。[www.loach.net.cn]这对16色或灰度级是足够了。如果想在PostScript输出中能有更多的颜色,Device 命令的Bits_Per Pixel关键字能设置到8位。例如,要输出一幅使用了全部256色的图像,可以如下设置设备:

IDL> image=LoadData(7)

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, Color=1, Bits_Per_Pixel=8

IDL> TVSCL, image

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

真彩图像

读者的PostScript设备也许能支持24色或真彩图像。真彩图像是一个3D的数组,其中有一维是3。例如,一幅m*n的真彩图像可以是隔像素扫描(3,m,n),也可以是隔行扫描(m,3,n),还可以是隔波段扫描(m,n,3)。

真彩图像可以以显示在显示器上的相同方式来显示在PostScript中。就是,在TV或TVScl命令中使用True关键字,以表明真彩图像如何扫描的。确保将Bits_Per_pixel关键字的值设为8。例如,一幅隔像素扫描真彩图像可以送到一个真彩PostScript设备上:

IDL> image3d=LoadData(16)

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, Color=1, Bits_Per_Pixel=8

IDL> TV, image3d, True=1

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

在继续阅读本章的内容前,确保当前的图形输出设备是显示设备。若不能肯定,使用以下命令:

IDL> Set_Plot, ‘X’ ;或 ‘Win’ 或 ‘Mac’

在PostScript设备上创建高质量的输出

创建看上去类似于显示设备输出的高质量硬拷贝输出的秘诀在于要理解显示设备和输出设备之间的共同点与不同点,比如,考虑显示设备和PostScript设备之间的相同点。

158

IDL IDL入门教程

显示设备和PostScript设备之间的相同点

最明显的相同点就是在每种设备上为显示图形而创建的图形窗口,尽管在每种设备上使用的方法不同。(www.loach.net.cn)在一般的显示设备上可能会以下面的代码来创建图形窗口:

Window, XSize=300, YSize=400, XPos=100, YPos=200

创建了一个X方向300像素和Y方向400像素的图形窗口。此窗口的左下角位于显示器(比如,显示器的分辨率为1024*768)的(100,200)处。

而在PostScript设备上创建一个图形窗口的操作是类似的。区别在于不是使用Windows命令来创建。(当PostScript设备为当前图形输出设备时,Window命令是个无效的命令,这点在写程序的时候必须记住。)而是用Device命令来告诉PostScript设备要创建的窗口的大小。例如:

Device, XSize=3, YSize=4, XOffset=1, YOffset=2, /Inches

可以这么想,PostScript页面类似于整个显示设备,而用Device命令创建的区域类似于显示设备上的图形窗口。换句话说,调用上面的命令的含义是在显示设备上或在将要输出图形的页面上创建一个位置。

IDL使用归一化原则把图形放入任何一个窗口。那么,:当键入以下这样一个命令后会怎么样?

Plot, FindGen(11)

IDL使用归一化原则来将图形定位于窗口中。在此例中,IDL用设备坐标来计算字符的大小,并通过它来决定图形缺省的边缘。该图形在这个边缘的基础上被置在窗口内,一般是正好填满整个图形窗口。

但是在显示设备上的图形与在PostScript设备上的输出图形是否一样呢?尽管会很相似,很可能不是。原因在于在显示设备上对图形的解释方式与PostScript设备不一样。

显示设备与PostScript设备之间的不同点

显示设备与PostScript设备之间有几个不同点,这对于要想在PostScript设备上输出与显示设备上几乎一样的图形是非常关键的。有一两个例外的情况,它们的差别不是很大,或者这些差别看上去不重要。但是以笔者个人的经验来看,若没有理解这些差异,要想生成高质量的硬拷贝输出将费很大的精力。

问题:PostScript窗口可能会有不同的纵横比例

首先,一个相对较小的不同点,在显示设备上创建的图形窗口与在PostScript页面上创建的图形窗口的纵横比例可能不同。这并不奇怪,因为两个窗口的创建方法不同:显示设备上是使用Windows命令,PostScript页面是使用Device命令。

事实上,大部分使用者在IDL中显示图形时不用Windows命令。而是简单地使用Plot或Surface命令以及它们打开的一个窗口。缺省窗口的大小随机器不同而不同,而且可由用户设置。在工作站上,缺省的窗口大小为640*512像素大小。在PC上,缺省的窗口大小通常为显示设备尺寸的四分之一。在PostScript设备上,缺省的窗口大小为7*5英寸。三种情况下的纵横比(Y/X)为0.800,0.750和0.714。

很清楚,同样Plot命令的输出在三个窗口中不同,因为三个窗口纵横比不一样,而且,IDL将填满Plot命令所获得的窗口。

159

IDL IDL入门教程

解决方法:让图形窗口的纵横比保持不变

所以,创造完全一样的输出的第一条原则就是确保显示窗口和PostScript窗口具有相同的纵横比。(www.loach.net.cn)这很容易做到。只要计算当前显示窗口的纵横比,并将PostScript窗口设为一致即可。例如(假设在显示器上已经有一个打开的窗口),可以键入:

IDL> aspectRatio=Float(!D.Y_Vsize)/D.X_Vsize

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=5, Ysize=5*aspectRatio, /Inches

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

这样,在显示窗口与PostScript输出中看上去相同的可能性就比以前大多了。

笔者喜欢用PSWindow程序来创建一个与当前显示窗口有相同纵横比的PostScript图形窗口。(pswindow.pro在已经下载的本书配套文件之中)。该程序返回在PostScript页面上创建所能创建的最大图形窗口时所必须的尺寸和偏移量(默认以英寸为单位),所建立的窗口与当前图形窗口具有相同的纵横比。返回值用来设置Device命令的相应关键字,通常是它的_Extra关键字。(关于_Extra关键字详细信息见240页的“使用关键字继承”)

扩展:idl / idl教程 / idl程序设计

看看它是如何使用的,首先打开一个图形窗口,并显示一幅线画图。

IDL> Window, XSize=400, YSize=300 ; Aspect Ratio=0.75

IDL> curve=LoadData(1)

IDL> Plot, Curve

现在用相同的纵横比创建一个PostScript窗口,并在上面画图。键入:

IDL> rightSize=PSWindow()

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, _Extra=rightSize, /Inches, File=’test.ps’

IDL> Plot,curve

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

如果有PostScript打印机或PostScript预览软件,将此文件传给它。比较输出内容和显示窗口中的内容。一样吗?

不同?但相似吧?请继续!

问题:PostScript设备有更高的显示分辨率

在默认状态下,IDL在字符尺寸的基础上计算出图形的边缘,从而决定将图形的坐标轴放在图形窗口的何处。但用于计算边缘的字符尺寸在PostScript设备上和在显示设备上不一样。 原因是PostScript设备有一个比显示设备更精细的分辨率。可以检查IDL系统变量!D.X_PX_CM(决定每厘米的像素个数)和!D.X__CH_SIZE(以设备坐标决定缺省字符X方向上的尺寸),看看有何不同。输入:

IDL> thisDevice=!D.Name

IDL> Print, !D.X_PX_CM, !D.X_CH_SIZE

160

IDL IDL入门教程

IDL> Set_Plot, ‘PS’

IDL> Print, !D.X_PX_CM, !D.X_CH_SIZE

IDL> Set_Plot, thisDevice

例如,在Macintosh计算机上的数据为:

Mac: 28.35 6

PS: 1000.00 222

换句话说,在显示屏上的一个像素,在PostScript上就有大约35个像素。[www.loach.net.cn)而且,两种设备的字符尺寸相对于分辨率的比率也不一样的。Macintosh是:0.212和PostScript 是0.222。

马上就明白了,在IDL中用设备坐标或像素坐标来定位任何一图形是个不好的主意,除非将分辨率因素考虑进去。例如,假设想在显示器上围绕一个X方向像素从100到200,Y方向像素从150到250的图像周围画一个方框,可能会这样画:

xBox=[100,100,200,200,100]

yBox=[150,250,250,150,150]

PlotS, xBox, yBox, /Device

在一个400*400相似素的显示窗口上,方框与窗口的大小之比为1:16。在10*10厘米的PostScript窗口上,方框与窗口的大小之比将为1:10,000!这是相当小的盒子,肯定不是所要的。

解决方法:不用设备坐标来定位图形

创建实际一致的图形输出的第二条原则是,确保在输出窗口中使用数据或归一化坐标而不是设备坐标来在图形窗口中定位图形。

例如,如果按下面这样定义上面的方框,它将在显示窗口和PostScript窗口两者中包围同样的相对区域:

xBox=[0.250, 00,200,200,100]

yBox=[150,250,250,150,150]

PlotS, xBox, yBox, /Device

字符尺寸与分辨率的比率影响输出的另外一种方式是,在图形窗口中放置图形输出的方法。回想一下,缺省情况下,IDL使用边缘来在窗口中定位图形以及基于字符尺寸计算边缘。如果字符尺寸在显示设备上和在PostScript输出中不同,这将稍微影响图形输出。

但可以用Position关键字来定位图形从而弥补这一点。 无论是使用显示窗口还是PostScript 窗口,都可用归一化坐标来将坐标轴定位在准确的地方。(详见50页的“设置图形位置”。)

上面简单的图可以使用以下命令以相同的方式放到任何一个窗口中。

Plot, Load Data(1), Position=[0.1, 0.1,0.95,0.95]

问题:PostScript设备能使用不同的显示字体

缺省时,IDL使用Hershey字体输出图形。Hershey字符集是一种典型的矢量字体。这些字体是用一些矢量描述的,显示时象被着色一样。使用矢量字体有两大优点:它们能很容易地在3D空间中缩放和旋转,而且与设备无关。矢量字体最大的缺点是:在象PostScript打印机这类高分辨 161

IDL IDL入门教程

率输出设备上,其质量不如真实的PostScript字体。(www.loach.net.cn)

正因为如此,许多人趋向于用PostScript字体来输出 PostScript输出图形。这就产生了在显示屏上与在PostScript输出中稍有差异,因为在 PostScript打印机上没有与Hershey矢量字体一一对应的字体。PostScript字体必须代替Hershey字体。这导致字体字符的大小不同,这可能导致文字排列的问题。

解决方案:仔细设计和定位文字

这个解决方法就是要注意如何设计文本的输出。例如,如果可能的话,标题应以点为中心或者在XYOutS命令中用Alignment关键字来特意布置在某些点上。可以从图65中看出Hershey和真实的PostScript字体的区别。

图65:左边的图是用Hershey Simplex Roman字体创建的。右边的图是用PostScript

Helvetica字体创建的。两幅图看上去类似,但不同。

要选择一种真实的PostScript字体,可将系统变量!P.Font或Font关键字设为0。若没有其它信息的话,IDL将根据表10的映射关系将Hershey字体映射到PostScript字体。可以使用以下命令随时查询IDL中的这种映射关系:

Set_Plot, ‘PS’

Help, /Device

注意,下表中的正常的默认字体Simplex Roman被映射到 Helvetica。在PostScript的输出中,在同样的字体尺寸下,Helvetica的字体稍大于Simplex Roman。也就是说,应在图形输出时,使用代替字体定位文本必须小心,要用一种合理的从显示到硬拷贝的过度方式。

实际中,这就意味着要将调节输出文本左右或中心对称的坐标归一化。例如,下面的代码是在图上生成一个图例:

Plots, [0.2,0.3], [0.7,0.7], /Normal

Plots, [0.2,0.3],[0.6,0.6], LineStyle=3, /Normal

XYOutS,0.32,0.7, ‘Normal bias’, /Normal, Alignment=0.0

XYOutS,0.32,0.6, ‘No bias’, /Normal, Alignment=0.0

这个代码用来在显示窗口和PostScript窗口两者相同的相对位置处定为文本。

注意,能用Device命令改变字体映射关系。如:字体!4一般被映射为Helvetica-Bold PostScript字体。若要改为Palatino-Bold-Italic字体,应键入:

扩展:idl / idl教程 / idl程序设计

Device, /Palatino, /Bold, /Italic, Font_Index=4

将Palatino-Bold-Italic字体用于上述的图例中,可键入。

162

IDL IDL入门教程

Plots, [0.2,0.3], [0.7,0.7], /Normal, Font=0

Plots, [0.2,0.3],[0.6,0.6], LineStyle=3, /Normal

XYOutS,0.32,0.7, ‘!4Normal bias!X’, /Normal, Alignment=0.0

XYOutS,0.32,0.6, ‘!4No bias!X’, /Normal, Alignment=0.0

在上面的字符串中的!X将字体返回为使用!4之前的字体。[www.loach.net.cn)关于 XYOutS命令的用法详见55页的“在图形显示中添加文本”。

序号字体字体

!9 Math and Special Symbol

表10:缺省的Hershey字体与PostScript字体之间的映射关系。Hershey字体一般用于显

示设备上。PostScript字体一般用于PostScript输出。

问题: PostScript设备使用背景颜色和绘图颜色时的不同

另外一个显示设备与PostScript设备的不同是PostScript处理颜色的方式不同。例如,PostScript设备把正常的背景颜色和绘图颜色反过来。在显示器上输出时,背景色是由!P.Background系统变量控制的。当启动IDL时,该变量被设为0,意味着IDL使用当前色彩表中的第0号颜色作为背景颜色。IDL带的大多数的色彩表的0号色都是黑色。黑色的背景下,当把图形输入PostScript设备时,使耗用很多色粉。因此,当将PostScript设备设为当前图形设备时,IDL自动将此变量的值改为255。

但是IDL的PostScript输出处理比这更隐蔽,因为,无论用什么颜色索引值替换255,只能获得一份白色背景的PostScript输出。也就是说,PostScript 实际上除了白色忽略了其它任何背景颜色。即使将背景颜色设为不是255的值,上面这个规则仍然起作用。例如,可以尝试用以下命令在获得一个具有碳灰色背景的绿色图形:

TVLCT,[0,70],[255,70],[0,70],100

Plot, LoadData(1), Color=100, Background=101

163

IDL IDL入门教程

然而,这两个命令在显示屏上输出正确,但在PostScript输出中是一白色背景的绿色图形。(www.loach.net.cn) 解决方法:理解PostScript如何处理背景颜色和绘图颜色

在PostScript输出中得到其它颜色的背景的唯一方法是,把某种特定的颜色作为背景图来着色处理。例如,可能用PolyFill命令,如下:

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, /Color, Bits_Per_Pixel=8, File=’example.ps’

IDL> TVLCT, [0,70],[255,70],[0,70],100

IDL> PolyFill,[0,1,1,0,0], [0,0,1,1,0], /Normal, Color=101

IDL> Plot, LoadData(1), Color=100, /NoErase

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

绘图颜色同样可以被PostScript设备改变了。系统变量!P.Color通常是设为色彩表中的最后一种颜色。在显示设备上等于!D.Table_Size-1。例如,运行IDL时有220种颜色,!P.Color的值就为219。而如果选择了PostScript设备,!P.Color的值总是0。

这意味着,如果用IDL缺省时装载的灰度色彩表在显示设备上画图,将看到黑色背景下的白色图形。如果在PostScript文件中做同样的事情,将看到白色背景下的黑色图形。如图66所示。

绘图色总是受到PostScript设备的尊重,这点不象背景色。所以,能够用!P.Color指定一个除0以外的颜色,就可以在显示器上和PostScript中用该颜色着色图形。也可以使用Color关键字和一个给定的值来画图,这个颜色将同时受到显示设备和PostScript的尊重。例如,下面两行命令总是绘出红色图形,不管是显示器上还是在PostScript输出中:

TVLCT, 255,0,0, 100

Plot, LoadData(11), Color=100

注意,在灰度打印机上,颜色将由经过抖动处理的线条代替,至于是显示点还是虚线,取决于打印机的分辨率。实际中,这意味着如果要在灰度打印机上输出,最好确保绘图颜色是黑色。

图66:PostScript将背景色和绘图色反色。因此,在显示上是黑底白图,但在PostScript上

是白底黑图。

164

IDL IDL入门教程

问题:PostScript设备的颜色数目多于显示设备

PostScript设备与显示设备的另外一个不同的方面是它们所使用的颜色数。(www.loach.net.cn)PostScript通常至少能显示256种颜色。一般情况下,用户在显示设备上使用的颜色少于256种。可以通过打开一个图形窗口并打印系统变量!.D.Table_Size的值来得知现在IDL所使用的颜色数量,如下:

IDL> Window

IDL> Print, !D.Table_Size

通常,这个颜色数量在200到240之间。如果在一个8位显示卡的PC机上运行IDL,这个值总是少于256色。在具有8位显示卡的其它计算机上,这个值将会更少,除非有自己的色谱表。如果在显示数据时不注意,这个差别就会影响输出。

例如,当在IDL运行中使用200种颜色,而且想用一个灰度色彩表来显示图像。可以按如下装入色彩表:

IDL> LoadCT, 0

这个命令在色彩表文件中查找组成灰度色彩表的红、绿、蓝颜色矢量,并重采样这些矢量,以便它们可以代表IDL运行时所使用的颜色。在此例子中,被载入实际的色表中的矢量为200元素。要显示图像,可按如下这样:

IDL> image=LoadData(7)

IDL> TVScl, image

它看上去像图67中左边的图像。

如果想将这幅图像存到PostScript文件中,可以将PostScript设备设置为当前图形设备(用Copy关键字将当前的色谱表拷贝到PostScript文件中),并从新调用上面的TVScl命令。例如,可按如下操作:

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’ , /Copy

IDL> Device, XSize=3, Ysize=3, /Inches, /Color, $

Bits_Per_Pixel=8, File=’image.ps’

IDL> TVScl, image

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

然而,如果这样做,可能会对结果很失望。输出可能类似于图67中右边的图像。这就是说,PostScript输出上的灰度阴影不同于显示设备上的灰度阴影。

扩展:idl / idl教程 / idl程序设计

产生这种现象的原因在于色彩表装入到PostScript设备时的方式。当调用Set_Plot命令时,IDL将显示色彩表的前200种颜色复制到PostScript色彩表中相应的颜色中去。(无论是否使用Copy关键字都是这样。)但它并不影响颜色索引 号在200以上的颜色,这些颜色早就被初始化为灰度

看看体现在PostScript文件中的红色矢量的图就知道发生什么了。这个矢量应该是线性型级颜色。

的,但在图68中,索引号为200时极不连续。那么图像中像素值大于199的像素在PostScript输出中将用不正确的颜色显示。

165

IDL IDL入门教程

图67:如果不注意颜色,显示设备上的输出(左图)将不同于PostScript文件输出(右图)。(www.loach.net.cn)特别是,像素值大于显示设备所使用的颜色数的像素将着色不正确。在此例中,很多像素显示的太

亮。

图68:在将PostScript设备设置为当前图形设备后的红色矢量图。色彩表中的前200种颜色是从显示设备的色彩表中拷贝过来的。所以,像素值大于199的像素在PostScript将显示的不正确。

解决方法:在PostScript输出中确保恰当地缩放数据

这个问题可以用两种方法来解决。第一,一旦将PostScript设备设置为当前图形设备时,可以重新装载色彩表。或者,确保将图像数据缩放到显示设备所能得到的颜色范围内。重新装载色彩表将使显示设备上的输出与PostScript输看上去几乎一样。为了使输出完全一样(当然,是在各种颜色发生技术的约束条件下),有必要将数据缩放到显示设备所能得到的颜色数量范围内。如果色彩表和数据一样,输出也将一样。(关于正确缩放数据参见66页的“缩放图像数据”。)

注意,在缺省情况下,在PostScript图像中每个图像像素只保存四位信息。这意味着,即使PostScript设备能够显示256色,但在输出图像中将只能看到16色。如果想看到全部256色,必须储存8位的像素信息。可以用Bits_Per_Pixel关键字在Device命令中设置,如下:

Device, Bits_Per_Pixel=8, Color=1

问题:PostScript设备显示图像时的不同

显示设备与PostScript设备的另外一个不同点是显示图像时的区别。尤其是,显示设备具有固定尺寸的像素,而PostScript设备具有可变的像素尺寸。换句话说,在PostScript中一个像素实际上可以是任意矩形尺寸。这会影响图像输出到PostScript文件中的方法.

PostScript设备根据PostScript画图窗口的尺寸和图像的纵横比来决定图像的大小。例如,如果PostScript的绘图窗口为2*2英寸,并且要输出的图像为360*360像素,那么一个简单的TV命 166

IDL IDL入门教程

令就能输出2*2英寸的PostScript图像:

IDL> thisDevice=!D.Name

IDL> image=LoadData(7)

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=2, Ysize=2, /Inches, /Encapsulated

IDL> PlotS, [0,1,1,0,0],[0,0,1,1,0], /Normal

IDL> TV, image

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

上述命令产生的输出如图69所示。[www.loach.net.cn)

图69:PostScript设备用可变的像素来使图像适应输出窗口的尺寸。这里的尺寸为2*2英寸。

然而,如果输出窗口尺寸与原图像的纵横比不同时,图像将改变尺寸以保证自身的纵横比,其中有一方向将完全填满输出窗口。例如,同样使用上述图像,这里的输出窗口为X方向1英寸,Y方向2英寸。

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=1, Ysize=2, /Inches, /Encapsulated

IDL> PlotS, [0,1,1,0,0],[0,0,1,1,0], /Normal

IDL> TV, image

IDL> Device, /Close_File

这些命令得到的结果见图70。注意,此图像只有1*1英寸,只填充了输出窗口的一半。

图70:当输出窗口和原图像具有不同的纵横比时,图像将改变尺寸以维持自身的纵横比,并

且其中的一个方向将充满整个输出窗口。

167

IDL IDL入门教程

类似地,如果有一个2*1英寸的输出窗口,如下:

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=2, Ysize=1, /Inches, /Encapsulated

IDL> PlotS, [0,1,1,0,0],[0,0,1,1,0], /Normal

IDL> TV, image

IDL> Device, /Close_File

结果见图71。[www.loach.net.cn)

图71:此图类似于图70,除了输出窗口的X方向是Y方向的两倍外。

如果PostScript绘图窗口是X方向1英寸和Y方向3英寸,那么TV命令输出的结果是1*1英寸的图像。

事实上,图像总是根据输出窗口的尺寸以及原图像的纵横比来确定大小可能会造成困难。例如,假设有一个500*500像素的显示窗口,并且想将图像显示在400*400像素大小的窗口的中心。更进一步假设,要在图像的周围画一外框。可能会用以下命令在窗口中定位显示图像: IDL> image=LoadData(7)

IDL> image=Congrid(image, 400, 400, /Interp)

IDL> Window, XSize=500,Ysize=500

IDL> TV, image, 0.1, 0.1, /Normal

IDL> Plot, FindGen(100), /NoData, /NoErase, $

Position=[0.1,0.1,0.9,0.9]

如果当前图形窗口为显示设备时,可以看到图72所示的输出。

图72:在显示设备上带边框的图像。

但如果在PostScript设备上运行这些命令(不是用Window命令),将得到非常不一样的结果。 168

IDL IDL入门教程

扩展:idl / idl教程 / idl程序设计

尤其是,图像根据输出窗口尺寸改变大小,很可能导致图像的外框的位置不对,如图73。[www.loach.net.cn)

解决方法:使用TV命令设置图像大小

设置将进入PostScript输出中的图像尺寸的正确方法是在TV命令中使用的XSize和YSize关键字。例如,要在PostScript输出中得到与图72具有相同输出的正确方法如下:

IDL> thisDevice=!D.Name

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=3.5, Ysize=3.5, /Inches, /Encapsulated

IDL> TV, image, 0.525, 0.25, XSize=2.8, Ysize=2.0, /Inches

IDL> Plot, FindGen(100), /NoData, /NoErase, $

Position=[0.15,0.10,0.95,0.90]

IDL> Device, /Close_File

IDL> Set_Plot, thisDevice

图73:在PostScript输出中,图像的尺寸是根据输出窗口的尺寸来决定的,这可能并非

用户想要的,如本图所示。

图74:在PostScript窗口中,缩放和放置图像的正确方法是利用TV

命令的缩放和

定位的能力。将此图与图73比较一下。

如果想编写一个通用的IDL程序,就象上面这个,无论窗口大小如何变化都能正常工作,无论是在显示设备上还是在PostScript文件中,也同样能工作。这时候,也许需要计算图像在显示窗口中基于设备坐标的大小和位置。在PostScript设备上和在显示设备上工作时,唯一的真正区别在于如何计算图像的尺寸。程序imageax.pro就是用于此目的(此程序在下载的本书配套程序中)。

PRO ImageAx, image, Position=position

169

IDL IDL入门教程

IF N_PARAMS() EQ 0 THEN Message, 'Must pass image argument.'

IF N_ELEMENTS(position) EQ 0 THEN $

position = [0.2, 0.2, 0.8, 0.8]

; Get the size of the image in pixel units.

s = SIZE(image)

imgXsize = s(1)

imgYsize = s(2)

; Calculate the size and starting locations in pixels.

xsize = (position(2) - position(0)) * !D.X_VSize

ysize = (position(3) - position(1)) * !D.Y_VSize

xstart = position(0) * !D.X_VSize

ystart = position(1) * !D.Y_VSize

; Size the image differently in PostScript.

IF !D.NAME EQ 'PS' THEN $

TV, image, xstart, ystart, XSize=xsize, YSize=ysize ELSE $

TV, Congrid(image, xsize, ysize, /Interp), xstart, ystart

; Draw the axes around the image.

Plot, FIndGen(100), /NoData, /NoErase, Position=position

END

打开几个不同尺寸的窗口运行该程序,输出依次显示在每一个窗口中。(www.loach.net.cn)注意,图像的纵横比不再保持了。反而,它在窗口的位置保持不变。

IDL> image=LoadData(9)

IDL> Window, XSize=400, YSize=400, /Free

IDL> ImageAx, image

IDL> Window, XSize=300, YSize=500, /Free

IDL> ImageAx, image

IDL> Window, XSize=600, YSize=300, /Free

IDL> ImageAx, image

可以运行这个程序,将输出结果传送到任何窗口,无论是显示设备还是PostScript设备都可以。例如,可以用下面的命令将输出送到PostScript文件中。

IDL> Set_Plot, ‘PS’

IDL> Device, XSize=3.5,YSize=2.5,/Inches, /Encapsulated

IDL> ImageAx, image, Position=[0.15,0.15,0.95,0.95]

IDL> Device, /Close_File

输出结果见图75。

想以完全独立于设备的方式来显示图像,笔者偏爱用TVImage程序(已下载的本书配套程序)。 170

IDL IDL入门教程

它不仅能用Position关键字按上面ImageAx的风格来在显示窗口中定位图像,同时如果愿意,还能保持图像的纵横比。[www.loach.net.cn)关于TVImage命令详见72页的“用归一化坐标定位图像”。

IDL> Window, XSize=600, YSize=400, /Free

IDL> TVImage, image, Position=[0.15,0.15,0.95,0.95], $

/Keep_Aspect_Ratio

图75:在3.5*3.5英寸的输出窗口中运ImageAx程序。注意,图像的纵横比不再保持了,尽管保

留了其在窗口中的位置。

在PostScript中显示图像的另一个极其重要的地方是,缺省情况下,PostScript设备对每个图像像素只保留四位的信息。这对16色或是灰度级的图像已经足够了。如果想要256色,应该将关键字Bits_Per_Pixel设为 8,像这样:

IDL> Set_Plot, ‘PS’

IDL> Device, Bits_Per_Pixel = 8, Color = 1

在横向输出模式中计算PostScript的偏移量

纵向模式下的PostScript文件偏移量为X方向上0.75英寸,y方向上5英寸。这就将图形输出到页面的上半部分。因此,非常容易就看出偏移量是基于页面的左下角计算出来。(见图 78。)键入下列语句就可以看出缺省的偏移量:

图76:纵向和横向模式下窗口的尺寸和偏移量。注意,在横向模式下,整个页面被旋转了90度,

并且偏移量(不是窗口尺寸)也随着一起旋转。

IDL> thisDevice = !D.Name

IDL> Set_Plot, ‘PS’

171

IDL IDL入门教程

IDL> Help, /Device

IDL> Set_Plot, thisDevice

然而当把图像横向输出为时,整页已被旋转了90度,包括页面的左下角!可以在图76中看到它们的缺省值。[www.loach.net.cn]

如果没有意识到偏移点已随页面旋转了,可能设置的偏移量会使图形超出页面。例如,想要使X和Y方向的偏移量都为1英寸,可以会这样做:

Set_Plot, ‘PS’

Device, XOffset = 1.0, YOffset = 1.0, /Inches, /Landscape

扩展:idl / idl教程 / idl程序设计

Plot, data

可以在图77中看到想象中作出的图形和实际上作出的图。确信自己明白了偏移量在横向模式下是怎样工作的。

图77:如果不注意横向模式下偏移量是怎样工作的,图形将被旋转偏出页面右边。

用PS_Form配置PostScript设备

已下载的本书配套程序中有一个名为PS_Form的程序。这个程序的目的是让用户能够交互式地决定将图形放在PostScript输出窗口的哪里,以及设置PostScript设备其它的配置。可以在图78中看到PS_Form的图示说明。

在右上角的绘图组件内的黄色方框代表PostScript页面。黄色框内的绿色框是PostScript页面上输出窗口的位置。用鼠标左键沿在页面内移动绿色方框。用鼠标右键画一个新的绿色框。

当设置好之后,点Accept按钮。PS_From将返回一个结构,此结构里面的字段都是Device命令的有效关键字。如下PS_Form被用来画一个简单图形的例子。

deviceKeywords = PS_Form (Cancel = canceled)

If canceled NE 1 THEN Begin

currentDevice = !D.Name

Set_Plot, ‘PS’

Device, _Extra = deviceKeywords

Plot, LoadData (1)

Device, /Close_File

Set_Plot, currentDevice

ENDIF

注意PS_Form的一个好特点是,当设备被设置为横向时,用户不必考虑偏移量的旋转问题。对用户来说,偏移量好像总是基于左下角算出来的。

PS_Form另一个好特点是,它可以记住上次设置。例如,像下面这样调用PS_Form,并更改它的配置。结束以后点Accept按钮。

IDL> setup = PS_Form ()

172

IDL IDL入门教程

要看设置的内容,键入:

IDL> Help, setup, /Structure

要用刚才的设置内容来启动PS_Form,键入:

IDL> newSetup = PS_Form (Defaults = setup)

要看PS_Form 是如何被应用的,可以试着调用XWindow程序,它也是下载的本书配套程序之一。[www.loach.net.cn)XWindow是一个“智能化”的图形窗口,它可以自我调整大小,可以载入只用于它自己的色彩表,也可以将它的输出送到PostScript文件中。可以这样来调用它:

IDL> XWindow, ‘Shade_surf’, LoadData(2), /Output, /XColors

尝试用XWindow程序将窗口里面的内容制作成一个PostScript文件。这本书余下的大部分内容将讨论如何编写一个类似于Xwindows的程序。

配置和使用打印设备

打印设备在IDL5.0中被引入介绍,最初不像其它图形输出设备能用Device命令来配置。Dialog_PrinterSetup命令是用来存取计算机上缺省打印机的配置参数。解释默认打印机如何安装和配置已经远远超出了本书要讨论的范围,但一般来说,打印机配置对话框提供了较多配置打印机自身的选项,但对于如何定位图形输出的选择相对较少。比如说,PostScript设备。

为了让用户在打印设备上有更多的选项来松定位图形输出,Research Systems公司在IDL5.1.1中为Printer设备引入了Device关键字。(注意,这些关键字仅适用于当向打印机发送直接图形命令时。)这些关键字XSize,YSize,XOffset和YOffset和其它硬拷贝输出设备中同名关键字很象,尽管不完全是。下面将指出它们的一些不同之处。

图78:弹出式组件程序PS_Form。这个程序为用户配置PostScript设备提供了交互式方法。此

图为如何配置PostScript设备来生成本书的大部分图形。

要存取默认打印机的配置,键入:

IDL> ok = Dialog_PrinterSetup ()

此对话框在Windows NT上如图79所示。

173

IDL IDL入门教程

图79:在Windows NT机器上的Dialog_PrinterSetup的对话框

在使用打印设备时,重要的是要知道,只有关键字Close_Document被用于Device命令时,输出内容才会被送到打印机上。[www.loach.net.cn)例如,生成一幅线画图命令的正确顺序类似于下面的代码。关闭打印机文档是必须的。如果忘了这条代码,不会输出任何东西。由于下面的代码中有一个IF循环,所以下面的代码必须放在一个文本编辑器中编辑,就像一个IDL主程序一样。将文件存为sendprinter.pro。可以在下载的程序中找到这个程序。

data = LoadData(1)

ok = Dialog_PrinterSetup()

IF ok THEN BEGIN

thisDevice = !D.Name

Set_Plot, 'PRINTER'

Plot, data

Device, /Close_Document

Set_Plot, thisDevice

ENDIF

END

如果想运行这个主程序并且把结果输出到默认打印机上,可以这样做:

IDL>. Run sendprinter

用打印设备定位图形

在Printer设备的第一版中,当将图形输出到默认打印机时,输出的图形常常充满了整页纸,常常看起来什么根本不象显示设备上的图形。实际上,不能控制将图形放在打印机的什么位置。例如,图像按设备精度打印时,其左下角位于页面的左下角。一个256乘256的图像用600 dpi的像素分辨率打印到一个PostScript打印机上时,经常只有0.5平方英寸,除非应用了合适的比例放大因子。像素在PostScript设备上,不会按比例地缩放到纸上。TV或TVscl命令中的XSize和YSize关键字当它们用于PostScript时同样不能缩放像素。(例子见 71页的“在PostScript设备上改变图像尺寸”)

这一点在IDL5.1.1种作了改进,对Device命令增加了关键字,可以和Printer设备一起被用来定位图形在纸上的位置和比例。像在PostScript设备中的同名关键字一样,Printer设备的关键字 174

IDL IDL入门教程

默认时用厘米单位。(www.loach.net.cn)(也可以设为英寸,如果Inches关键字被使用的话。)

在纵向输出模式下,XSize, YSize, XOffset和YOffset的默认值是(用英寸):

XOffset: 0.75 inches

YOffset: 5.0 inches

XSize: 7.0 inches

扩展:idl / idl教程 / idl程序设计

YSize: 5.0 inches

在横向输出模式中,默认值为:

XOffset: 0.75 inches

YOffset: 0.75 inches

XSize: 9.5 inches

YSize: 7.0 inches

读者立即会发现这些缺省值在Printer设备上和在PostScript设备上给出了相同的相对输出尺寸。但是也要注意,偏移量常常从页面的左下角计算出来。这是显而易见的,但是横向模式下PostScript偏移量不是这样计算的(见199页的“在横向输出模式中计算PostScript的偏移量”)。就意味着在编写一个既能创建PostScript文件又能将图形显示直接送到打印机的程序时,要格外注意横向模式下的偏移量。

为了帮助读者正确地计算这些关键字的值,Research Systems公司同时为Device命令引入了Get_Page_Size关键字,它可以用来返回一个包含打印设备页面的X方向尺寸和Y方向尺寸的两维矢量。奇怪的是,只能用设备坐标返回页面的尺寸,尽管页面和偏移量的关键词是用英寸或厘米来表示。因此,要想在页面上获得精确的输出结果,必须做些计算。

例如,如果想让一个图形在输出时占页面的80%,也可以键入这些命令:

IDL> thisDevice =!D.Name

IDL> Set_Plot, ‘PRINTER’

IDL> Device, Get_Page_Size = myPage

IDL> Device, XSize = myPage[0]*0.8, XOffset = myPage[0]*0.1, $

YSize = myPage[1]*0.8, YOffset = myPage [1]*0.1, /Device

IDL> Plot, Findgen (11)

IDL> Device, /Close_Document

IDL> Set_Plot, thisDevice

用打印设备输出图像

向Printer设备输出一张图像稍不同于PS设备向PostScript文件输出一张图像(见193页的“问题:PostScript设备显示图像时的不同”。)主要区别在于通过Printer设备输出的图像没有保持纵横比,而用PostScript设备输出时却保留了。但是,和PostScript设备一样,Printer设备也能在TV或TVScl命令中用XSize,YSize关键字来恰当的改变图像的尺寸。图像的偏移量可以用Device命令中的关键字Xoffset,YOffset关键字来设置。

例如,假设想在页面的中间放一个常规的图像,在页面上的纵横比为2/3,可以键入下面这些命令:

IDL> thisDevice =!D.Name

IDL> Set_Plot, ‘PRINTER’

IDL> Device, XOffset = 1.25, YOffset = 3.5, /Inches

IDL> image = LoadData (7)

175

IDL IDL入门教程

IDL> TV, image, XSize = 6, YSize = 4, /Inches IDL> Device, /Close_Document

IDL> Set_Plot, thisDevice

176

IDL IDL入门教程

第九章 编写 IDL 程序

本章概述

尽管IDL是一个程序语言,但在官方的IDL文档里是不可能发现如何编写IDL程序的方法。(www.loach.net.cn)当然,这并不意味着只有一个正确的方法。任何人,只要了解过那些和我一样的IDL程序员,都知道一个优秀的IDL程序员和一个不是那么优秀的程序之间的差距是很明显的。作为和IDL程序初学者长期打交道的人,作者见过了很多不是很好的程序。

出现这个问题,可以肯定是由于对IDL信息缺乏了解。因为大多数这样的人毕竟是科学家,而不是电脑程序员。他们很聪明,并且在干自己的本行,但他们并不是去编写优秀的电脑程序。 如果只要遵循几条基本的原理,他们编写的程序就会出色,而且对他们自己也更有用。因此,本章就是阐述这几个原理。

本章的任务就是展示怎样编写一个合理的复杂图形演示程序。而且这个程序能从IDL命令行上调用。同时也希望该程序能够将数据显示在可改变大小图形窗口中,从IDL命令行上直接打印,或者直接传送到PostScript文件中。此外,这个程序能够轻松将数据文件保存成GIF或JPEG文件。即使这写程序采用不同的颜色,这个程序应该是具有颜色敏感功能,而且能够和其他程序共存。而且,在程序中增加一个图形界面应该很简单,即使那些对该程序一知半解的用户也可以容易掌握。

再者,这个程序应该维护简单,易扩展。简而言之,该程序应当以模块化方式来编写。尽管可能不清楚为什么要这样做,这里还是有必要介绍以一下面向对象编程的概念,特别是自身模块和方法的概念。如果已经了解这个程序的原理 ,那么就能够毫无困难地理解在IDL5中引进的对象类和方法方面的复杂知识。

基本的ImageBar程序

这个基本程序的思路很简单,即显示图像,在图像周围显示坐标轴。并在此图像的上方绘制一个颜色栏,用来表示图像色彩与其值的相关性。将这个程序取名为Imagebar。结果与图80相似。 177

IDL IDL入门教程

图片82:此程序显示了一个由轴环绕的图象,图象上方的色彩栏标出了图像值的范围。[www.loach.net.cn)

这个程序的基本框架很简单。图像用一个类似于 Tvimage 的命令来显示。(程序Tvimage,如果带关键字Position就可用来定位显示图像。此外,它还可以根据图像输出设备的不同而输出不同大小的图像。详细信息请参阅72页的“用标准化坐标来定位图像”。)坐标轴是用带关键字 NoData 的Plot命令绘制,色彩栏用 Colorbar命令显示的。(程序Coloebar是并与本书配套使用的程序之一。)

注意,如果是在24位颜色环境中运行这个程序,色彩分解应处于关闭状态。详细信息请参阅87页的“在24位颜色环境中指定分解颜色”。要使颜色分解处于关闭状态,键入:

IDL>Device, Get_Visual_Depth=thisDepth

IDL>IF thisDepth GT 8 THEN Device, Decomposed=0

如果愿意,也可将上述代码添加到下面的Imagebar程序中。

新建一个文本编辑窗口,并且给程序命名为ImageBar。其定义如下所示:

PRO ImageBar, image

其中,image是要显示的二维图像。

作者喜欢编写简单易懂程序,即使那些不清楚程序如何工作的用户也能够看懂。如果那样,即使用户不知道要将图像作为程序的第一个参数,程序也可以让用户有机会打开并读取一个图像文件。

编写这个程序的目的之一就是能够在 Z图形缓冲区或在PostScript文件中输出显示图形。那两个图形输出设备既不支持窗口,也不支持组件程序。因此,程序要生成一个命令,类似于Window或一个在支持窗口的图形设备中创建窗口的命令。

扩展:idl / idl教程 / idl程序设计

为达到这个目的,可以使用!D.Flags系统变量。如果在这个变量的第八位的值不为0,那么当前图形设备就支持窗口。用256和这个系统变量进行逻辑AND运算,如果返回值为0,那么当前的图形设备就不支持窗口。

178

IDL IDL入门教程

这些代码让用户有机会在支持窗口的图形设备上打开图像文件。[www.loach.net.cn]从这个意义上说,参数image可以说是一个可供选择的参数。如果设置不支持窗口,参数image就是一个必须的参数了。增加了逻辑判断的代码如下:

IF N_Params( ) EQ 0 THEN BEGIN

IF (!D.Flags AND 256) NE 0 THEN BEGIN

Image = GetImage ( Cancel= canceld, ‘m51.dat’,$

Xsize=340,Ysize=440)

IF CANCELD then return

ENDIF else begin

Message, ‘Please supply an image argument,’,/Continue

RETURN

ENDELSE

ENDIF

注意,GetImage 命令用来打开图像文件。程序getimage.pro 是和本书配套使用的文件之一。GetImage是用来读取无格式数据文件的一个通用程序。它是一个对话框程序。详细信息请参阅291页的“创建模态对话框程序”。

下一步,就是检查图像参数是必须是一个2 D 图像。注意,当出现错误时,用Message 命令而不是Dialog_Message命令来提示错误信息。这是因为 Dialog_Message是个组件命令。同时在 Z或PostScrippt设备里用Dialog_Message会导致错误。

S= Size (image)

IF S [0]NE 2 THEN BEGIN

Message, ‘Image argument must be 2D.’, /Continue

RETURN

ENDIF

最后,要定义X轴和Y轴矢量的默认值。(在现实的应用程序里,这些矢量可能代表确切的物理含义,并且作为固定参数传递到程序中。但是,如果在这里也这么做,只会使例子更加复杂,而对增加程序原理的理解毫无裨益。)

X=FindGen ( [1])

Y=FindGen( [2])

下一步,就可以绘制图形了。 这个图像显示在窗口的偏下部分,而窗口的上部分用来显示颜色栏。

注意,窗口不是专门为显示图形而建立的。这是又能输出到Postscript文件,又能输出到可改变大小的窗口的图形程序的重要特点。如果程序要能够在任何图形显示窗口或设备上正常工作,应当如下所示:

imagePos=[ 0.15,0.15.0.9,0.75]

TVImage, image, Position=imagePos

接着,运用带关键字NoData的Plot命令图像周围画轴。记住,要使用相同的定位参数,以确保坐标轴的范围与图像范围一致。此外,必须设置关键字NoErase,否则Plot命令会擦除刚才显示的的图像。键入:

Plot,X,Y, /NoDATA,xstyle=1,Ystyle=1,Position=imagePos, /NoEase

最后,在图像上方绘制颜色栏。通过变量imagePos可以计算出颜色栏的位置,还应该稍微地延伸图像的长度,并颜色栏定位在图像上方。键入:

barPos=[imagepos[o],(imagepos[3]+0.15,imagepos[2],$

(imagepos[3]+0.15)+0.05]

179

IDL IDL入门教程

Colorbar,Position=barpos

END

整个 ImageBar 程序的源代码如下所示。[www.loach.net.cn)(在与本书配套使用的文件中,可以找到本程序的源代码,文件名称为ImageBar.1.pro.)

PRO ImageBar , image

IF N_Params ( ) EQ 0 THEN BEGIN

IF (! D. Flags AND 256 )NE 0 THEB BEGIN

Image = Get Image ( Canceled, ‘m 51.dat’. $

XSize=340, Ysize=440)

IF canceled THEN RETURN

ENDIF ELSE BEGIN

Message, ‘please supply an image argument.’, ?Contimue

RETURN

ENDELSE

ENDIF

S =Size (image)

IF s [0] NE 2 THEN BEGIN

Message, ‘Image argment must be 2D.’ , /Continue

RETUEN

ENDIF

X =FIndGen (s [1] )

Y= FIndGen (s [2] )

Image Pos = [0.15, 0.15, 0.9, 0.75 ]

TVImage, image, Position =image Pos

Plot, x, y, /NoData, XStyle =1, YStyle=1,$

Position = imagePos, / NoErase

BarPos = [imagepos[0], (imagepos[3]+0.15), imagepos[2], $

(immagepos [3] +0.15)+0.05]

Colorbar, Position =barPos

END

保存并编译这个程序,如果程序编译失败,在继续之前应该修订错误。

IDL> .Compile ImageBar

程序编译完后,打开银河系图像M51和程序一起运行,键入:

IDL>image =LoadData(12)

IDL>Window

IDL>ImageBar, image

如果程序在运行时出现程序崩溃,需要对程序作一些修改。在每次修改程序时,要保存并重新编译文件。

注意,这个程序可以在任何尺寸的窗口中显示图像,如下所示:

IDL>Window, /Free, XSize=500, YSize =250

IDL> ImageBar, image

IDL>Window, /Free, XSize=300, YSize =600

IDL> ImageBar, image

180

IDL IDL入门教程

给程序ImageBar增加一个“先擦除”功能

注意,如果只是用鼠标改变图形窗口(而不是新建一个窗口),并重新执行ImageBar命令,这时图像绘制在原有窗口的上面。[www.loach.net.cn]这与TV或TVScl命令的效果相似,但在这里并不合适。在这个程序的TVImage 命令之前,可以增加一个永久性的擦除命令,但是在每次程序运行时都将擦除窗口里的内容。在显示设置上,这可能不是问题,但是如果是在创建PostScript 文件,这个永久性的删除命令就会将输出结果输出到第二页中。这样,每次创建一个ImageBar程序的PostScript文件,就有输出两页,第一页是空白的。

为避免这一点,可以把擦除功能作为可选择的关键字增加到程序中。通过修改ImageBar程序的定义语句,可以给擦除关键字定义如下:

PRO ImageBar, image, EraseFirst=erasefirst

这个EraseFirst关键字可以设置也可以不设置,即有双重属性,这也意味着可以用Keyword_Set 命令来检查它是否存在。(详细信息请参阅213页的“处理具有双重属性的关键字”。)在TVImage 命令之前增加该行语句,如下所示。增加部分已用粗体字标出。

扩展:idl / idl教程 / idl程序设计

ImagePos = [0.15, 0.15. 0.9, 0.75 ]

IF Keyword_Set (eradefurst) THEN Erase

TVImage, image, Position =imagePos

保存并重新编译ImageBar,并重新执行该程序,如下:

IDL> ImageBar, image ,/EraseFirst

注意,这是可以用鼠标来改变图形窗口的大小,并重新执行ImageBar命令来重画图形。当进入到下一章时,就可以用一个方法自动处理这个过程。因此,当窗口改变大小时,图形就自动重画。

向ImageBar程序增加颜色敏感功能

颜色识别,是作者定义的一个术语,就是程序可以确切地知道哪部分颜色表可以使用。(详细信息请参阅67页的“将图像缩放成颜色表的不同部分”)Xload 或Xcolors是颜色识别的最好例子。这两个例子分别通过使用关键字Ncolors和Bottom来计算出当前应用程序使用多少色彩以及这些色彩索引的开始位置。

IDL> Xcoloes, Ncolors=100 Bottoms=100

颜色识别命令可以通过不同方式一起使用来开发整个颜色识别的IDL应用程序。要使ImageBar 程序能识别色彩。首先,给程序中的关键字Ncolors和Bottom下定义,如:

PRO ImageBar, image, EraseFirst=erasefirst, Ncolors=ncolors, Botttom =bottom

下一步,检查关键词,若需要定义它们的默认值,键入如下:

IF N –Elements (Ncolors)EQ 0 THEN $

Ncolors = ! d. .Table_Size

IF N_Elements (bottom)EQ 0 THEN bottom=0

这时,再定义绘制颜色这个关键字或许是个好主意。现在,并将它作为色彩表的最顶层颜色。然后将下面一行代码紧放在上述两行代码的后面:

drawColor = ncolors –1 + bottom

下一步,修改程序TVImage,使得图像合适地缩放到指定的颜色数。同时,也对刚才定义用来绘制颜色地Plot命令。键入下面的黑体部分:

TVImage,BytScl (image,Top=ncolors-1)+bottom,Position=ImagePos

181

IDL IDL入门教程

Plot,X,Y, /NoData,XStyle=1, Ystyle=1,Psition=imagePos, /NoErase, Color=drawColor

最后,修改ColorBar。[www.loach.net.cn)幸运的是,ColorBar已经是按照颜色识别的方式写的,只要将nColors和bottom变量传递到程序中即可,如下所示:

ColorBar,Postion=barPos,Ncolors=ncolors,Bottom=bottom,Color=drawColor

这时程序ImageBar变成如下所示。(在与本书配套的文件中可以找到这个程序的源代码,名称是ImageBar2.pro)

PRO ImageBar ,image, EraseFirst=erasefirst ,Ncolors=ncolors, Botttom =bottom

If N_Params() EQ 0 Then Begin

IF (!D.Flags AND 256) NE 0 THEN BEGIN

Image = GetImage ( Cancel= canceld, ‘m51.dat’,

Xsize=340,Ysize=440)

IF Canceld THEN RETURN

ENDIF ELSE BEGIN

Message, ‘Please supply an image argument,’,/Continue

RETURN

ENDELSE

ENDIF

S =Size (image)

IF s [0] NE 2 THEN BEGIN

Message, ‘Image argment must be 2D.’ , /Continue

RETURN

ENDF

X =FIndGen (s [1] )

Y= FIndGen (s [2] )

Image Pos = [0.15, 0.15, 0.9, 0.75 ]

TVImage, image, Position =image Pos

Plot,X,Y, /NoData,XStyle=1, Ystyle=1, Position = imagePos, / NoErase

BarPos = [imagepos[0], (imagepos[3]+0.15), imagepos[2], $

(immagepos [3] +0.15)+0.05]

Colorbar, Position =barPos Ncoloes=ncolors,Botttom =bottom, Color=drawColor

END

查看这时程序是否能够色彩识别,保存并重新编译,然后键入下面的命令:

IDL>LoadCT,1,Ncolors=75,Bottom=0

IDL>LoadCT,3, Ncolors=75,Bottom=75

IDL>window,1

IDL>ImageBar, image, Ncolors=75, Bottom=0

IDL>Window, 2

IDL>ImageBar, image, Ncolors=75, Bottom=75

这时,第一个窗口的图像应该是蓝色的,第二窗口是红色的。

要改变第一窗口的颜色,可以键入:

IDL>Xcoloes, Ncolors=75, Title=’Window 1 Colors’

要改变第二窗口的色彩,键入:

IDL>Xcoloes, Ncolors=75, Bottom=75, Title=’Window 2Colors’

注意,这时应该可以单独改变这两个窗口的颜色。如果结果不是这样,也许在程序中还存在 182

IDL IDL入门教程

错误。(www.loach.net.cn)仔细检查程序代码以及键入在IDL命令行上的命令,然后再试一遍。

给ImageBar中的命令传递关键字

在上面的程序中,把变量ncolors 和bottom传递给命令ColorBar时,就提出了这样一个问题,即一般来讲,怎样给程序内部的命令传递关键字变量呢。例如,假设想要保存程序ImageBar中图像外观比例,就必须为ImageBar程序内部的TVImage 命令设置关键字Keep_Aspect_Ratio。

要做到这一点,可以为程序ImageBar定义一个关键字Keep_Aspect_Ratio,然后将关键字的值传递给TVImage命令。例如,修改ImageBar程序定义语句,如下所示:

PRO ImageBar ,image, EraseFirst=erasefirst ,Ncolors=ncolors,Botttom =bottom,$

Keep_Aspect_Ratio=keepaspect

将变量keepaspect的值传递给TVImage命令,如下:

TVImage, BytScl (image, Top=ncolors-1)+bottom,Position=imagePos,$

Keep_Aspect_Ratio=Keyword_set (keepaspect)

保存并重新编译程序,键入下面命令,看程序的运行结果如何:

IDL>ImageBar, image, /keep,/Erase

知道为什么可以用缩写的关键字Keep和Erase,而不必拼写完全吗?。如果不能,请参阅211页的“使用关键字的缩写”。

使用关键字继承

定义关键字并将它们的值传递给程序内部的IDL命令的方法,简单易懂,但是也很容易就看出这将是一个负担。例如,运行ImageBar程序后,可能会觉得使用轴标题会更好,或者加上图像名称,或者将颜色表划分成更多部分。事实上,很快就会觉得对这个程序的修改将是永无止境了。简而言之,如果要定义和检查大量的关键字,一个简单的程序也会就变得庞大和复杂。(仅仅Plot命令就有50多个关键词可以使用。)

扩展:idl / idl教程 / idl程序设计

一个好的解决方案就是充分利用IDL中的关键字的继承。

在IDL中使用关键字的继承就和为程序模块定义一个Extra关键字一样简单。(下划线是非常重要的。)例如,可以通过修改ImageBar程序的程序定义语句给它增加关键字的继承。如下所示:

PRO ImageBar ,image, EraseFirst=erasefirst ,Ncolors=ncolors, Botttom =bottom,$

Keep_Aspect_Ratio=keepaspect,_Extra=extra

注意,关键字名称是_Extra,而且关键字变量是extra。这个关键词的名字必须是_Extra,但是关键字变量可以随便命名。

当一个程序模块定义了关键字_Extra时,IDL将所有没有定义的关键字以及它们相应的值收集到一起,并且把他们放入一个匿名的结构中。在这个结构里,关键字名称是结构的字段,而关键字变量的值则是相应字段的值。然后这个匿名结构就被赋值给关键字_Extra的相应变量。(例如,这里是变量extra。)

例如,假设用下面形式调用ImageBar程序:

IDL>ImageBar, image,Xtitle=’Width (mm)’,XcharSize=1.3

尽管关键字Xtitle和Xcharsize对程序ImageBar而言是没有定义的,但是它们对于Plot命令却是有效的。由于关键字_Extra已经被定义,IDL根据这些没有定义的关键字建立一个匿名结构,这个结构定义如下:

extra={ Xtitle:’Width (mm)’,XcharSize:1.3}

183

IDL IDL入门教程

接下来的是将这些关键字传递给Plot命令。(www.loach.net.cn)这点可以通过使用已经为Plot命令定义了的关键字_Extra。(事实上,所有IDL系统程序和函数都定义了关键字_Extra。)修改ImageBar程序中的Plot命令,如下所示:

Plot,X,Y, /NoData,XStyle=1, Ystyle=1,Psition=imagePos,/NoErase, $

Color=drawColor, _Extra=extra

在给ImageBar程序和Plot命令定义了关键字_Extra后,保存并重新编译程序。用下面形式调用这个程序时,结果会怎样呢?

IDL>ImageBar, image,Xtitle=’Width (mm)’,XcharSize=1.3

如果带关键字Division,程序ColorBar的一个关键字,来调用这个程序,会出现什么样的结果呢?

IDL>ImageBar, image,Xtitle=’Width (mm)’,XcharSize=1.3 $Divisons=5

如果是在IDL5的环境下运行,什么也不会发生。匿名结构extra中的divisions字段会被Plot命令忽略。如果是在IDL4的环境下运行,程序将会崩溃,这是因为在IDL4中,IDL命令不能忽略不适用它的关键字。关键字Divisions的值也能通过关键字_Extra传递到ColorBar命令中。可以这样来修改ImageBar程序,见下:

Colorbar, Psition=imagePos, /NoErase, Color=drawColor, _Extra=extra

保存并重新编译此程序,如下调用它,会发生什么呢?

IDL>ImageBar, image,Xtitle=’Width (mm)’,XcharSize=1.3, Divisons=5

在这个例子中,ColorBar命令忽略了关键字Xtitle和CharSize,并接受了关键字Divisions。然而,Plot命令恰好相反。这样,通过关键字继承,可以给包含在其他程序的IDL 命令传递不同关键字,每个命令只接收与自己相关的关键字。

注意,在ImageBar代码中,无论是Plot命令还是ColorBar命令都使用了关键字Color,然而在ImageBar程序中关键字Color并没有定义。如果象这样调用ImageBar程序会发生什么呢?

IDL>TVLCT,0,255,0,101

IDL>ImageBar, image, Ncolors=100,color=101

实际上,这样一来会使Plot命令和ColorBar命令调用时带有两个color关键字,(一个来自于本身,另一个来自于_Extra机制)每一个关键字的的值都不相同。这时,IDL怎样取舍呢?在这种情况下,IDL就使用通过_Extra机制传递过来的color关键字。

在大多数情况下希望是如此,然而却未必经常希望这样。比如,要是ImageBar程序这样调用会出现什么结果呢?

IDL>ImageBar, image,Xstyle=4

对于在某些关键字必须设置的情况,就必须自己来截取_Extra结构并修改相应的数值了。要从匿名结构中获取个别字段,命令Tag_Names就非常有用了。例如,如果想知道Plot命令在调用时关键字Xstyle是否已设置,可以如下编写IDL代码:

IF N –Elements (extra) GT 0 THEB BEGIN

theseFields=Tag_Names (extra)

index=Where (theseFields EQ ‘XSTYLE’ , count)

IF count GT 0 THEB extra.xstyle =extra.xstyle OR1

ENDIF

注意,关键字_Extra继承机制是通过传值而不是引用来传递关键字及其相应值的。通过传值来传递变量也就是说,传递到程序中的只是变量的一份拷贝,而不是变量本身。这样,程序对变量所做的改变只是局部有效,而对全局没有影响。因而,这就使得关键字_Extra不能作为输出型的关键字。由于这个限制的原因,结果在IDL5.1中一个新的关键字继承机制产生了,这就是通过引用来传递关键字。这个机制使用关键字_Ref_Extra来定义,在使用时,可以使用_Extra或 184

IDL IDL入门教程

_Ref_Extra机制,但是注意这两种机制不能同时使用。(www.loach.net.cn]

根据窗口大小改变字符大小

当改变显示窗口的大小并重新执行ImageBar命令时,图像和颜色栏能够适当改变大小,而轴和颜色栏的注释却没有。这也就意味着,当在小窗口里显示时,文本显示太大;当在大窗口中显示,文本又显得太小。

要改变字符大小,可以用关键字CharSize,但是怎样做才能根据窗口大小来选择合适的字体大小呢?这个问题的答案是,用标准化坐标单位来表示字体的大小。然而遗憾的是,在IDL中这是不可能的,因为字符大小是以字符单位来表示的。即使如此,仍有办法解决。

通过使用XYOuts命令可以获得在标准化坐标下的字符串的宽度。先给关键字charsize赋予一个负值,然后用XYOuts计算出字符串的宽度,而非真正地向显示窗口输出字符串。例如,假设想知道文本串“A Sample String”的宽度,可以键入:

IDL>thisSize=-1

IDL.XYOuts,0.5, 0.5 ‘A Sample String’ ,/normal,$

Width=thisWidth,CHARsize=THISSIZE

现在,比较一下thisWidth和目标宽度。例如目标宽度是0.25,也就是说字符串长度可以达到显示窗口的25%。如果目标宽度比这个thisWidth值大,可以增加字符大小然后再试一次直到字符大小合适为止。代码如下:

扩展:idl / idl教程 / idl程序设计

IDL>thisSize=-1

IDL.XYOuts,0.5, 0.5 ‘A Sample String’ ,/normal,$

Width=thisWidth,CHARsize=THISSIZE

最后,thisWidth将在目标宽度的微小误差内。如果ThisWidth比目标宽度长,可以用类似的算法来解决。

这种算法已经开发出来了,这便是Str_Size程序,这个程序可以在与本书配套的文件中找到。程序Str_Size参数是个字符串和标准化坐标下的目标宽度。作者发现,如果要显示的字符串为“A Sample String”以及目标宽度为0.25,则在作者常用的显示窗口中可以很好。

IDL>thisSize = Str_Size (‘ASample String’, 0.25)

为了给ImageBar程序增加自动调整字符大小的功能,在程序代码的TVImage命令前增加下行:

thisSize = Str_Size (‘ASample String’, 0.25)

接着,给Plot和Colorbar命令定义关键字CharSize。注意,Colorbar上的字符大小是图像上字符大小的75%。代码如下所示:

Plot,X,Y, /NoData,XStyle=1, Ystyle=1,$

Psition=imagePos, /NoErase, Color=drawColor,$

_Extra=extra, CharSize=thisSize

BarPos=[ imagepos[0], (imagepos[3]+0.15), $

Imagepos[2], (imagepos[3]+0.15)+0.15 ]

ColorBar, Position=BarPos,Bcolors=ncolors, Bottom=bottom,$

Color=drawColor, _Extra=erxtra, Charsize=thisSize*0.75

保存并重新编译程序。试着以各种大小的显示窗口来运行,看运行结果有什么变化。

185

IDL IDL入门教程

程序ImageBar的最终代码

最后的ImageBar程序代码如下所示。(www.loach.net.cn)(在与本书配套的文件中可以找到其源代码,文件名成为ImageBar.3.pro)

PRO ImageBar ,image, EraseFirst=erasefirst , Ncolors=ncolors, Botttom =bottom,$ Keep_Aspect_Ratio=keepaspect,_Extra=extra

IF N_Params ( ) EQ 0 THEN BEGIN

IF (! D. Flags AND 256 )NE 0 THEB BEGIN

Image = Get Image ( Canceled, ‘m 51.dat’. XSize=340, Ysize=440)

IF Canceld THEN RETURN

ENDIF ELSE BEGIN

Message, ‘Please supply an image argument,’,/Continue

RETURN

ENDELSE

ENDIF

S =Size (image)

IF s [0] NE 2 THEN BEGIN

Message, ‘Image argment must be 2D.’ , /Continue

RETURN

ENDIF

X =FIndGen (s [1] )

Y= FIndGen (s [2] )

IF N Elements (Ncolors)EQ 0 THEN Ncolors = ! d. .Table_Size

IF N_Elements (bottom) EQ 0 THEN bottom=0

drawColor = ncolors –1 + bottom

ImagePos = [0.15, 0.15. 0.9, 0.75 ]

IF Keyword_Set (eradefurst) THEN Erase

ThisSize= Str_SIZE (‘’A Sample String’, 0.25)

TVImage,BytScl (image,Top=ncolors-1)+bottom, Position=ImagePos,$

Keep_Aspect_Ratio=keywoed_Set(keepaspect)

Plot,X,Y, /NoData,XStyle=1, Ystyle=1, Psition=imagePos, /NoErase, $

Color=drawColor, _Extra=extra, CharSize=thisSize

BarPos = [imagepos[0], (imagepos[3]+0.15), imagepos[2], $

(immagepos [3] +0.15)+0.05]

Colorbar, Position =barPos Ncoloes=ncolors, Botttom =bottom, Color=drawColor ,$

CharSize=thisSize*0.75

END

实用程序的特点

下面是本程序的特点,这使得程序非常实用。

首先,自包含性。给出一个二维图像,程序可以以正确方式执行。这个图像不必有特别的大小。

186

IDL IDL入门教程

其次,设备无关性。[www.loach.net.cn]通过避免当前窗口或正使用的命令(除了在支持他们的设置上),程序可以在不同的图表输出设置上运行,包括PostScript设置和Z-图表缓冲区。

然后,窗口无关性。通过运用正规协调的命令可以确定窗口中图表的位置,程序可在窗口中以任何大小执行,包括PostScript窗口。

第四,颜色敏感功能。程序使用不同的颜色,这使得此程序和其它可以共享颜色表的程序运作变得更有利。

最后,程序的可扩展性。建立在程序里的关键词继承特性,可以以一种自然方式设置Plot和Colorbar命令参数。

在图形用户界面中包装ImageBar

一般地说,编写程序ImageBar方法的意外收获就是,它能够满足程序Xwindow调用其他程序的标准。程序Xwindow可以在与本书配套使用的文件中找到。Xwindow的三个标准是;(1)程序本身并不打开自己的显示窗口;(2)程序至多有三个参数;(3)程序通过定义关键字_Extra来进行关键字继承。

Xwindow是一个具有可改变大小的图形窗口的窗体程序。在以后的章节里可以学到更多的关于窗体程序如何工作的信息,但到现在为止,只可以用它显示ImageBar程序。除了具有可改变大小的图形窗口的优点外,Xwindow也可以将窗口中的内容(例如,ImageBar程序)输出为JEG, GIF, TIFFF 和PostScript文件,并且能够交互式地改变边程序颜色。

(Xwindow程序还要求其他三个程序,它们都在与本书配套的文件中。这些程序是Pswindow.pro、Xcolors.pro和Ps_form.pro。在键入以下命令之前,确保这些程序下载到本地。

IDL>Xwindow,’ImageBar’,image, /EraseFirst,m/Output, /Xclolors

对Xwindow的属性来做试验。试着将窗口图形输出为GIF或TIF文件,然后用浏览器来浏览输出文件。载入不同的颜色表并察看输出结果。

关闭Xwindw程序。要看看程序ImageBar的优越性,键入如下所示:

IDL>LoadCT, 3, Ncolors=75

IDL>LoadCT,4, Ncolors=75,Bottom=75

IDL>Xwindow,’ImageBar’,image, /EraseFirst,m/Output, /Xclolors=[75,0],Ncolors=75

IDL>Xwindow,’ImageBar’,image, /EraseFirst,m/Output,/Xclolors=[75,75], $

Ncolors=75, Bottom=75

注意,可以独立地改变两个窗口的色彩。

在下面的章节,将要引入一些概念和编写技巧来创建功能较为强大地程序。

187

IDL IDL入门教程

扩展:idl / idl教程 / idl程序设计

第八章 IDL编程基础

本章概述

本章的目的是学习IDL基本编程技巧,具体的来说,可以学到以下方面内容:

1. IDL批处理文件、主程序、过程和函数之间的区别;

2. 如何在IDL程序中输入和输出信息;

3. 如何在IDL程序中使用定位参数和关键字;

4. 如何编译和运行IDL程序;

5. 程序中的常用控制语句语法

如果把IDL程序看作一系列IDL命令的话,那么IDL程序——或称为IDL程序模块——可以分为四类:(1)批处理文件(2)主程序(3)过程(4)函数。(www.loach.net.cn)

编写IDL批处理文件

最简单的程序是一个IDL批处理文件。一个批处理文件由一系列命令组成,这与在IDL命令行敲入的命令完全一样。大多数人用批处理文件是为了自动执行自己在IDL命令行一次又一次敲入的命令。

例如,假设要在IDL中打开并显示一组图像,如果已经将图像数据读入到变量image中,那么用来显示图像的命令可以如下所示:

IDL> thisImage = BytScl (image, Top = 199)

IDL>LoadCT, 5, NColors = 200

IDL> s = Size (image)

IDL> Window, /Free, XSize = s[1], ysize = s[2]

IDL> TV, thisImage

这五行代码并不多,但键入三、四次之后,可能已决定把他们放在一个名为ImageOut.pro文本文件中。这文件就是所谓的批处理文件。

要执行该文件中的命令,必须把@放在IDL命令行的开头,其后再加上文件名即可。(.pro为默认扩展名。如果加了其它扩展名,那么应该把文件名称写完整)如下所示:

IDL> @ImageOut

注意,文件名没有在引号中,这与IDL文件名的一般规则是不一致的。

IDL会严格执行批处理文件中的命令,就像在命令行上键入一样。这意味着有必要用行续字符($)和其他命令行语言来让IDL确认键入的命令。如果在文件中的命令输入错误,那么出现的错误结果和在命令行键入命令出现的错误结果是一样的。

假设要打开8到10个图像文件,因而不得不分别打开每个图像文件,读取数据,然后运行批处理命令将每一个图像显示在窗口中。具体地说,可以这样自动进行读取数据和显示图像过程:

theseFiles = FindFile (‘*.img’, count = numFiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

188

IDL IDL入门教程

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

但是这个文件不适合于作批处理文件,因为FOR循环里面有多行语句。(www.loach.net.cn)如果没有续行符($)和命令连接符(&)的话,这种程序是很难写在命令行中的。为了自动执行由多行控制语句组成的命令,最好使用IDL主程序。

编写IDL主程序

IDL主程序和批处理文件在很多方面很相似,但也存在着很大的区别。像批处理文件一样,一个主程序也包含一系列命令。但与之不同的是,这些命令必须以END语句结束。例如,上面自动读取数据和显示图像的程序可以写成一个IDL主程序:

theseFiles = FindFile (‘*.img’, count = numFiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

End

批处理文件和IDL主程序最大的区别就是主程序的命令语句先由IDL编译器编译成程序模块,然后才执行代码。这就是为什么在主程序中可以有多行控制语句的原因。

如果将上面的代码保存在文件ImageOut.pro中,键入如下命令就可以编译并运行这个程序模块:

IDL> .RUN ImageOut

现在,主程序就已经驻留在IDL内存中了。同一时间只能有一个主程序驻留在IDL内存中。如果没有重新编译该代码而要重新运行这个程序,可以使用可执行命令.GO来实现,如下:

IDL> .Go

可执行命令(.Go,Compile,Run,等等)只能应用在IDL开发环境中,而不能用在IDL过程和函数中(尽管有很多方法可以在过程和函数中编译程序。详细情况请参阅 P230“特殊编译命令”)

编写主程序的最大优点是在程序中定义的变量可以在IDL开发环境中使用,IDL的命令都是 189

IDL IDL入门教程

在那里键入并被解释的。(www.loach.net.cn)换句话说,在主程序中定义的变量的作用范围是IDL整个开发环境,因而它可以被IDL其它命令使用。

更为通常的是,往往希望限定变量的作用范围,以使它们不致于占用大量的内存。在编程过程中,使用大量的全局变量会使内存的使用效率大大降低。为此,大部分应用程序都用过程和函数来编写。

编写IDL过程

IDL过程和IDL主程序很相似,同时也存在很大的区别。首先,在编写一个过程时,实际上所做的是创造另外一个IDL命令,一个构造在IDL系统语言之上的新命令。在IDL命令行和程序中,可以使用所创造的命令,就像使用IDL系统内置的命令一样。

过程看起来和主程序非常相似,不过过程是以过程定义语句开始的。定义语句的目的就是为过程命名(如果喜欢,也可以称之为命令名称)和定义过程参数。过程的参数既可以是定位参数也可以是关键字。待一会儿将学到更多有关过程参数如何定义方面的知识。

例如,想将上面的主程序写成一个名为ImageOut的IDL命令,可以这样编写这个过程:

PRO Imageout

theseFiles = FindFile (‘*.img’, count = numFiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

扩展:idl / idl教程 / idl程序设计

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

End

如果这些代码被保存在一个名为ImageOut.pro文本文件中,在命令行上键入此程序的名字,它就会自动编译和执行。如下所示:

IDL> Imageout

有时候想在运行之前清楚显式地编译一个过程或函数。(例如,在代码中有一个错误,在修改完毕之后,运行之前希望将程序重新编译。)如果想要显式地编译和运行上面的代码,可以这样键入:

IDL>.Compile ImageOut

IDL> Imageout

注意,在上述编译语句中的ImageOut是想要编译的文件的文件名(IDL默认.pro为文件扩展名)。文件名并没有加引号,但是它可能会区分大小写,这取决于当前的操作系统。

.Compile命令将编译该文件中所有的程序模块,但不会运行其中的任何一个。(在这个例子中只有一个程序模块。编译方面的详细信息请参阅P228“编译和运行IDL程序模块”。)文件名不一定必须和过程名称相同,但通常情况是这样的。上面的第二行语句是想要运行的程序或程序模块的名 190

IDL IDL入门教程

称。(www.loach.net.cn]

过程和与函数中变量的作用范围

关于过程和函数很重要的一点是在过程和函数中创建的变量是局部变量。也就是说,只有过程和函数内部的命令才能调用在他们内部创建的变量。

例如,在上面的过程中,在IDL命令行中是不可能识别变量theseFiles,thisimage或者s的,因为这些变量的作用范围只限于过程。而且,当IDL退出该过程时(在这种情况即为执行到end语句),这些变量被清除,它们所占用的内存也被释放。

如果所有的变量的作用范围都只是局部的,这就变得极其不方便了。因为那样就不可能在过程、函数之间,或命令行与过程及函数之间进行信息交流和数据传递了。因此,可以采用多种办法来扩展变量的作用范围,或在过程、函数之间传入或传出信息通常情况下,信息(例如变量等),是以过程和函数的参数形式来传递的。

创建定位参数

定位参数是在过程的定义语句中被定义的。一般来说,定位参数的数量没有限制的,但对它们的顺序有严格的要求。第一个定义的定位参数(“定义”的意思是把它放在过程名称的右边)是参数1,紧接着定义的是参数2,以此类推。

例如,希望指定ImageOut过程通常在哪一个子目录下查找图像文件。可以通过字符串参数lookHere把信息传入到ImageOut过程中去,修改后的ImageOut过程如下所示,增加部分已用黑体字标出。

PRO ImageOut, lookHere

Cd, lookhere

theseFiles = FindFile (‘*.img’, count = numFiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

End

若要调用修改以后的过程,应该重新编译它。之后,就可以这样来调用它:(假设在Windows操作系统中。下面的代码仅起示范使用)

IDL>. Compile ImageOut

IDL> ImageOut, ’c:\rsi\mydatafiledir’

字符串c:\rsi\mydatafiledir被拷到参数(有时也叫形参或局部变量)lookhere后,命令CD根 191

IDL IDL入门教程

据这个将工作路径改变到与之同名的指定路径中。(www.loach.net.cn]

这个字符串也可以作为变量传递到过程ImageOut中。如下面所示:

IDL> myimagedirectory = ‘c:\rsi\mydatafiledir’

IDL> ImageOut, myimagedirectory

在这里,变量myimagedirectory通过引用方式来传递的。它的意思以后将会更详细讨论到。但其中一点就是,这个变量现在可以在主程序和过程ImageOut中被使用。这意味着如果在过程内部改变它的值,这个变化将会反应在IDL命令行上来。换句话说,IDL命令行上的myimagedirectory变量和ImageOut程序中的变量lookhere是相同的。

换句话说,这个变量有两个名字,这两个名字都指向同一个数据或IDL中的物理地址。(就好比说,一个人在美国时人们叫他为乔,而当他回到家乡智利时是人们叫他乔丝。两个名字所指的是同一个人,但是一个地方的人只知道他的一个名字。)

但是如果在调用ImageOut时忘记了传递参数会怎样呢?如果键入如下所示会发生什么事?

IDL> ImageOut

在这种情况下,什么也不会传递到变量lookhere中去,所以在过程中变量就没有定义(但是,没有定义也是变量的一个有效类型)。遗憾的是,这会带来很大的麻烦,因为在过程中,CD命令语句把lookhere变量作为它的参数。而在cd命令中使用一个没有定义的变量会导致错误的。因此,清楚地知道过程如何被调用是非常有必要的。

定义可选的或必须的定位参数

IDL提供的N_Params命令可以返回过程在被调用时定位参数的调用个数(不包括关键字)。这个命令如下所示:

Numparams = N_Params ()

知道了过程在调用时输入了多少个定位参数,就有机会对所传入的参数是可选择的还是必须的进行判断。例如,想把参数lookHere作为一个必须的参数,在过程的前几行应增加如下几行:

Pro ImageOut, lookHere

Numparams = N_Params ()

If numparams EQ 0 Then $

Message, ‘must supply one parameter to this procedure.’

Cd, lookHere

这里,message命令会产生一个错误,导致IDL停止执行过程中的命令,同时错误信息被送到命令记录窗口或输出窗口。这个结果和在没有数据参数的情况下调用plot命令是一样的。

也许在ImageOut过程中把参数lookHere作为一项可选参数更合情合理。

如果没有提供参数,那么lookHere变量的值就会设置为当前子目录。实现这部分功能的代码如下所示:

扩展:idl / idl教程 / idl程序设计

Pro ImageOut, lookhere

Numparams = N_Params ()

If numparams EQ 0 Then CD, current = lookhere

Cd, lookHere

在这里,如果在调用ImageOut时没有提供lookhere参数,那么cd命令就和关键字Current一起使用,并将当前工作目录放在lookhere变量中(关键字current在这里是一个输出性质的关键字,关于这个,读者等下将会了解更多。)

在IDL过程和函数中,定义参数的习惯性规则是,参数是指必须的参数,关键字是指可选参数。这种规则常常因为参数而破例,而几乎不会因为关键字而破例。也就是说,经常可能会出现 192

IDL IDL入门教程

参数是可选的,却很少有关键字是必选的情况。[www.loach.net.cn]

定义关键字

和定位参数一样,关键字也是在过程的定义语句中加以定义的。只要愿意,关键字可以和混杂在参数中定义,(这对参数的相对位置没有任何影响),但一般地,关键字在参数定义之后再定义。

如果读者看了过程ImageOut的代码,就会发现程序中硬性地载入了颜色表5。但是,如果允许用户指定颜色表,并且只有在用户没有指定的情况下才将颜色表5作为默认的颜色表,那样就更符合情理了。这便是关键字所起的作用。关键字定义语法如下:

Keywordname = keywordsymbol

等号左边的是关键字名字,这是关键字使用时的名字;等号右边是在过程和函数中关键字所赋的值。例如,下面是为Imageout过程定义关键字colortable的例子:

Pro ImageOut, lookHere, colortable = thiscolortable

关键字colortable是连同颜色表值一起调用ImageOut过程时即将使用到的名称。例如,想用Red-Temperature色表,即色表3,来浏览这些图片,可以这样调用过程:

IDL> ImageOut, ‘c:\data’, colortable = 3

使用缩写关键字

关键字名称,colortable,在使用过程中不必全部拼写,只要有足够多的字母将它和其它关键字区别开来就可以了。因为colortable 是ImageOut中唯一的关键字,所以用c已足够来定义这个关键字。过程ImageOut也可以这样调用。

IDL> ImageOut,’c:\data’, c = 3

或者这样:

IDL> ImageOut, ‘c:\data’, color = 3

上述三种调用方法的作用是相同的。

关键字右边的变量,thiscolortable,是用来给程序中关键字赋值的。在这里值赋为3。 现在可以重新编写ImageOut的代码了(修改部分已用粗体字标出):

Pro ImageOut, lookHere, colortable = thiscolortale

Numparams = N_Params ()

If numparams EQ 0 Then CD, current = lookHere

CD, lookHere

theseFiles = FindFile (‘*.img’, count = numFiles)

Print, ‘number of files found:’ numfiles

For j = 0, numFiles-1 do begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

Thisimage = BytScl (image, top = 199)

LoadCT, thiscolortablel, ncolors = 200

S = size (image)

193

IDL IDL入门教程

Window, /Free, XSize = s (1), YSize = s (2)

TV, thisimage

Endfor

End

但是如果变量thiscolortable没有定义呢?换句话说,如果用户这样调用ImageOut过程会怎么样呢?

IDL> ImageOut

由于关键字colortable没有被使用,所以在变量thiscolrotable里面什么也没有。(www.loach.net.cn]这话也可以这样理解:变量thiscolortable在过程中没有定义。如果这样,那么在上述代码中LoadCT命令以下的三分之二的编码都会无效,因为它不能接受一个没有定义的变量作为参数。

这使读者想起上次曾经遇到的情况,即在调用ImageOut时应该知道参数是否被使用。在这里读者应该知道,在调用ImageOut时关键字是否被使用的情况。

定义可选择的关键字

关键字是可选择的参数。这意味着,在调用含有关键字的过程和函数时,关键字很可能不会被使用。但是赋有关键字的值的变量还是会在程序中被使用的。这意味着要对每一个已经定义的关键字赋予默认值。实际上如果不这样做的话,IDL应用程序迟早会失败。

但是怎样才知道需要定义默认值呢?当然,如果用户已经赋值那就没有必要来定义默认值。 但是怎样知道用户是否已赋值了呢?如果是参数,可以用N_Params命令来提供这方面的信息。遗憾的是,N_Params只对参数才起作用,而不能提供关于关键字的任何信息。

很多人把这个问题想象为“我想知道关键字使用了还是没有?”。如果是这样的话,要查明关键字是否使用比其最初出现的时候要难多了。我们经常期待好事出现,但是现实却未必如此。我们问,“这个变量已经赋予了关键字的值还是没有?”

关键字定义了吗?

对一个关键字,比如colortable,很可能被赋予很多可能的值,用N_elements这个IDL命令可以知道在过程和功能程序中,变量thiscolortable是否已经定义。尽管N_emements在IDL中还有其他作用,但是在这个情况下,使用这个函数的功能是:如果N_emements的参数没有定义,则函数返回值为零。否则将返回参数的元素个数。该参数可以是任意数据类型。

在这种情况下,过程ImageOut在用户没有提供色表的情况下,将会定义默认的色表值,它的前几行如下所示:

Pro ImageOut, lookhere, colortable = thiscolortable

Numparams = N_Params ()

If numparams eq 0 hen cd, current = lookhere

Cd, lookhere

If N_Elements (thiscolortable) EQ 0 Then thiscolortable = 5

注意,一定要把关键字的名字(比如,它在IDL命令行中是如何使用的)和赋有关键字的值的变量区分开来。关键字的名字在关键字定义的左边,赋值的变量在右边。N_elements的参数必须是关键字的变量,而不是关键字的名字。

这点经常被错误理解,因为一些IDL程序员(包括作者在内)喜欢把关键字名字和变量拼写成一样。换句话说,如果作者正在为自己编写以下程序,作者会这样写:

194

IDL IDL入门教程

Pro ImageOut, lookHere, colortable = colortable

扩展:idl / idl教程 / idl程序设计

Numparams = N_Params ()

If numparams EQ 0 Then CD, current = lookHere

Cd, lookHere

If N_Elements (colortable) EQ 0 Then colortable = 5

这里不存在正确与否,但还是存在区别。[www.loach.net.cn)IDL会清楚地区别这些。建议读者也这样做。记住,检查的应是关键字变量,不是关键字名字。作者喜欢把关键字变量和名字拼成一样,因为这样可以减少拼写错误。我可以认为我是在检查关键字名字,事实上我在检查关键字变量。

处理具有双重属性的关键字

如果仔细检查ImageOut的代码,将会发现图像数据在显示之前已经被拉伸了。这个特殊代码行是:

Thisimage = BytScl (image, top = 199)

图像数据文件很可能已被拉伸过,因此这个步骤并不是必须的。如果这样的话,就可以定义一个名为scale的关键字,当过程带该关键字调用时,则拉伸图像,如果不带该关键字调用,则忽略图像的拉伸。这样的关键字具有双重属性,它可以设置也可以不设置。其它关键字同样具有双重属性,也就是说它们可以是真或假,是或者否,1或者0。

IDL提供了一个特殊的命令来处理类似的关键字,即Keyword_Set命令。和N_elements一样,Keyword_Set的参数是关键字变量而不是关键字名字。但Keyword_Set有一点不一样就是,如果Keyword_Set的参数没有定义或者值为0,那么它将会返回 0。否则返回1。所以,Keyword_Set的返回值只有两种:0或者1。

许多程序员错误运用了Keyword_Set,用它来检测关键字是否使用。其实不是这样的,用它来验证一个关键字是否已经使用最终会导致IDL应用程序的错误。只能在关键字具有双重属性时才能使用它。

在确定只有设置了关键字scale情况下才调用图像拉伸步骤,这时的ImageOut代码可以这样来写:

Pro ImageOut, lookhere, colortable = thiscolortable, Scale = scaleIt

Numparams = N_Params ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = findfile (‘*. img’, count = numfiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numfiles-1 do begin

Openr, UN, thesefiles (j), /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

If Keyword_Set (scaleIt) then $

Thisimage = BytScl (image, top = 199) else $

Thisimage = image

LoadCT, thiscolortable, ncolors = 200

S = size (image)

Window, /Free, XSize = s (1), YSize = s (2)

195

IDL IDL入门教程

Tv, thisimage

Endfor

End

现在,希望对图像进行拉伸,可以这样调用过程ImageOut:

IDL> ImageOut, /scale

记住/keyword只是表示keyword=1。[www.loach.net.cn)这种设置关键字的简单表示方法在使用具有双重属性的关键字时经常碰到。

创建输出型参数

到目前为止,在过程ImageOut中创建的关键字或参数都是输入型参数。换句话说,信息是从程序以外传入进来的。但是,还经常需要把信息输出到此程序外部。

比如说,ImageOut的用户想要知道被打开的文件名。在过程内部这些文件名被储存在变量thesefiles中。对ImageOut来说,这是个局部变量。也就是说,该变量的作用范围仅限于过程本身。一个用户怎样才能从过程外部获得这些信息呢?

有许多方法可以采用,比如说,一个公用模块可以扩大变量的作用范围,但在通常情况下,不需要公用模块。一个简单的方法是使用输出型参数。

用引用和传值的方法传递信息

209页中,在“创建参数”中讨论了关于用引用传递参数的问题。通过引用的方式,参数的作用范围比局部变量作用范围要大。也就是说,通过引用来传递参数事实上是——在低级意义上——指向IDL内存中的地址,这从而使得在所有具有引用权限的程序中该参数都可以被引用。在程序之间通过引用方式进行传递数据的实际上是,任何程序对该数据的修改都将影响其他程序中该数据的结果。

另一个传递信息的方法是传值。就是说,传递到程序中的不再是指向数据地址的指针,而是数据值的拷贝。在程序模块中对数据的修改不会对原有数据有任何改变。

在IDL中,所有的变量都是以引用的方式传递的。其他的,比如下标变量、系统变量、表达式、结构,常量,都是通过传值的方式进行的。

这里举一个简单的例子来说明这一点。这是一个用来调整图片的大小并将它显示在一个150*150的窗口中的程序。在文本编辑器中键入如下所示,并保存为resizeit.pro。

Pro resizeit, image

Image = congrid (image, 150,150)

Window, 0, xsize = 150, ysize = 150

Tvscl, image

End

现在用loadata来载入世界海拔高度数据,如下:

IDL> image = loaddata (7)

用help命令检查变量:

IDL> help, image

Image byte = array (360,360)

现在用resezeit程序调入该数据,再次检验图片变量。这时,变量image已经变成了一个150*150的字节型数组。

196

IDL IDL入门教程

IDL> resizeit, image

IDL> help, image

Image byte = array (150,150)

变量image已经发生变化,这是由于在传入过程resizeit中时,它是以引用方式被传递的。[www.loach.net.cn)换句话说,它在resizeit程序中具有使用权限。在这里,变量image既是输入型变量(即传入到程序中的信息),又是输出型变量(输出到程序外的信息)。变量可以是输入型变量、输出型变量,或者两者都是。这完全取决于在IDL程序中怎样编写它的代码。

假如想把image数据传入到程序,但不想在程序内部对它有所改变,也可以选择下面两种方法之一:(1)重新编写resizeit程序,不改变image变量;(2)用传值的方式将变量image传入到当前程序中。如果是第一种方法,可以这样重新编写程序:

Pro resizeit, image

Window, 0, xsize = 150, ysize = 150

Tvscl, congrid (image, 150,150)

扩展:idl / idl教程 / idl程序设计

End

在第二种情况下,也可以用表达式作为参数,如下所示:

IDL> rsizeit, image + 0b

有时候也会遇到相反的问题,比如,希望在过程或函数中改变某个值而实际上却没有。这通常是因为输入的参数不是变量,而可能是变量的一部分。例如,它可能是结构中的一个字段。因为结构、系统变量,任何一种表达是都是用传值方式传递,而不是引用。只有变量(并且是完整的变量)才是通过引用方式来传递的。

例如,假设上面的变量是一个自定义的系统变量:

IDL> image = loaddata (7)

IDL> defsysv,’image’, image

如果这样调用resizeit程序:

IDL> resizeit, !image

这时,就不可能将新尺寸的图片输出到程序外和传递到系统变量中,因为这是系统变量!image总是以传值方式传递的而不是以引用的方式。正确的命令如下:

IDL> thisimage = !image

IDL> resizeit, thisimage

IDL> !image = thisimage

为了解决从ImageOut过程中获得文件名的问题,选择输出型关键字是比较恰当的。关键字,和参数一样,可以通过传值或引用方式来传递。带有关键字filename的新程序代码如下,变化已经用粗体标出:

Pro ImageOut, lookhere, colortable = thiscolortable, Scale = scaleit, $

Filenames = theseFiles

Numparams = N_Params ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = findfile (‘*.img’, count = numfiles)

Print ‘number of files found:’, numfiles

For j = 0, numfiles-1 do begin

Openr, lun, thesefiles (j), /get_lun

Image = bytarr (512,512)

Readu, lun, image

197

IDL IDL入门教程

Free_lun, lun

If Keyword_Set (scaleit) then $

Thisimage = bytscl (image, top = 199) else $

Thisimage = image

Loadct, thiscolortable, ncolors = 200

S = size (image)

Window, /free, xsize = s(1), ysize = s(2)

Tv, thisimage

Endfor

End

如果用户希望返回文件名,只需简单使用关键字名字,并将它赋值给某个变量。(www.loach.net.cn)如下所示:

IDL> ImageOut, filenames = myfiles

尽管在过程ImageOut被调用时变量myfiles可能还没有定义,但当过程返回时它会被定义为一个字符数组。如果变量myfiles在调用ImageOut时已经定义,它会被ImageOut再定义一次,而它的初始值将会丢失。

参数存在吗

使用输出型参数,常常希望知道该参数或是关键字是否存在。例如,某个程序在计算数据时很费时间,但计算结果并不需要。因此希望,只有在用户需要输出参数时才会进行计算。

为了更生动的说明这个问题,怎样才知道当用户这样调用程序ImageOut时,参数是否存在?

IDL> ImageOut

或这样:

IDL> ImageOut, filenames = myfiles

答案是无法确定。在过程ImageOut内部,可以用N_emements或Keyword_Set来检查为与关键字filenames相对应的的变量(即变量thesefiles)的赋值情况,遗憾的是,这些命令最多只能说明这些变量是否已经定义。但这并不同于变量是否存在。如果变量myfiles没有定义,那么N_elements,Keyword_Set都无法区别上述两种调用程序方法的区别。

为了帮助人们知道一个关键字是否已经使用,IDL5.0引入了新的函数arg_present。arg_present将返回1,如果参数(变量)是以关键字和或参数传递的(换句话说,关键字和参数被使用了),并且变量以引用的方式传递的。后面一点极其重要。否则,arg_present将返回0。如果关键字或参数已经使用了,但参数以传值方式传递,这时arg_present将返回0,就好像参数没有被使用或者并不存在一样。在使用这个新命令时应该倍加小心。

编写IDL函数

函数的定义和过程非常相似。也就是说,函数的参数和关键字的定义与过程中的一样。但也两点不同:(1)函数定义以Function而不是以pro开头;(2)函数通常返回一个单一的、特定的IDL变量给函数调用者。被返回的变量被称函数的返回值,它可以是任何一种有效的变量类型或结构。例如,它可以是一个很大的结构变量。实际上,这意味着在所有函数的return语句中必须有一个参数来保存函数的返回值(一个没有Return语句的函数将隐性地返回零)。

例如,为了把过程ImageOut改成一个函数,只需做一点改动,如下所示。变化部分已经用粗体字标出。

198

IDL IDL入门教程

Function ImageOut, lookhere, colortable = thiscolortable, Scale = scaleit, $

filename = thesefiles

Numparams = n_paras ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = findfile (‘*.img’, count = numfiles)

Print,’ number of file found:’, numfiles

For j = 0, numfiles-1 do begin

Openr, un, thesefiles[j], /get_lun

Image = bytarr (152,512)

Readu, lun,image

Free_lun, lun

If Keyword_Set (scaleit) then $

Thisimage = bytxl (image, top = 199) else $

Thisimage = image

Loadct, thiscolortable,l ncolors = 200

S = size (image)

Window, /free, xsize = s[1], ysize = s[2]

Tv, thisimage

Endfor

End

在IDL中,函数的调用语法不一样。(www.loach.net.cn]函数总是会返回一个值,因此把用来接收返回值的变量放在等号左边,函数调用名放在等号右边,而函数的所有参数和关键字都放在括号内,如下所示:

IDL> thisvalue = ImageOut (‘c:\data’, filenames = mydatafiles)

由于函数ImageOut没有Return语句,所隐性地返回为 0。

但是如果希望ImageOut返回打开和显示的文件的数目,可以这样编写程序。更改的地方已经用粗体字标出:

Function image, lookhere, colortable = thiscolortable, Scale = scaleit, $

扩展:idl / idl教程 / idl程序设计

filenames = thesefiles

Numparams = N_Params ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = findfile (‘*.img’, count = numfiles)

Print, ‘number of files found:’, numfiles

For j = 0, numfiles-1 do begin

Openr, lun, thesefiles[j], /get_lun

Image = bytarr (512,512)

Readu, lun, image

Free_lun, lun

If Keyword_Set (scaleIt) then $

Thisimage = bytscl (image,top = 199) else $

Thisimage = image

Loadct, thiscolortable, ncolor = 200

S = size (image)

199

IDL IDL入门教程

Window, /free, xsize = s[1], ysize = s[2]

Tv, thisimage

Endfor

Return, numFiles

End

如果想要显示和打印出某个特定子目录下的所有图片文件的文件名,可以键入如下代码:

IDL> numfiles = ImageOut (‘c:\data’, filenames = mydatafiles)

IDL> for j = 0, numfiles-1 do print, mydatafiles[1]

不管有10个还是100个文件,这段代码照样运行得很好。(www.loach.net.cn]

更多的函数是用来获得对变量操作的结果。例如,想得到一个矢量或一组数组的平均值,可以这样在文本编辑器中编写average函数:

Function average, data

Averageavalue = total (data)/n_elements (data)

Return, averagevalue

End

调用形式如下:

IDL> thisdata = [3,5,6,2,9,5,4]

IDL> avg = average (thisdata)

IDL> print, avg

4.85714

函数的返回值可以作为另一个过程或函数的参数,所以也可以把上面的三行代码写为一行:

IDL> print, average ([3,5,6,2,9,5,4])

4.85714

方括号和函数的调用

当调用一个IDL函数时,可能会有些迷惑。要分辨是函数调用,还是引用数组的下标,仅仅是通过检查IDL代码是很困难的。比如, 上述代码就显示出这方面的问题。如果没有其他信息,Average也可以认为是一个IDL变量。(一个函数和一个变量是可能同时名取为average的,但这是一种不明智的做法。)

为了增加代码的可读性,Research System公司在IDL5中引入了方括号来指定下标变量。从而就可以这样定义名为average的变量了,如下所示:

IDL>average=[4,6, 3,8,2,1]

它还可以这样引用:

IDL>number=average [3]

这种语法就可以将变量average和函数average区分开来了,因为函数average要求其参数用括号括起来。

用Forward_Function命令保留函数名

为了确保IDL编译器能区别函数调用而不是带下标的数组,可以用Forward Function命令来预先声明函数名称并为之保留。例如,如果想保留上述函数Average,可以键入:

IDL>Forward_Function Average

200

IDL IDL入门教程

注意:保留的函数名不必用引号括起。[www.loach.net.cn]

现在,如果在编译程序模块中,IDL编译器碰到average,并且跟有参数,那么它就认为这是函数调用而不是对数组元素的下标引用。

使用程序控制语句

与其他程序语言一样,IDL程序可以通过控制语句来控制程序语句的执行顺序。大多数情况下,一个程序控制语句包括布尔测试(通常是程序变量的一种表达式)、当测试为真时执行的命令和为假时的执行命令。或者,它包括一个计数器和反复执行某个语句的方式。重复执行某个语句的情况,取决于计数器的值。

IDL中表达式的真和假

在IDL中,一个表达式的真或者假取决于该表达式中的数据类型。真的情况可以概括如下: 奇数,非零的字节型、整型和长整型;

非零的浮点型、双精度型和复数类型(包括单精度和双精度);

非空的字符串类型;

在IDL中,通过布尔逻辑运算为非真的表达式,都为假。

注意,指针和对象不是通过真或假来衡量,而是分别由Ptr_valid或Obj_Valid命令来检查是有效还是无效。

第一中类型的例子是典型的IF…THEN… ELSE 控制语句。在IDL中是这样表述的:

IF test THEN statement1

其中,test 通常是一个布尔表达式,stattement1是IDL命令。

注意,test必须总是数值并且必须是在表达式调用前已经定义。例如,下面的表达式就会出错,因为变量 coyote没有定义 。

IDL> IF coyote EQ ‘tricky’ THEN Print, ‘Missed him!’

第二种类型的例子是典型的FOR循环控制语句。在一个FOR循环语句中,语句反复地被执行,直到计数器达到某个预设定的值。如:

FOR j=0,10, DO statement1

这里,j是计数器,statement1是将要执行的IDL命令.

例如下面的例子中,Print命令将执行11次,打印数值0到10.

IDL> For j=0,10, Do Print, j

将多个语句处理成单个语句

大多数控制语句(见上面)在语法中,要求是单个语句,因为对IDL解释器而言,所有的命令看上去都是一个命令。但这常常不是所希望的。例如,在某中测试的基础上,如果这种测试是真,就执行4个语句,如果为假就执行15个语句。

在IDL中,通过使用BEGIN 和END语句块,可以清楚地让IDL解释器将多个语句看作是单个语句。语句块以BEGIN始,以END结束。

例如,若在FOR循环中执行多个语句,上面的循环应写作 :

FOR j=0, 10 DO BEGIN

201

IDL IDL入门教程

Statement1

Statement2

Sratement3

END

当与BEGIN相匹配的多个END语句结束时, 程序变得难于阅读和交互操作。[www.loach.net.cn]这是因为在IDL中,使用控制语句的地方都可以以END语句结束。例如,在上述控制语句中,通常用ENDFOR命令代替END命令,因为可以用ENDFOR命令结束FOR循环。

FOR j=0,10 DO BEGIN

Statement1

Statement2

Statement3

...

ENDFOR

有效的END语句可以是ENDIF、ENDELSE、ENDFOR、ENDWHILE、ENDCAS和ENDREPEAT,下面将更详细地讲述这方面的知识。

扩展:idl / idl教程 / idl程序设计

If…Then…Else控制语句

IF…THEN…ELSE控制语句是应用最广泛的控制语句之一。它是以布尔测试或可以用“真”和“假”来衡量的表达式为基础的。(详细参见220页中的“IDL中表达式的真和假”)下例是一个简单的例子。

If (num GET 10) THEN index =2 ELSE index=4

在IF…THEN…ELSE中,ELSE是可有可无的,上述的语句也可以表示为:

If (num GET 10) THEN index =2

在使用多行语句时,IF…THEN…ELSE控制语句的语法就比较灵活了。注意,这多行IDL语句对IDL编译器来说必须是个简单IDL命令。如果在IDL命令行中,想要编写一个多行命令,就必须用行连续符和行连接符。例如,IF…THEN…ELSE控制语句可以写作:

IDL>IF (num GT 10) THEN BEGIN $

Index=2&$

Num=0&$

ENDIF ELSE THEN BEGIN $

Index=4&$

Num=-10&$

ENDELSE

如果代码是文件中,那么就完全没有必要使用行连续符和行连接符了。例如,在IDL主程序的过程或函数中,代码可以写成:

IF (num GT 10) THEN BEGIN

Index=2

Num=0

ENDIF ELSE BEGIN

Index=4

Num=-10

ENDELSE

202

IDL IDL入门教程

与在IDL解释器中相反,对IDL编译器而言,BEGIN和END语句已经暗示行连续符和行连接符。[www.loach.net.cn]但是,在IDL编译器中,IF…THEN…ELSE的语法也不是任意的。

例如,一些程序员喜欢将BEGIN语句和END语句对齐,这样可以一目了然地知道程序所指的什么,比如:

IF (num GT 10) THEN $

BEGIN

Index=2

Num=0

ENDIF ELSE

BEGIN

Index=4

Num=-10

ENDELSE

但是在IDL中,代码不能那样写,因为在编译中不能恰当地分开IF…THEN…ELSE控制语句,从而不能将其作为一个独立语句。如果想把用上述格式,就必须在代码中应用行连续符来连接,如下:

IF (num GT 10) THEN $

BEGIN

Index=2

Num=0

ENDIF ELSE $

BEGIN

Index=4

Num=-10

ENDELSE

条件表达式

在IDL5.1中,引入了一种新的条件表达式(?:)。用它可以代替IF…THEN…ELSE控制语句。如变量num大于或等于10, 设变量index=2,否则设变量index=4,可表述为:

Index=(num GE 10)? 2:4

在这个语句中,先进行条件判断(num GE 10),如果判断结果为真,变量index就设为问号右边和冒号左边的变量(或表达式)的值;如果为假,变量index就设为冒号右边的变量(或表达式) 的值。

FOR循环控制语句

FOR循环运用计数器来多次执行一个或多个语句,如:

A =1

FOR j=1, 10 DO BEGIN

Print, j

A = a * j * 2

ENDFOR

203

IDL IDL入门教程

在这种情况下,计数器j从1 开始直到10,这个语句将执行10次。[www.loach.net.cn]如果希望计数器J的步长不是1,就应当明确指定步长。例如,让j的步长为2,可参见下例:

A =1

FOR j=1, 10 DO BEGIN

Print, j

A = a * j * 2

ENDFOR

WHILE循环控制语句

WHILE循环控制语句循环不断到执行一条或多条语句,只要判断条件为真。如:

WHILE (number LT 100) DOES BEGIN

Index =amount * 10

Number =number +index

ENDWHILE

在进入WHILE循环之前,首先进行条件判断。

REPEAT...UNTIL 循环控制语句

REPEAT...UNTIL循环语句与WHILE 循环相似,不同之处在于,REPEAT...UNTIL循环语句在循环末尾进行条件判断,而不是在循环的开始。下面是一个简单的例子:

REPEAT BEGIN

Index =amount * 10

Number =number +index

ENDPEP UNTIL number GT 100

CASE控制语句

有时会遇到一个判断条件,其判断结果可能有一些不同的值,无法用真和假来表示,这时就应该使用case语句。

case语句常用于响应程序的不同的事件从而执行相应的代码。下面的代码是一个响应按钮事件的例子:

; What button caused the event?

Widget Control, eventide, Get Value=button Value

; Branch based on button value

CASE button Value OF

‘Sober’: TVScl, sobel (image)

‘Roberts’: TVscl, Roberts (image)

‘Boxcar’: TVScl, Smooth (image, 7)

‘Median’: TVScl. Median (image, 7)

‘Original Image’ :TVScl, image

‘Quit’: WIDGET Control, event. Top, / Destroy

204

IDL IDL入门教程

Elae: ; Do nothing at all

ENDCASE

注意,当判断条件与所列的条件不匹配时,ELSE语句定义了默认操作,在这里ELSE语句让事件进入事件处理代码中。(www.loach.net.cn]在使用ELSE时,后面必须有一个冒号。实际上,ELSE语句不一定得出现在case语句中,但是如果判断条件与所有列出的条件都不匹配时,程序就会产生错误。

如果在case语句中使用BEGIN和END语句,一定要小心,END语句既可以是块的结束语句,也可以是case的结束语句。有时多个CASE语句可以写成如下所示:

CASE this TEST OF

0:x=5

1: BEGIN

X =5

Y =x *2

END

ELSE: x=0

ENDCASE

注意,当有一个case语句满足条件时,case总是跳出来。换句话说,一旦一个case语句为真,程序将跳转到case语句的下一条程序语句。这和在C程序不一样。

GOTO控制语句

和所有的计算机高级语言一样,IDL也有GOTO语句。IDL程序员只有在的确需要时 ,才使用GOTO语句。任意使用GOTO语句会使简单的程序变得复杂。

GOTO语句指定一个程序标识符,当程序执行到GOTO语句时,程序就跳转到程序标识符所在的程序行。比如,可以用GOTO语句来打断一个FOR循环,见下:

FOR j =0, n DO BEGIN

Index= j * ! PI * 5.165 * thisTESTValue

IF index GT 3000.0 THEN GOTO, jump out

ENDFOR

Jump out: Print, index

程序标识符后面要有一个冒号。在程序标识符所在行应当是可执行语句,如这个例子所示,但也可以不是可执行语句。如果该语句无效,IDL程序将跳转到标识符下面的一个可执行语句。 错误处理控制语句

扩展:idl / idl教程 / idl程序设计

在IDL中,有多个错误处理语句,通常使用到的有:ON_IOError,ON_Error和Catch。 ON__IOError 控制语句

ON__IOError语句与IDL中的GOTO语句相似,它可指定一个标识符,当出现任何输入输出错误时,程序就跳转到标识符所在的行。

例如,ON__IOError语句可以在读取数据时进行相应的处理,如下所示:

ON_IOError, Problem

205

IDL IDL入门教程

OpenR, lun, filename, / Get_Run

Data =Bytarr 9256,256)

ReadU, lun, data

Free_Lun , iun

...

RETURN

Problem: Print, ‘Problem reading data. Returning...’

IF N_Elments (lun) NE 0 THEN Free_Lun ,lun

RETURN

END

在GOTO语句中一样,注意程序标识符后的冒号,程序标识符所在行不一定必须是有效的IDL语句。(www.loach.net.cn]

ON_Error 控制语句

在程序运行过程中出现错误时,ON_Error 控制语句指明了 IDL 应该怎样做。与其他的控制语句不一样,ON_Error 控制语句并不执行一个新的IDL语句,而是指出错误出现时应采取的措施。ON_Error命令的有效参数为0,1,2,3。表11表明了程序出错时采取的措施。

1

2

3 行动 当程序内容模块引起错误时立即停止。这是错误的行动。 立即停止返回主程序。主程序在命令进入程序行的地方。 立即停止返回程序模块称为模块错误。 立即停止返回程序模块,登记环境(这可能不是当前模块。)

表11 程序出错时ON_Error 控制语句采取的措施。

当程序出现错误时,IDL新手通常不之所措。因为默认的行为(ON_Error设为0)是在程序出错的地方停止下来。由于受IDL提示的影响,许多用户认为错误出现在IDL主程序中。当用“HELP”命令时,他们非常失望,因为所有的变量已经消失,不存在了。

用户实际看到是位于已经破坏了的程序中的变量。使用命令RETALL(返回IDL主程序)将会恢复消失的变量,从而获得它们的实际值。

RetAll是用户最重要的工具。在程序不能正常退出时,有经验的用户常常自动用RetAll命令来恢复。特别是在编写模块程序时。如果忘记了这个重要的命令,程序将会常常变得让摸不着头脑,尤其是在编写IDL程序的时候。

为了避免用户在使用这个程序时感到迷惑,最好把 On_Error,1加到正在调试的程序中。如果程序出错了,IDL将隐性地执行RetAll命令,从而返回到IDL主程序上。

Catch 控制语句

第三种控制语句,即Catch 控制语句,可能是最有效的,它与GOTO语句相似,但也不完全一样。Catch 控制语句调用如下:

Catch,error

这里的error是一个变量名。(当然,变量名称可以取自己喜欢的)。当执行到这条语句时,IDL为该特定程序模块记录了一个Catch错误处理语句,同时,将该变量(即Error)设为0。 206

IDL IDL入门教程

若在程序运行过程中出错时,且在该程序中有相应的Catch错误处理语句,则该变量被赋予相应的错误值(每个错误都有与之相对应的数值),然后程序跳转到Catch语句后的第一条语句。[www.loach.net.cn)

在实际操作中,CATCH语句行包括一块错误处理代码。例如,要读一个文件,这不可避免会出现许多错误,就可以用Catch语句俩来进行错误保护。

Catch ,error

IF error NE 0 THEN BEGIN

Catch, / Cancel

Print, ‘Problem reading data file .Returning…’

IF N_Elements (lun) NE 0 THEN Free_Lun ,lun

RETTURN

ENDIF

OpenR, lun, filename, /Get_Lun

Data=BytArr (256,256)

ReadU, lun, data

Free_Lun, lun

Catch, /Cancel

注意,Catch错误在任何时候都可以忽略。上个特殊的例子中,在错误语句代码中的第一行就忽略了所出现的错误。如果将在错误保护程序中也有错误,这绝对是不允许的。后面,程序可能会产生新的错误,并且可能会有任意多个catch错误,但是在任何时候,只有一个错误处理语句。注意:catch语句也可以用来捕获输入输出错误的。那和On_INError有什么关系呢?

错误处理语句的优先级

错误处理语句有个优先级关系,程序模块中将会出现的命令取决于这个优先级关系和在该程序模块中使用的错误捕获语句。可以根据这个关系,找到恰当的错误处理语句。它们的关系如下所示:

On_Error?catch?on_error

如果在程序模块中使用了On_IOError,那么On_IOError将屏蔽其它的错误处理语句。如果使用的是Catch语句,那么它将屏蔽On_Error语句。

要是没有任何错误处理语句,那么默认的On_Error将会在程序模块中起作用。

编译和执行IDL程序模块

在IDL编译程序中有三个可执行的命令,它们是.Run、.Rnew和.Compile。其中,.Run命令是最早的可执行命令。早期版本的IDL中,是没有过程和函数的,只是一些命令的简单组合。.Run命令是用来编译和执行那些称为 IDL主程序的命令组合。

自从主程序有了自身的变量后,内存的分配就显得较为重要了。尤其是其他程序编译后,每个主程序都有自己的变量。这样,就引入了.Rnew命令。Rnew命令与.Run命令很相似,不同之处在于,Renew命令在主程序编译和运行之前将删除所有已经存在的变量。

后来,IDL中引入了程序和函数的概念。实际上,.Run和Rnew也用来编译新的程序模块,仅是编译而不运行。IDL新手对于.Run命令几乎什么都不运行(除了偶尔运行主程序外)感到很迷惑。其实,在IDL4中引入的.Compile命令所做的,是以前版本中.Run命令所做的事,即编译过程和函数。

207

IDL IDL入门教程

如今,人们已经普遍接受用.Compile命令编译过程和函数,用.Run(.Rnew)命令编译和运行主程序。[www.loach.net.cn]编译名为Cindex.pro文件可以用以下几种形式。

IDL>.Run cindex

IDL>.Rnew cindex

IDL>.Compile cindex

注意,使用这三个命令时,文件名不必用引号括起,也没有必要用扩展名.pro(如果文件中用了一个不同的文件扩展名,就必须指明文件名和扩展名)。几乎所有的IDL文件以Pro为扩展名。在不同的操作系统上,扩展名是区分大小的,比如说Unix,文件名称也失去分大小写的。一般地,在区分大小写的操作系统上,最好将文件名称都写成小写,这对文件的自动编译是非常有必要的。(关于自动编译的详细信息请参见230的程序自动编译和运行规则)。

扩展:idl / idl教程 / idl程序设计

要运行一个已经编译的程序,只要IDL命令输入行中健入程序模块名。例如,用上述已编译过的程序模块,应健入:

IDL>CIndex

在IDL内部,模块名称是不区分大小写的。

程序编译规则:

对于一个文件中有多个程序模块,程序编译规则对他们的顺序作出了要求。文件中的程序模块是一个接一个地编译,直到满足某个条件。下面的规则列出了这些条件。

规则1:编译到主程序后,编译就会停止,接着编译和运行主程序。

这个规则表明了在一个文件中,只允许有一个主程序模块。如果要编译所有的程序模块,主程序模块必须是该文件中最后一个程序模块。

规则2:编译到与文件同名的程序模块时,文件将停止编译。在这种情况下,与文件同名的程序模块被编译后,立即执行该模块。

这就是说,如果想编译文件中的所有程序模块,与文件同名的程序模块应当放在最后。换句话说,如果最后一个程序模块是一个名为ImageOut的函数,文件名应为ImageOut.pro。

规则3:编译到文件末尾或适合其他规则时,文件将停止编译。

这个规则表明,如果此文件中没有一个主程序或一个与文件同名的程序模块,文件中所有的模块将会被编译,并且不会运行任何一个程序模块。

程序编译和自动运行规则

当过程或函数出现在IDL命令,或IDL命令行,或IDL代码中,它们会自动地被编译和运行。例如:

过程或函数所在的文件存在于当前工作路径或由!PASH系统变量指定的路径中。

过程或函数名与文件名相同。在区分大小写的操作系统中,如UNIX,文件名必须用小写。

这就意味着,在实践中,那些非常重要的、经常被其他程序调用的程序模块应该放在自己的文件或与之同名的文件中。其他任何模块应位于主程序模块之前,或者它们是主程序的辅助性模块。

注意,IDL的系统过程,如 Plot,Surface等,的优先级比其他命令高,因此,应当尽量避免自己的程序与IDL内置命令同名。

208

IDL IDL入门教程

特殊编译命令

在IDL程序模块中,有两个特殊的编译命令。[www.loach.net.cn]可以在程序模块中调用它们来编译其他的程序模块,这就是是Resolve_Routine和Reserve_All。

Resolve_Routine命令,以IDL程序模块名作为参数,编译与之同名的文件。换句话说,它好比是使用.Compile 命令来编译文件。Resolve_Routine命令的优点在于可以用在IDL程序模块里来编译其他程序模块,而Compile命令只适用于IDL命令行。例如:要编译Cindex.pro文件中的所有程序模块,可以健入:

IDL>Resolve,‘Cindex’ 如果程序模块是一个函数而不是过程,应该使用IS_Function关键字,如: IDL> Resolve_Routine‘Congrid’, /IS_Function

编译时,不管它以前是否已编译过,程序仍然将被编译。

Resolve_All 命令的功能与之很相似,不同之处在于,它不是仅编译一个特定的文件。Resolve_All 命令将交互式地搜索IDL内存中任何未编译的程序模块并同时编译它们。Resolve_All也遵循自动编译的一般规则,这意味着如果模块不能自动编译,Resolve_All 命令将无法查找到该模块,当然也就无法编译了。

若准备将一个应用程序的save文件运行在其他计算机上,因为另外的计算机上配置可能不一样,这时,Resolve_All 命令非常方便。例如,如果应用程序名为BigApp,可以用Resolve_All 命令来编译,并将与这个程序相关的库文件和程序文件也编译在内。如下所示:

IDL>.Compile BigApp

IDL>Resolve_All

IDL> Save, /Routines, File=’bigapp.sav’

然后,其他用户只需要恢复这个save文件即可,同时所有的程序模块就已经编译完毕,可以使用了。而用户的计算机上就不必有源代码文件。如下:

IDL>Resolve ‘bigapp’

IDL> BigApp

注意,用Save命令编译的过程和函数在IDL的其他版本中未必能够正常使用。(但Save文件中的数据和变量在不同IDL的版本是一致的。)因此,一般来讲,用户必须用创建save文件版本的IDL来恢复save文件。

209

IDL IDL入门教程

第十一章 组件编程技巧

本章概述

这一章将介绍一些重要的、经常使用的组件编程技巧。[www.loach.net.cn]和前一章介绍的技巧一起使用,应该可以编写出功能强大的组件程序。在本章中,将学习以下内容:

1. 如何扩充组件程序的功能;

2. 如何保护具有公共块的组件程序;

3. 在组件程序中如何使用指针;

4. 组件程序之间或者是组件程序模块之间的通信新方法;

5. 如何编写适合在8位和24位的显示器上运行的程序;

6. 如何保护程序的颜色;

7. 如何将图形输出为适合广域网传输的文件和PostScript文件;

事实上,一个不能容易扩充或添加的组件程序是一个差劲的程序。任何一个组件程序,首先必须保证能被简单扩充。实现扩充最简单的方法是让组件程序能够调用其他组件,或被其他组件调用。在很大程度上,这意味着用模块化或面向对象的方式来编写程序,可以提高程序的聚合度。 回想一下上一章中所写的XimageBar程序,作为示例,它的功能非常有限。实际上,它仅仅实现了适于某一种特殊显示设备的、可改变大小的图形窗口,对其稍加扩充便是一个很棒的程序。本章将引导读者对其进行扩充(如果需要一个起点,可以利用与本书配套使用的文档中找到xImageBar.3.pro)。

改变颜色表

IDL中,改变颜色表常用的工具是组件程序XloadCT,这是一个很不错的颜色表加载程序,并可以在IDL命令行中调用。而它的局限性在于它是个组件程序在组件,其主要不足是它将颜色向量存储在公共块中。这意味着,在任何时刻只能有一个XloadCT组件可以使用。例如,输入如下三行命令:

IDL>XloadCT

IDL>XLoadCT

IDL>XloadCT

无论输入多少次,仅有一个XloadCT出现在屏幕上。这便是它与其他组件的不同之处,如XimageBar。试着输入以下命令:

IDL>img1=LoadData(7)

IDL>img2=LoadData(11)

IDL>XimageBar, img1

IDL>XimageBar, img2

这时可以看到两个XimageBar程序同时出现在屏幕上。(当他们显示时,必须将其移开,因为程序每次调用时都出现在同一位置)其实,可以获得任意多个的组件程序。这两个程序间有那些区别呢?

扩展:idl / idl教程 / idl程序设计

210

IDL IDL入门教程

保护公共块

区别在于,XloadCT采用在任意时刻确保只有一个程序运行的方法来保护公共块。[www.loach.net.cn]这是任何一个使用公共块的组件程序所必须的要求的,同时也是防止其它组件使用该公共块的最好方法。它的缺点就是使组件程序的适用性降低。

XloadCT在它的组件定义模块中使用了Xregistered命令来实现保护公共块的目的。Xregistered命令将程序的名字作为参数,如果那个名字的程序已经注册到Xmanager,Xregistered命令返回1,否则返回0。

如果检查一下XloadCT的源代码(它在IDL安装目录的lib子目录下),可以在组件定义模块的顶部附近,发现这样一行,任何组件被创建之前:

IF Xregistered(‘xloadct’)NE 0 THEN RETURN

换句话说,如果名为‘xloadct’的程序已经注册,XloadCT 程序将不创建任何组件而直接返回。程序名就是与Xmanager一起调用时的注册名,它是区分大小写的。(这便是为什么当在用Xmanager 来注册程序时全用大写或全用小写来拼写程序名是个好主意的原因。)

一个可选择颜色表的工具

在屏幕上能够显示一个程序的多个实例,是一大优势。例如,XimageBar是一个颜色敏感的应用程序,它可以很好地在屏幕上显示两个不同的实例,不同程序实例使用不同颜色表。

IDL>LoadCT, 4,Ncolors=75

IDL>XImageBar,img1, Ncolors=75

IDL>LoadCT,3,Ncolors=75,Bottom=75

IDL>XimageBar,img2,Ncolors=75,Bottom=75

如果要在这些窗口中改变颜色将会怎样?可以用XloadCT 改变一个或另一个窗口的颜色,而不是同时改变这两个窗口的颜色。

IDL> LoadCT, Ncolors=75

或者

IDL> XloadCT, Ncolors=75, Bottom=75

实际上,如果这里的每个图形窗口能够只是改变自己的颜色,并且是通过自己的颜色表工具来实现,那将会更好。其实就有这样一个名叫Xcolors的工具,在与本书配套使用的文档中可以找到它。

Xcolors用它的标题来作为它的注册名字(详细信息请参阅Xcolors的源代码).这样,只要每个程序实例有不同的标题,就可拥有多个Xcolors实例了。按如下方法试一下:

IDL> Xcolors, Ncolors=75, Title=’Window 1 Colors’

IDL> XimageBar, img1, Ncolors=75

IDL> Xcolors, Ncolors=75, Bottom=75, Title=’Window 2 Colors’ 这个信息

IDL> XimageBar, img2, Ncolors=75, Bottom=75

现在每个XImageBar程序都有自己的XColors程序,并能改变所在窗口的颜色。

但是怎样从XImageBar程序调用Xcolors呢?这很容易实现。打开xImageBar.pro文件,在组件定义模块里找出下行:

quitID = Widget_Button(field, Value=’Quit’, Even_Pro=’XimageBar_Quit’)

211

IDL IDL入门教程

接着,在上面一行的下面添加如下几行,用以在菜单栏里创建一个Colors按钮,Colors下面在创建一个Image Colors按钮,并将XimageBar_Colors事件处理程序和Image Colors按钮联系起来.

Colors = Wudget_Button (menubaseID, Value =’Colors’)

icolors = Widget_Button (colors, Value =’Image Colors’, $

Event_Pro=’XimageBar_Colors’)

一个关键字继承的问题

在编写事件处理程序之前,还需解决一个小问题。(www.loach.net.cn)很显然,事件处理程序XimageBar_Colors将要调用XColors程序,要正确地调用它,就必须知道应该加载多少种颜色以及在哪里加载颜色表。换句话说,需要知道关键字Ncolors 和Bottom的值。

在XImageBar程序中,如果extra结构变量里有Ncolors 和Bottom信息存在的话,Ncolors 和Bottom是可以通过关键字继承机制获得的。(详细信息请参阅240页的“使用关键字继承”)。那几个变量是不会产生的,除非调用XImageBar程序时出现不认识的关键字。然而,在XImageBar程序里Ncolors和Bottom要能够方便使用,并且应该将它和其它有用的程序信息一起存储到info结构中。

获取这些信息的方法之一就是从extra变量获得,如果调用XimageBar程序时使用了Ncolors和Bottom关键字,那么变量extra就会包含这方面的信息。代码如下所示。(请不要键入这些代码,因为这不是解决问题的最好方法。在这只是显示它的不足之处。)

IF N_Elements (extra) EQ 0 THEN BEGIN

extra ={Junk : 1}

ncolors = !D.Table_Size

bottom = 0

ENDIF ELSE GEGIN

fields = Tag_Names (extra)

dummy = Where (fields EQ ‘NCOLORS’, count)

IF count GT 0 THEN ncolors = extra.ncolors ELSE $

ncolors =! D.Table_Size

dummy = Where (fields EQ ‘BOTTOM’, count)

IF count GT 0 THEN bottom = extra.bottom ELSE $

bottom = 0

ENDELSE

这些代码中,使用了Tag_Names和where命令来查找extra变量里是否存在Ncolors和Bottom字段。它是解决这种问题的一个可行方法,但是它有最基本的缺陷。如果严格按照上面Where命令中的拼写方式,那么就可以找到这些字段。换句话说,当XImageBar程调用如下,这种方法就能够正常运行。

XimageBar, Ncolors = 200, Bottom = 100

如果XImageBar程序像下面一样调用,就不能正常运行了。

XimageBar ,Ncol =200 ,Bot = 100

通过关键字继承,关键字Ncol和Bot传递给XImageBar程序后被接受了,但代码还是不能正常运行。原因是它们不能够以一种可靠的方式从extra变量里传出,除非知道它们的确切拼写方式。

关于关键字继承,有很重要的一点值得一提。要把关键字的值从一个程序模块传到另外一个程序模块,关键字继承是一个非常有效的方法,但是如果需要获得在不同程序模块里的特定关键 212

IDL IDL入门教程

字的值,最好是在每个单独的模块中定义它们。[www.loach.net.cn)

例如,这里的最好的解决方法就是,在XImageBae组件定义模块中直接添加NColors和Bottom关键字。找出XImageBar程序第一行,增加这些关键字,并在没有定义的情况下,设置这些关键字的默认值。如下:

扩展:idl / idl教程 / idl程序设计

PRO XimageBar , image, _EXTRA = extra

作如下修改:

PRO XimageBar , image, _EXTRA = extra, Ncolors =ncolors, $

Bottom = bottom

IF N_Elements (ncolors) EQ 0 THEN $

NCOLORS =! D.Table_Size

IF N_Elements (bottom) EQ 0 THEN bottom = 0

把变量ncolors和bottom存储到info结构中,在组件定义模块底端找出下面几行:

ImageBar, image, _Extra = extra

Info ={image : image, $ ; The image data.

extra : extra, $ ; The exta ImageBar keywords.

wid : wid, $ ; The window index number.

drawID :drawID} ; The draw widget identifier.

使用这两个变量,通过如下修改代码添加到info结构:

ImageBar, image, _Extra = extra, Ncolors = ncolors, Bottom = bottom

Info = { image: image, $ ; The image data.

ncolors: ncolors, $ ; The number of colors.

bottom: bottom, $ ; The starting color index.

extra: extra, $ ; The extra ImageBar keywords.

wid: wid $ ; The window index number.

drawID: drawID } ;The draw widget identifier.

在XimageBar_Resize事件处理程序里,还需要修改调用ImageBar程序的那一行程序,找出此行:

ImageBar, info.image, /EraseFirst, _Extra = info.extra

修改如下:

ImageBar, info.image, /EraseFirst, _extra = info.extra, $

Ncolors = info.ncolors, Bottom = info.bottom

编写颜色表工具的事件处理程序

最后准备编写XimageBar_Colors事件处理程序,这个非常简单。从存储器里取出info结构,根据适当的ncolor和sbottom信息,调用Xcolors程序,然后将info结构放回。确保文件中事件处理程序放在组件定义模块之前,编写如下所示:

PRO XimageBar_Colors, event

Widget_Control, event.top, Get_UValue = info, /No_Copy

XColors, Ncolors = info.ncolors, Bottom = info.bottom, $

Title = ‘XimageBar Colors’

Widget_Control, event.top, Set_Uvalue = info, /No_Copy

END

保存并编译程序。程序能运行吗?要使程序正常运行,可能需要检查并修正其中的错误。仔 213

IDL IDL入门教程

细检查代码以免有问题。[www.loach.net.cn)

IDL> . Compile XimageBar

IDL> image = LoadData(7)

IDL> LoadCT, 4, Ncolors = 100, Bottom = 50

IDL> XimageBar, image, Ncolors = 100, Bottom = 50

如果同时运行两个XimageBar程序,会发生什么情况?

IDL> LoadCT, 4, Ncolors = 75

IDL> XimageBar, image, Ncolors = 75

IDL> LoadCT, 3, Ncolors =75, Bottom = 75

IDL>XimageBar, image, Ncolors = 75, Bottom = 75

可以同时运行一个以上的程序吗?

答案是否定的。在任何时候只能有一个XColors程序在屏幕上运行,因为这两个程序实例使用了相同的标题来表示XColors程序。现在要做的是给每个程序一个独一无二的名字,最好是在程序里使用一个独一无二的数字来表达。最好是使用窗口索引号,那是绝对独一无二的,而且它也在info结构中,使用非常方便。

修改程序里的XColors命令,如下:

Xcolors, Ncolors = info.ncolors, Bottom = info. Bottom, $

Title = ‘XimageBar Colors (‘+ strTrim(info.wid, 2) +’)’

如下,又会出现什么情况呢?

IDL> LoadCT, 4, Ncolors = 75

IDL>XimageBar, image, Ncolors = 75

IDL>LoadCT, 3, Ncolors = 75, Bottom = 75

IDL>XimageBar, image, Ncolors = 75, Bottom = 75

每个窗口都有一个(也只有一个)颜色变换工具,但是屏幕上可以有许多颜色转换工具。 指定Group Leader

这个程序几乎能正常工作了。试一下这个。使XImageBar和XColors程序同时出现在屏幕上。(运行XImageBar,再按一下Image Colors按钮.)

IDL> LoadCT, 4

IDL> XimageBar, image

现在选择Quit按钮关闭程序,会出现什么情况?

XColors程序仍然在屏幕上,这并非是所想要的,因为从某种意义上来说,程序Xcolors是“属于”程序XImageBar的。当然,假设一个程序是由另一个程序产生的,如果另一个程序已经消失,那么这个程序也应该随之消失。

IDL用一个Group Leader的概念来处理这种问题。组件程序可以有Group Leader,即为另外一个组件(通常是顶级base)。当Group Leader死亡或销毁,所有属于它的组件也会死亡或销毁。 在本程序中,最好让XImageBar程序的顶级base成为XColors程序的Group Leader。幸运的是,Xcolors可以通过Group_Leader这个关键字来接受Group Leader所指定的组件标识符。利用这一点,使XimageBar成为Xcolors的Group Leader,修改如下:

Xcolors, Ncolors = info.ncolors, Bottom = info.bottom, $

Title = ‘XimageBar Colors (‘ + StrTrim(info.wid, 2) + ‘) ‘ ,$

Group_Leader = event.top

知道在这个事件处理程序中,event.top所标识的是哪一个组件呢?如果还不知道,请参阅256 214

IDL IDL入门教程

页的“事件结构中的公共字段”。[www.loach.net.cn)

保存和编译并运行程序。出现了什么情况?这时,当XImageBar程序选择quit按钮时,XColors也会消失。

给组件程序增加Group Leader

对于组件程序,指定一个Group Leader是特别有用的。如果能够使任一组件程序成为其它某一组件程序的成员,那么就能够轻易地将组件程序组合在一起。设想一下,如果在其它组件程序中,XimageBar的功能显得非常实用的。如果能为XImageBar指定一个Group Leader,那么在其它程序中增加它的功能就如同定义一个按钮和添加一个调用XImageBar程序的事件处理程序一样简单了。当调用程序被销毁,程序XimageBar也被销毁。

扩展:idl / idl教程 / idl程序设计

怎样指定一个顶级base作为Xcolors的Group Leader呢?

非常简单。查找到XImagebar程序的定义行,可以找到如下几行:

PRO Xcolors, Ncolors = ncolors, Bottom = bottom, $

Group_Leader = group

更进一步,上面代码中变量group所标识的组件被设置为Xcolors程序的顶级base的Group Leader。代码如下:

Xmanager, ‘xcolors’, tlb, Group_Leader = group

幸运的是,group变量不一定必须得定义,因此,不必在变量group没有定义的情况下给它赋一个默认值。

在作者看来,每一个组件程序都应该定义一个Group_Leader关键字,这样就可以在其它任一程序中都可以调用它。这是一个简单但是很有用的扩充组件功能的方法。

添加这个功能到XImageBar程序,找出这个程序的定义行。

PRO XimageBar, image, _Extra, Ncolors = ncolors, $

Bottom = bottom

修改后如下:

PRO XimageBar, image, _Extra = extra, Ncolors = ncolors, $

Bottom = bottom, Group_Leader = group

并在组件定义模块的底部找出Xmanager命令行:

Xmanager, ‘xImageBar’, tlb, /No_Block, $

Event_handler = ‘XimageBar_resize’

并作如下修改:

Xmanager, ‘xImageBar’, tlb, /No_Block, $

Event_Handler = ‘XimageBar_Resize’, Group_Leader = group

修改后的程序可以在与本书配套使用的文档中找到,文件名为xImageBar.4.pro。

在24位显示器上改变颜色表

这个程序最终能够在使用颜色索引模式的8位显示器上正常运行了,在在那种环境中,图像像素颜色与颜色表向量中的当前值连在一起。但是,在使用RGB模式的在24位显示器上程序工作就不一样了,其中的颜色由RGB值直接指定,而与颜色表向量的当前值无关。在这些显示器上,如果颜色表向量发生变化或它们与颜色表向量的当前值不一致时,图形或图像必须重新显示。(详细信息请参阅84页的“颜色索引模式和RGB模式的使用”。

215

IDL IDL入门教程

在24位显示上也能在窗口里重新显示图形,简单得如同执行ImageBaar命令。[www.loach.net.cn)问题是并不知道什么时候去做它。

在颜色表发生变化后,图形应该重新显示。但是怎样知道它什么时候发生变化呢?答案是,无法知道它什么时候改变。颜色表或许已经被其它组件程序完全改变了,但是无法将颜色表已经改变的事件返回给程序。

当XColors程序改变颜色的时候,它应该能传递消息(或称为事件)给XImageBar程序。事实上,XColors程序已经是这么编写的了。

创建事件并将事件传递给其它程序

这里的关键是,使用关键字NotifyID,将一个的具有两个元素的组件标示符数组(或一个二维数组,如果有不止一个组件需要通知)传递给XColors程序。数组的第一个元素是当颜色表发生变化时要通知的组件的标志符。它可以是任何组件程序中任何组件的组件标志符。数组的第二个元素是包含了info结构的组件标示符。(实践中,第二个元素常常被设置为Event.Top,但也不一定非得如此。)

例如,假设传递到关键字的数组定义如下:

array = [event . id, event . top]

其中,event.id标示一个特定的组件(在这里即为产生事件的组件),event.top标示的是顶级base,即为第一个组件所在层次结构中的顶级base,同时也是info结构存储的地方。

当颜色表改变的时,Xcolors就使用这个信息建造一个伪事件,伪事件定义如下:

colorEvent = {XCOLORS_LOAD, $

ID : array[0], $

Top : array[1], $

Handler : 0L, $

R : !D .Table_Size, $

G : !D . Table_Size, $

B : !D .Tabe_Size}

其中,字段ID被设置为数组的第一个元素,字段TOP设置为数组的第二个元素,R、G和B字段的值则是从当前颜色表向量中获得。

使用Widget_Control命令中的关键字Send_Event,讲伪事件被放置于事件队列,如下所示:

Widge_Control,array[0],Send_Event=colorEvent

这个事件结构很像由组件程序产生的其他事件结构。然后也和他事件一样按顺序处理。事件处理程序在获取一个事件后做什么完全取决于事件处理程序本身。

看一下XimageBar程序能做什么。在XimageBar_Colors事件处理程序中找出下行:

Xcolors,Ncolors = info.ncolors,Botton = info.bottom,$

Titile = ‘XimageBar Colors (‘_ StrTrim(info.wid,2) +’)’, Group_Leader = event.top

增加NotifyID关键字,如下所示:

Xcolors, Ncolors = info.ncolors, Bottom = info.bottom,$

Title = ‘XimageBar Colors (‘+StrTrim(info.wid,2)+’)’,$

Group_Leader = event.top,NotifyID = [event.id, event.top]

在本程序中,event.id标识的是Image Colors按钮。(选择此按钮,原始事件就进入到XimageBar_Colors事件处理程序)这样,当Xcolors改变颜色表时,Xcolors伪事件就会被送到相同的程序处理程序中。因而必须修改Ximage_Colors事件处理程序,以便接受这个事件。

当前的Ximage_Colors的事件处理程序如下:

216

IDL IDL入门教程

PRO XimageBar_Colors, event

Widget_Control, event.top, Get_Uvalue = info, /No_Copy

Xcolors, Ncolors = info.ncolors, Bottom = info.bottom, $

Title = ‘XimageBar Colors (‘ + StrTrim(info.wid, 2) +’)’, $

Group_Leader = event.top, NotifyId = [event.id, event.top]

Widget_Control, event.top, Set_Uvalue = info, /No_Copy

END

这个事件处理程序是为了对Image Colors按钮作出反应而设置的,现在需要对两种事件作出反应:一个是按钮事件和一个从XColors送来的事件。[www.loach.net.cn]使用命令,通过使用Tag_Names命令中的Structure_Name关键字,可以找出进入这个事件处理程序的事件类型。记住,Tag_Names命令总是返回一个大写的字符串。如事件来自Xcolors程序,检查一下看显示器是否是24位颜色(景深大于256)。如果是,重新显示图形。

扩展:idl / idl教程 / idl程序设计

修改事件处理程序如下:

PRO XimageBar_Control, event

Widget_Control, event.top, Get_UValue = info, /No_Copy

ThisEvent=Tag_Names(event, /Structure_Name)

CASE thisEvent OF

‘WIDGET_BUTTON’: BEGIN

Xcolors, Ncolors = info.ncolors, Bottom = info.bottom, $

Title = ‘XimageBar Colors (‘ + StrTrim(info.wid,2) +’)’, $

Group_Leader = event.top, NotifyID = [event.id, event.top]

ENDCASE

‘XCOLORS_LOAD’:BEGIN

Device,Get_Visual_Depth=ThisDepth

IF thisDepth GT 8 THEN BEGIN

West,info.wid

ImageBar, info.image,Ncolors=info.ncolors, $

Bottgom=info.bottom,/EraseFirst,_Extra=info.extra

ENDIF

ENDCASE

ENDCASE

Widget_Control,event.top,Set_Uvalue=info,/No_Copy

END

如果有一台24位真彩显示器,可以试着运行一下这个程序,它现在可以顺利地运行在8位及24位显示器上,在与本书配套使用的文档中,可以找到该程序的源代码,文件名为xImageBar.5.pro。 在组件程序中使用指针

不管入如何,在组件程序中已经使用道了指针。使用顶级base的用户值是一个指针技术,尽管准确地讲,应当说是句柄,因为“指针”(用户值)没有直接指向信息。(必须将用户信息拷贝到一个局部变量中)

但是指针在组件程序里还有其他重要作用。例如,当inf结构中的被引用的数据数据类型或数据组织改变,指针就常用于info结构。

如果程序增加一项功能,使它能装入一个新的图象到XiamageBar程序中,那将会是个好例 217

IDL IDL入门教程

子。[www.loach.net.cn]如在File 按钮底下增加一个Open按钮,在组件定义模块中找到如下代码:

fileID=Widget_Button(menubaseID,Value=’File’,Menu=1)

quitID=Widget_Button(fileID ,Value=’Quit’, Event_Pro=’XimageBar_Quit’

在这两行代码之间增加一行代码,用于创建Open按钮,并使得它有自己的事件处理程序,即XimageBar_Open。当使用它时,给Quit按钮添加一个分隔符,用户就会知道在File菜单栏下这个按钮与其它按钮作用有些不同,键入:

fileID=Widget_Button(menubaseID,Value=’File’,Menu=1)

openID=Widget_Button(fileID,Value=’Open…’,$

Event_Pro=’XimageBar_Open’)

QuitID=Widget_Button(fileID,Value=’Quit’$

Event_Pro=’XimageBar_Quit’,/Separator)

很显然,如果打开一个文件,把其它图象读入此程序,需要存储图像到局部变量中。当前图像存储在info结构中,为什么不放在此处?

只要新老图像的尺寸和数据类型都一致,程序就可以正常运行。但是通常情况不是这样的,在没有完全重新构建info结构之前,原始info结构中的图像数据所分配的内存是不能改变的。(info结构重新构建后可以容纳更大的图像,因为info结构是个匿名结构而不是命名结构,匿名结构就可以这样操作)与创新构建info结构相比(那样会陷入另一个info结构的定义中,也增加了代码的复杂性,从而会导致操作更加困难),一个好的解决办法就是定义一个指针,用于指向图像数据。指针所指向的变量可以动态的改变它的数据类型和组织,就像其它IDL变量一样。

在info结构中,要使image字段成为指针,就必须使用Ptr_New命令。在靠近组件定义模块的底端,定义info结构的地方找出下面这段代码:

info={image:image,$; The mage data.

换成指针,如下:

info={image:Ptr_new(image),$; The mage data.

下一步,在所有程序实例中,将info.image转换为指针形式。

Info.image必须转换为*info.image

搜索字符串info.image,并用字符串*info.image代替它,就会发现有一行出现过两次:一次在XimageBar_Colors事件处理程序中,一次在XimageBar_Resize事件处理程序中。替换它们:

ImageBar, *info.image, Ncolors = info.ncolors, $

Bottom = info.bottom, /EraseFirst, _Extra = info.extra

下一步,可以编写XimageBar_Open事件处理程序了。可以使用Get_Image命令,允许用户

。实际打开一个图像数据文件(如果想知道文件的大小,请参阅313页的附录B:数据文件描述)

上,可以使用与XImageBar组件定义模块顶部的代码相似的代码。在文件中组件定义模块代码之前添加这些代码。

PRO XimageBar_Open, event

Image = GetImage(Cancel = canceled, parent = event.top)

IF canceled THEN RETURN

s= Size (image)

IF s [0] NE 2 THEN BEGIN

Message, ‘Image argument must be 2d.’, /Continue

Return

ENDIF

注意在命令GetImage中关键字Parent的用法。在模式组件程序中,关键字Parent是必须要的。这在291页“创建模式对话框”中已经讨论过。

218

IDL IDL入门教程

程序进行到这里,可以假设有一个有效的图像,这个新图像必须存储在指针内,并在窗口中显示。[www.loach.net.cn]添加下行代码,从存储器里取出info结构,替换info结构中的图像数据并显示图像,然后将info结构返回给存储器。

Widget_Control, event.top, Get_Uvalue = info, /No_Copy

*info.image = image

Wset, info.wid

ImageBar, *info.image, Ncolors = info.ncolors, $

Bottom = info.bottom, /EraseFirst, _Extra = info.extra

Widget_Control, Event.top, Set_Uvalue = info, /NO_Copy

END

编译并运行程序,看变化后能否正常运行?

IDL> .compile XimageBar

IDL> .XimageBar, LoadData(7)

还有最后一个问题要解决。当退出程序时,在程序中使用的指针必须释放。指针数据是全局范围的并且在IDL的当前任务中会一直存在中,除非它们被显式地被释放。

使用Cleanup过程防止内存泄露

为防止内存泄露,必须在程序销毁之前清除那些会导致内存泄露的东西(如指针,对象,位图窗口等)。有多种方法可以用来清除内存,其中可以采用在顶级base的Xmanager命令中设置Cleanup关键字的方法。

扩展:idl / idl教程 / idl程序设计

给XImageBar程序增加一个Cleanup过程,在程序代码底部找到下行:

Xmanager, ‘ximanager’, tlb, /No_Block, Group_Leader=group, $

Event_Handler = ‘XimageBar_Resize’

并作如下修改:

Xmanager, ‘xImageBar’, tlb, /No_Block, Group_leader = group, $

Event_Handler = ‘XimageBar_Resize’, Cleanup = ‘XimageBar_Cleanup’

当顶级base从屏幕上移走后,但在还没有完全销毁之前,将调用Cleanup过程。

事实上,在顶级base调用Cleanup过程时,唯一能做的就是将保存于顶级base的用户值中的info结构取出来。但这还不够,因为是info结构里的信息才需要清除。尤其是要将图像数据在堆栈中分配的内存释放掉。

Cleanup过程与IDL自动调用的事件处理程序相似,但它不是一个事件处理程序。Cleanup过程必须有唯一一个定位参数,一个用来标识与Cleanup过程相联系的组件的参数。换句话说,参数用来标识顶级base组件。Cleanup过程的前两行编写如下(将这个程序模块放在组件定义模块之前):

PRO XimageBar_Cleanup, tlb

Widget_Control, tlb, Get_Uvalue = info, /NO_Copy

无论顶级base何时被销毁,Cleanup过程都将被调用。在顶级base销毁时,info结构才可能从顶级base的用户值中释放出来。

当在开发自己的应用程序时,这将是一个特殊情况。程序的错误会导致应用程序在事件处理程序中崩溃。当用鼠标销毁组件时,IDL仍然会调用该程序模块。

如果info结构被释放,那么这时就不能再将它放回原处,内存泄露就几乎是不可避免的了。幸运的是,在这种极少数的情况下,可以使用Heap_GC来清除内存垃圾。Heap_gc找出存储在堆栈中的数据,并将那些无效引用的数据删除,XimageBar_Cleanup程序的剩下部分代码如下所示: 219

IDL IDL入门教程

IF N_Elements(info) EQ 0 THEN Heap_GC ELSE $

Ptr_Free, info.image

END

编译并运行程序,看它是否能运行。[www.loach.net.cn]

IDL>.Compile XImageBar

IDL>XimageBar, LoadData(11)

修改后的XimageBar代码能在与本书配套使用的文档中可以找到,文件名为xImageBar.6.pro。 使用伪事件进行程序通信

另外一个有用的组件编程技巧就是,知道什么时候以及怎样使用伪事件。伪事件是自己创建的事件,用于组件程序模块之间,尤其是事件处理程序模块之间的通讯。

从Xcolors命令的关键字NotifyID中,就能够简单地了解到这个技巧(请参阅274页的“创建并向其他组件发送事件”)。在下一章301页的“程序事件的指定组件”中,将会有这方面的更详细介绍。下面将会看到这种技巧是多么实用。

如果XimageBar程序除了能够在可改变大小的图形窗口中显示图像,还具有其它一些功能,那么它将会更加实用。如果想在程序中添加一个图像处理功能,需要创建一个新的菜单项,并在下面增加几个图像处理功能的菜单按钮。

在XimageBar的组件定义模块中找到下面一行:

drawID = Widget_Draw(tlb,Xsize=400,Ysize=400)

并在其前添加如下代码:

processID = Widget_Button(menubaseID,Value=’Processing’,$

Event_Pro = ‘XimageBar_Processing’/Menu)

SmoothID = Widget_Button(ProcessID,Value=’Smooth’)

EdgeID = Widget_Button(processID,Value = ‘Edge Enhance’)

EqualID = Widget_Button(processID,Value=’Histogram Equal’)

OriginalID = Widget_Button(processID, Value=’Original’)

注意,ProcessID所标识的组件已经指定了程序XimageBar_Processing作为其事件处理程序。由于其它按钮都是这个按钮的子按钮,因而这些按钮产生的事件将会“上浮”到XimageBar_Processing这个事件处理程序中。

事件处理程序的头两行很标准,并将这个事件处理程序添加在组件定义模块之前。

PRO XimageBar_Processing, event

Widget_Control, event.top, Get_Uvalue = info, /No_Copy

在刚才创建的5个按钮中,只有一个按钮不能返回事件,这就是与事件处理程序相关联的那个按钮。记住,菜单项按钮(带有关键字Menu的按钮或菜单栏)是不能产生事件的。但是,哪一个按钮能产生事件呢?

产生事件的按钮在事件处理程序里被标识event.id。通过获取按钮的值来找到Event.id所标识的是哪一个按钮是个不错的办法。按钮的值就是在按钮创建时通过关键字Value所指派的字符串。因而可以通过对按钮值进行分支,然后根据值的不同采取不同的操作。在这个例子中,所采取的操作就是对影像数据进行不同的图像处理。其代码如下:

Widget_Control, event.id, Get_Value = buttonValue

CASE buttonValue OF

‘Smooth’: thisImage = Smooth(*info.image, 7)

‘Edge Enhance’: thisImage = Sobel(*info.image)

220

IDL IDL入门教程

‘Histogram Equal’: thisImage = Hist_Equal(*info.image, $

Top = info.ncolors-1) + info.bottom

‘Original’ : thisImage = *info.image

ENDCASE

注意,在CASE语句中使用获取的按钮值的技巧只适用于按钮值是字串的情况。(www.loach.net.cn)在IDL中,按钮的值还可以是位图。如果是这样的话,按钮的用户值通常存储标识该按钮的字符串。

下一步,将绘图组件设置为当前图形窗口(这点特别重要),并通过调用ImageBar命令将处理后的图像显示在窗口中。事件处理程序的余下代码如下所示:

Wset, info.wid

ImageBar, thisImage, Ncolors = info.ncolors, $

Bottom = info.bottom, /EraseFirst, _extra = info.extra

Widget_Control, event.top, Set_Uvalue = info, /No=Copy

END

保存、编译并运行该程序。看看结果如何。

IDL>.Compile XImageBar

IDL>XImageBar, LoadData(9)

按钮正常运行,显示也漂亮极了。但是如果窗口中有一个处理后的图像,再改变窗口的大小结果会怎样呢?

创建一个具有“记忆功能”的程序

不管窗口里有什么内容,当窗口改变大小时,原始的图像数据都会在窗口中显示。这并不是程序想要的结果。当窗口尺寸发生变化时,不管窗口中的当前内容如何,都应该还在那里不变。但是怎样才知道在任一时间里窗口里有什么东西呢?没有洞察力,当然很难知道在任一时间里窗口中有什么了。它完全取决于不可预知的用户用该程序时所进行的操作。

扩展:idl / idl教程 / idl程序设计

不要绝望。无论显示窗口中有什么,它都是通过XimageBar_processing事件处理程序进入的。如果能使事件处理程序记住它最后一次操作,那么当改变窗口的尺寸时,就可以让事件处理程序再重复一次它的最后操作。

事实上,要知道最后一次操作,事件处理程序就必须记住最后一个它所处理的按钮事件对应的标识符。简单地说,就是event.id。但是,又将这个记忆存在哪里呢?当然在info结构里。 修改info结构,增加一个action字段。注意,最初的操作是将原始图像显示出来。因此action字段是的初始值就是original按钮的标识符。在组件定义模块中找出如下行:

info = {image: Ptr_New(image), $ ;The image data.

ncolors: ncolors, $ ;The number of colors.

bottom: bottom, $ ;The starting color index

extra: extra, $ ;The extra ImageBar keywords

wid: wid, $ ;The window index number.

drawID: drawID} ;The draw widget identifier.

修改为如下所示:

info = {image: Ptr_New(image), $ ; The image data.

ncolors: ncolors, $ ; The number of colors.

bottom: bottom, $ ; The starting color index

extra: extra, $ ;The extra ImageBar keywords

action:originalID, $ ;The last program action.

221

IDL IDL入门教程

wid: wid, $ ;The window index number.

drawID: drawID} ;The draw widget identifier.

接着修改事件处理程序XimageBar_Processing,当它处理一个事件时,更新action字段。(www.loach.net.cn]在事件处理模块中找出如下行:

Wset, info.wid

ImageBar, thisImage, Ncolors = info.ncolors, $

Bottom = info.bottom,, /EraseFirst, _Extra = info.extra

Widget_Control, event.top, Set_Uvalue = info, /No_Copy

END

修改为:

Wset, info.wid

ImageBar, thisImage, Ncolors = info.ncolors, $

Bottom = info.bottom,, /EraseFirst, _Extra = info.extra

Info.action = event.id

Widget_Control, event.top, Set_Uvalue = info, /No_Copy

END

现在好了,程序能够记住最后一次操作了,但是程序是怎样重复最后一次操作的呢?

答案是创建一个伪事件结构并将它传递到事件处理程序。在这个事件结构中,ID字段包含了存储在事件结构action字段中的组件标识符。

在XimageBar_Resize事件处理模块中找出如下所示两行代码:

Wset, info.wid

ImageBar, *info.bottom, Ncolors = info.ncolors, $

Bottom = info.bottom, /EraseFirst, _Extra=info.extra

删除这两行代码,并用下列代码代替它们:

psedoEvent = { WIDGET_BUTTON, ID: info.action, $

Top: event.top, Handler:0L, Select:1}

Widget_Control, info.action, Send_Event= psedoEvent

现在完整的XimageBar_Resize事件处理程序如下:

PRO XimageBar_Resize, event

Widget_Control, event.top, Get_Uvalue = info, /No_Copy

Widget_Control, info.drawID, Draw_Xsize = event.x, $

Draw_Ysize = event.y

PseudoEvent = {WIDGET_BUTTON, ID:info.action, $

Top: event.top, Handler:0L, Select:1}

Widget_Control, info.action, Send_Event = pseudoEvent

Widget_Control, event.top, Set_Uvalue = info, /No_Copy

END

其实,只有一两处将进行了改动。

当颜色变成24位颜色显示时,XimageBar_Colors事件处理程序也可以调用ImageBar程序来重新显示图像。在XimageBar_Colors事件处理模块中找出如下代码:

Device, Get_Visusl_Depth = thisDepth

IF thisDepth GT 8 THEN BEGIN

Wset, info.wid

ImageBar, thisImage, Ncolors = info.ncolors, $

222

IDL IDL入门教程

Bottom =info.bottom, /EraseFirst, _Extra = info.extra

ENDIF

并修改如下:

Device, Get_Visusl_Depth = thisDepth

IF thisDepth GT 8 THEN BEGIN

PseudoEvent = {WIDGET_BUTTON, ID:info.action, $

Top: event.top, Handler:0L, Select:1}

Widget_Control, info.action, Send_Event = pseudoEvent

ENDIF