Processing 在 2001 年诞生于麻省理工学院(MIT)的媒体实验室,主创者为 Ben Fry 和 Casey Reas,项目发起的初衷,本是为了满足他们自身的教学和学习需要。后来,当Casey在意大利的伊夫雷亚交互设计学院(Interaction Design Institute Ivrea)进行教学的时候,基于Processing,衍生出了Wiring和Arduino项目。随着时间的推移,又诞生了多个语言的版本,比如基于JavaScript的Processing.js,还有基于Python、Ruby、ActionScript以及Scala等版本。而当前的Processing,成立了相应的基金会,由基金会负责软件的开发和维护工作。
Processing项目是Java开发的,所以Processing天生就具有跨平台的特点,同时支持Linux、Windows以及Mac OSX三大平台,并且支持将图像导出成各种格式。对于动态应用程序,甚至可以将 Processing 应用程序作为 Java™ applet 导出以用在 Web 环境内。当然,为了降低设计师的学习门槛,用Processing进行图形设计的编程语言并不是Java,而是重新开发了一门类C的编程语言,这也让非计算机科班出身的设计师很容易上手。这里要多提一句,Processing支持OpenGL和WebGL,不但可以渲染2D图形,还可以渲染3D图形。
在过去的2015年,随着移动设备的普及,以及各大浏览器厂商对HTML5的支持日渐成熟,Processing也迎来了一次重大的升级。不但对开发工具做了优化和完善,也开始逐步支持Android应用的开发。Web方面,基于HTML5,重新开发了JavaScript版本的Processing,并且单独为其提供了Web开发工具,同时这也让Processing在网页上开发应用变的更加简单便捷。这里顺便提及一点的是,Processing可不只是能够渲染漂亮的图形,还支持与其他软件的通信,结合之前提到的Arduino项目,甚至可以和外部硬件进行交互!
Processing免费供设计爱好者们下载使用,官网:https://processing.org
1、Processing 环境:
第一步是安装 Processing 环境。去到 Processing.org(https://processing.org/download/),单击 Download Processing 并选择您的操作系统。此外,还需要确保 Java 技术已经可用。在windows上,下载解压后直接运行processing.exe即可。
这应该会弹出 Processing Development Environment(PDE 或 Processing IDE),如图 1 所示。占此窗口较大的部分是文本编辑器。如果输入图中所示的两行代码,然后单击 Run(左上角的三角形),出现一个窗口,显示您所输入的简单程序(或 Processing 术语所指的 sketch)的结果。单击 Stop(左上角的方框)退出程序,窗口消失。
2、Processing 语言
Processing 是用 Java 编程语言写的,并且 Java 语言也是在语言树中最接近 Processing 的。所以,如果您熟悉 C 或 Java 语言,Processing 将很容易学。并且在程序如何构造方面,也作了一些简化。Processing 并不包括 Java 语言的一些较为高级的特性,但这些特性中的很多特性均已集成到了 Processing, 所以您无需了解它们。
之所以选择 Java 语言是因为 Processing 应用程序被翻译成 Java 代码执行。选择 Java 范型简化了这种翻译并让开发和执行可视化程序变得十分简单和直观。
3、图形环境
在 Processing 内进行开发涉及到的是 PDE 和显示窗口。2-D 图形的坐标系如图 2 所示。size 关键字以像素为单位定义了显示窗口的大小并且通常都是 Processing 应用程序内的首要步骤。
如图 2 所示,size 关键字指定显示窗口的 X 和 Y 坐标。line 关键字则会在两个像素点之间绘制一条线(以 x1、y1 to x2、y2 的格式)。请注意,超出屏幕边界(size 定义的边界外)画线并非不允许,只是被忽略了而已。
本文无意对此做深入探讨,但 size 接受可选的第三个参数 mode。 mode 用来定义要使用的呈现引擎并支持 PDF(直接呈现为 Adobe® PDF 文档)、OPENGL (利用一个可用的 Open-GL 图形适配器)、P3D(为了迅速的 3-D 呈现)等。默认的是 JAVA2D,它最适合于高质量的 2-D 成像。
现在,我们来看一些基本的图形原语,然后再深入探讨几个示例应用程序。
4、图形原语
Processing 包含了大量各种各样的几何形状以及这些形状的控件。本节会简介一些基本的图形原语。
1)背景和颜色
background 功能被用来设置显示窗口的颜色。此函数可以使用各种不同的参数(来定义一个灰度值或 Red-Green-Blue [RGB] 颜色)。
size(100, 100);
background( 0, 128, 0 );
2)绘制像素点
可以使用 set 函数绘制单个像素点。此函数接受显示窗口内的 x,y 坐标以及作为颜色的第三个参数。Processing 也有一个类型,称为 color,通过它,可以定义用于某个操作的颜色。在本例中,我们创建了一个颜色实例并用它来设置显示窗口内的某个像素点。
size(100, 100);
for (int x = 0 ; x < 100 ; x++) {
for (int y = 0 ; y < 100 ; y++) {
color c = color( x*2, y*2, 128 );
set(x, y, c);
}
}
可以使用 get 操作来读取显示中的一个给定像素点的颜色。虽然 set 很简单,但它不是操做显示的最快方式。要想快速访问,可以使用 pixels 数组(与 loadPixels 和 updatePixels 函数一致)。
3)绘制形状
在 Processing 内使用单个函数绘制形状十分简单。要设置在绘制形状时使用何种颜色,可以利用 stroke 函数。此函数可接受一个单独的灰度参数或三个 RGB 参数。此外,还可以用 fill 命令定义这个形状的填充色。
下面代码显示了如何绘制线、矩形、圆(使用椭圆)及椭圆。line 函数接受四个参数,代表的是要在其间绘制线条的点。rect 函数可绘制一个矩形,并且前两个点定义位置,而后面两个点则分别定义宽度和高度。ellipse 函数也接受四个参数,分别定义位置和宽/高度。当宽和高相等时,就是一个圆形。还可以使用 ellipseMode 函数定制椭圆,它指定 x,y 位置是否代表了椭圆的角(CORNER)或中心(CENTER)。
size(100, 100);
stroke(0, 128, 0);
line(10, 10, 90, 90);
fill(20, 50, 150);
rect(30, 30, 60, 40);
fill(190, 0, 30);
ellipse(30, 70, 20, 20);
fill(0, 150, 90);
ellipse(70, 30, 30, 20);
4)绘制四边形
在 Processing 内使用 quad 可以很容易地绘制有四个边的多边形。四边形接受八个参数,代表的是这个四边形的四个顶点。清单 4 内的示例创建了 10 个随机的四边形(其中这些点必须是顺时针或逆时针顺序。此代码还会为每个四边形创建一个随机的灰度。
size(100, 100);
for (int i = 0 ; i < 10 ; i++) {
int x1 = (int)random(50);
int y1 = (int)random(50);
int x2 = (int)random(50) + 50;
int y2 = (int)random(50);
int x3 = (int)random(50) + 50;
int y3 = (int)random(50) + 50;
int x4 = (int)random(50);
int y4 = (int)random(50) + 50;
fill( color((int)random(255) ) );
quad( x1, y1, x2, y2, x3, y3, x4, y4 );
}
上面几个例子的结果
5、Processing 应用程序的结构
至此,通过几个简单的脚本,您已经对 Processing 语言有了大致的了解,但这些脚本是一些非结构化的代码,只提供了应用程序的一些简单元素。Processing 应用程序是有一定结构的,这一点在开发能够持续运行且随时更改显示窗口的图形应用程序(比如动画)时非常重要。在这种情况下,就凸显了 setup 和 draw 这两个函数的重要性。
setup 函数用于初始化,由 Processing 运行时执行一次。通常,setup 函数包含 size 函数(用于定义窗口的边界)以及在操作期间要使用的变量的初始化。Processing 运行时会不断执行 draw 函数。每次 draw 函数结束后,就会在显示窗口绘制一个新的画面,并且 draw 函数也会被再次调用。默认的绘制速度是每秒 60 个画面,但是您也可以通过调用 frameRate 函数来更改这个速度。
此外,还可以使用 noLoop 和 draw 来控制在何时绘制画面。noLoop 函数会导致绘制停止,而使用 loop 函数则可以重新开始绘制。通过调用 redraw 可以控制 draw 在何时调用。
下面我们做一个简单的模拟仿真,示例1:
实现森林火灾模型的 2-D 元胞自动机实现。这个模型来自 Chopard 和 Dro 的 “物理系统的元胞自动机建模”,它提供了一个简单系统,展示了树的生长以及由雷击导致的大火的蔓延。这个模拟包含了一组简单规则,定义如下:
这些规则的代码可以在 update
函数(下面代码)内找到,它迭代 2-D 空间以决定根据已定义的规则,状态如何转换。请注意这个 2-D 空间实际上是 3-D 的,因为保存了此空间的两个副本 — 一个针对的是当前迭代,一个针对的是上一个迭代。这么做是为了避免更改对空间的破坏。此空间然后会成为一个显示空间(被显示的东西) 和一个计算空间(规则的应用)。这些空间按每次生成对调。
从很大程度上讲,这个应用程序使用了极少的 Processing 图形关键字。为空间定义的颜色只有几个:stroke
用来更改颜色,point
用于绘制像素点。使用 Processing 模型,draw
函数调用 update
以应用规则;返回后,draw
将这个更新了的空间发到显示窗口。
int[][][] pix = new int[2][400][400];
int toDraw = 0;
int tree = 0;
int burningTree = 1;
int emptySite = 2;
int x_limit = 400;
int y_limit = 400;
color brown = color(80, 50, 10); // brown
color red = color(255, 0, 0); // red;
color green = color(0, 255, 0); // green
float pGrowth = 0.01;
float pBurn = 0.00006;
boolean prob( float p )
{
if (random(0, 1) < p) return true;
else return false;
}
void setup()
{
size(400, 400);
frameRate(60);
/* Initialize to all empty sites */
for (int x = 0 ; x < x_limit ; x++) {
for (int y = 0 ; y < y_limit ; y++) {
pix[toDraw][x][y] = emptySite;
}
}
}
void draw()
{
update();
for (int x = 0 ; x < x_limit ; x++) {
for (int y = 0 ; y < y_limit ; y++) {
if (pix[toDraw][x][y] == tree) {
stroke( green );
} else if (pix[toDraw][x][y] == burningTree) {
stroke( red );
} else stroke( brown );
point( x, y );
}
}
toDraw = (toDraw == 0) ? 1 : 0;
}
void update()
{
int x, y, dx, dy, cell, chg, burningTreeCount;
int toCompute = (toDraw == 0) ? 1 : 0;
for (x = 1 ; x < x_limit-1 ; x++) {
for (y = 1 ; y < y_limit-1 ; y++) {
cell = pix[toDraw][x][y];
// Survey area for burning trees
burningTreeCount = 0;
for (dx = -1 ; dx < 2 ; dx++) {
for (dy = -1 ; dy < 2 ; dy++) {
if ((dx == 0) && (dy == 0)) continue;
else if (pix[toDraw][x+dx][y+dy] == burningTree) burningTreeCount++;
}
}
// Determine next state
if (cell == burningTree) chg = emptySite;
else if ((cell == emptySite) && (prob(pGrowth))) chg = tree;
else if ((cell == tree) && (prob(pBurn))) chg = burningTree;
else if ((cell == tree) && (burningTreeCount > 0)) chg = burningTree;
else chg = cell;
pix[toCompute][x][y] = chg;
}
}
}
运行结果
示例2:
易染/感染/免疫模型模拟的是疾病在医院内的蔓延。与森林火灾模型类似,SIR 也是通过一套简单规则实现的,只不过添加了一些复杂性和有趣的行为。 在这个模型内,有一个由病人占据的病床组成的网格。在 time 0,所有病人都是某一种新疾病的易染人群,这意味着这些病人从未患过这种疾病,因此才有可能被感染。如果在某个病人的东/南/西/北的四个邻居中有一个患了这种疾病,那么该病人受感染的可能性为 tau。一个受感染的病人的患病时间为 K 天,在此期间病人有感染其他病人的可能性。在 K 天后,该病人康复并有了对这种疾病的免疫力。
正如之前的例子所示,setup 函数先初始化这个医院以及所有易染病人,只有最中心的这个病人是已经患病的。在该实现内,0 是易染病人,1-K 是感染病人,-1 是免疫病人。draw 函数将这种几何分布发到显示窗口,update 实施这些 SIR 规则。与之前一样,可以用一个 3D 数组保存当前的这些几何分布。如下代码。
int[][][] beds = new int[2][200][200];
int toDraw = 0;
int x_limit = 200;
int y_limit = 200;
color brown = color(80, 50, 10); // brown
color red = color(255, 0, 0); // red;
color green = color(0, 255, 0); // green
int susceptible = 0;
int recovered = -1;
float tau = 0.2;
int k = 4;
boolean prob( float p )
{
if (random(0, 1) < p) return true;
else return false;
}
void setup()
{
size(200, 200);
frameRate(50);
for (int x = 0 ; x < x_limit ; x++) {
for (int y = 0 ; y < y_limit ; y++) {
beds[toDraw][x][y] = susceptible;
}
}
beds[toDraw][100][100] = 1;
}
void draw()
{
update();
for (int x = 0 ; x < x_limit ; x++) {
for (int y = 0 ; y < y_limit ; y++) {
if (beds[toDraw][x][y] == recovered) stroke( brown );
else if (beds[toDraw][x][y] == susceptible) stroke( green );
else if (beds[toDraw][x][y] < k) stroke( red );
point( x, y );
}
}
toDraw = (toDraw == 0) ? 1 : 0;
}
boolean sick( int patient )
{
if ((patient > 0) && (patient < k)) return true;
return false;
}
void update()
{
int x, y, cell;
int toCompute = (toDraw == 0) ? 1 : 0;
for (x = 1 ; x < x_limit-1 ; x++) {
for (y = 1 ; y < y_limit-1 ; y++) {
cell = beds[toDraw][x][y];
if (cell == k) cell = recovered;
else if (sick(cell)) cell++;
else if (cell == susceptible) {
if (sick(beds[toDraw][x][y-1]) || sick(beds[toDraw][x][y+1]) ||
sick(beds[toDraw][x-1][y]) || sick(beds[toDraw][x+1][y])) {
if (prob(tau)) cell = 1;
}
}
beds[toCompute][x][y] = cell;
}
}
}
Processing 内 SIR 模型的输出如图 7 所示。请注意这里这些绿色的像素点代表的是易染病人,红色的代表的是感染病人,灰色代表的是免疫病人。由于病症会持续 4 天,且相邻病人患病的可能性为 20 %,因此这个病症会随机地在整个医院传播,感染很多病人,但也有数群未被感染的病人。
5、使用文本
Processing 不仅支持显示窗口内的文本,还支持控制台形式的用于调试的文本。要在显示窗口内使用文本,需要一种字体。所以,第一步是创建一种字体(使用 PDE 的 Tools 选项)。选择了要创建的字体后,字体文件(VLW)就会显示在项目的 ./data 子目录内。之后,就可以使用 loadFont 函数加载这个文件,然后再使用 textFont 将它定义为默认。这两个步骤在 图 5 的 setup 函数内有所显示。还请注意我们已经将画面速度减慢为每秒 1 个画面 (因为这也是更新自然发生的频率)。
draw 函数展示了您在之前没有见过的其他一些函数。首先是时间函数,它返回的是时钟的小时、分和秒。请注意有一些传统的函数可以返回年、月和日。存储了时间数据后,就可以使用 nf 函数创建一个字符串,它可以将数字转变为字符串。为了将向时钟添加一些花样,可以使用 background 和 fill 函数处理背景和时钟的颜色。背景的颜色范围是从 255(白)到 137(淡灰)。fill 函数可用于给文本上色,范围是从 100 (淡灰)到 218(接近于黑色)。颜色设好后,text 函数就会向将时间字符串发送到显示窗口已定义的坐标位置。也可以使用 println 函数将字符串发到控制台,如下。