Oh!Coder

Coding Life

有趣的Processing-像素

| Comments

上一次,我们一起学习了Processing中关于图片的基本操作。这一次,我们来一起了解一下Processing中的像素。

其实,不管是图片,还是显示图片的背景窗口,最终显示出来的,本质上都是一个一个的像素。背景窗口中有背景色,图片有图片本身的各种颜色,它们都是由一个一个的像素组成,区别是显示的位置不同而已,一个是窗口的故有颜色,另一个是不同图片的固有颜色。

好,既然它们有此细微区别,为了便于理解,就先让我们以此划分,逐一介绍不同显示位置的像素。

窗口像素

在之前的学习中,我们了解了如何设置窗口的背景色,如何在窗口中直接画图,比如直线,椭圆,矩形,以及如何设置这些基本图形的填充色,边框颜色。这些基本图形,本质上都是由一系列像素所组成。在设置填充色和边框颜色的时候,区别在于修改的像素位置不同而已。比如边框颜色,默认以图形边缘一个像素的宽度为起点。那么填充色,就是除去图形边缘一个像素宽度的边框以外,内部的像素颜色。

现在,我们对于窗口中的颜色与像素的关系,有了一个基本的了解。下面我们还是回到Processing,通过例子来学习,看看Processing中,窗口自身的像素是如何操作的。

首先,还是让我们打开PDE,新建一个项目窗口。

processing-new-window

图片来源:Processing新项目窗口

然后向其中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup(){
  // 设置窗口大小
  size(200, 200);
  // 加载像素值。
  // 此操作可以是为pixels数组赋值,
  // 即将窗口中的颜色值存入pixels数组中。
  loadPixels();
  // 循环遍历pixels数组
  for (int i = 0; i < pixels.length; i++) {
    // 生成一个范围在0~255之间的随机数
    float rand = random(255);
    // 根据生成的一个随机数,
    // 定义一个颜色值。
    color c = color(rand);
    // 将颜色值赋值给pixels数组
    pixels[i] = c;
  }
  // 更新像素值。
  // 此操作可看做是将pixels数组中的像素值,
  // 重新赋值给窗口。
  updatePixels();
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码。开森不?
}

添加完成之后,可编译并运行上述代码。

processing-window-menu

图片来源:Processing新项目窗口中的菜单栏

点击项目窗口中,菜单栏里的三角形按钮,如上图所示。

若一切顺利,运行结果大致如下图所示。

processing-fun-pixels-1

图片来源:Processing程序执行结果

上述示例中,有几处需要了解。其中,loadPixels()方法是Processing类库提供的,可以将窗口中的像素赋值给数组变量pixels,这里的数组变量pixels也是Processing提供的,可以看做是一个全局变量数组,类似于获取窗口宽高的widthheight。每当调用完一次loadPixels()方法,便可将窗口中的像素值向pixels做一次赋值。用一个for循环,将pixels数组中的数值进行修改,最后调用updatePixels()方法,可以看做是将pixels数组中的值赋值给窗口,改变窗口当中之前的像素值。updatePixels()方法也是Processing类库提供的,在对像素进行操作时,通常与loadPixels()方法配对使用。

简单来说,获取和设置窗口像素有三步,第一步,调用loadPixels()方法,将窗口中的像素值存入pixels数组。第二步,根据需要,通过for循环,修改pixels数组数值。第三步,调用updatePixels()方法,将修改后的pixels数组数值赋值给窗口,从而改变窗口当前的像素值。

有一点这里要说明一下,不管是对于窗口,还是下面要说的图片,获取其中某一个点,我们通常习惯于用二维坐标 (x, y) 来定位。而Processing提供的数组变量pixels是一维数组,它是将像素值按照线性次序,依次进行存储,如下图所示。

processing-fun-pixels-pixelarray

图片来源:Processing官网

所以我们将不能直接使用 (x, y) 这种形式来对像素进行操作。如果亦然想通过 (x, y) 这种形式进行操作,这里提供一种方法,可以自己做一下变换。先获取窗口或图片的宽度,然后通过 (x, y) 两个变量计算出pixels数组中对应的像素值,方法如下图所示。

processing-fun-pixels-pixelarray2d

图片来源:Processing官网

理解了窗口中的像素,作为类比,下面我们学习图片像素就会简单许多。

图片像素

类比于窗口像素,让我们看看图片像素是怎样的?我们还是以示例来进行说明。

首先,还是新建一个项目窗口。将如下代码填入窗口中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 声明图片变量
PImage img;
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup(){
  // 设置窗口大小。
  // 此处窗口大小刚好和图片大小一样。
  size(500, 375);
  // 加载图片 "spring.png"
  img = loadImage("spring.png");
  // 调用img对象的loadPixels()方法,
  // 注意:这里是用img对象进行方法调用,
  // 可以看做是将img对象中图片的像素存入
  // img对象中的pixels数组变量。
  img.loadPixels();
  // 遍历img对象中的pixels数组
  for (int i = 0; i < img.pixels.length; i++){
    // 若img对象中pixels数组像素的编号刚好被3整除,
    // 则将此像素的颜色改为白色。
    if (i % 3 == 0){
      // 将此像素改为白色
      img.pixels[i] = color(255);
    }
  }
  // 将img对象中的pixels数组里的像素值,
  // 赋值给图片本身,即更新修改后的图片像素值。
  img.updatePixels();
  // 显示img图片
  image(img, 0, 0);
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  //此处无代码
}

编译并运行上述代码,若一切顺利,执行结果类似如下图所示。

processing-fun-pixels-2

图片来源:Processing新项目窗口

看!效果是不是有点像是滤镜!现在让我们看看,Processing是如何对像素进行操作的。其实,正如窗口像素示例,这里也涉及到了三个步骤,分别是,调用img.loadPixels(),设置img.pixels,调用img.updatePixels(),是不是很像窗口像素所演示的那样?这里也分为三个步骤,只不过对于修改特定的图片像素,要通过对应的图片对象来设置才行。比如这里我们要改变名为spring.png的图片,此图片通过图片对象img所加载,所以就要通过对象img来调用loadPixels()方法和updatePixels()方法。同样的,修改pixels数组,也要通过img来进行修改。只有按照这种方式调用,才能准确更改对应的图片。若是直接调用loadPixels()方法和updatePixels()方法,受影响的则会是窗口像素。

总结

不管是窗口像素,还是图片像素。对像素进行操作,主要有三个基本步骤,分别是调用loadPixels(),将像素存入pixels数组,然后根据需求,修改pixels数组,最后调用updatePixels()方法,将修改后的pixels数组覆盖要修改的像素值,让修改生效。修改窗口像素和图片像素的区别,在于直接调用这些方法还是通过图片对象进行效用。直接调用则修改窗口像素,通过图片对象调用则修改的是图片自身。

下期预告

通过最近两次的学习,我们对图片和像素都有了初步的了解。之前我们知道如何画规则的基本图形,但更优美的曲线我们还没有学过。下一次,我们将学习如何画优美的曲线!

Comments