前不久CreativeApplicationsNetwork上介绍了一个Processing案例:Pixel Knitting。这个程序很有意思,它先读取一张图片,然后对每个像素进行变换,重新绘制图片,整个过程就像织毛衣,最后生成的新图片很有梵高油画的效果。



下面这个是原图:

转换完成的新图:

作者emoc分享了Processing源码。让我们来看看是怎么做的。

读懂一个程序最重要的是要读懂它的流程。这个流程定义在draw()函数里面。

void draw() {
  if (first) {
    image(img, 0, 0);
    first = false;
  }
  img.loadPixels();
  im += 800;
  if (im > imax) im = imax;
   
  for (i = int(imin); i < im; i++) {
    x = i%img.width;
    y = floor(i/img.width);
    if (MODIFY_ANGLE && (lasty != y)) amod += amod_inc;
     
    color cc = img.pixels[i];
    stroke(cc);
    fill(cc);
    float bri = brightness(img.pixels[i]);
    float sat = saturation(img.pixels[i]);
    if (bri > 245) draw_lines(bri, x, y, amod - x/3, smod);
    else if (bri < 10) draw_lines_dark(bri, x, y, amod - x/3, smod);
    else {
      strokeWeight(sat / 15);
      float a = (360.0 / 255.0) * sat + amod;
      float x2 = x + bri/4 * random(0.7,1) * smod * cos(radians(a));
      float y2 = y + bri/4 * random(0.7,1) * smod * sin(radians(a));
      line(x, y, x2, y2);
    }
    if ((sat > 200) && (bri > 150)) { // 
      draw_circle(sat, x, y, amod, smod);
    }
    lasty = y;
  }
  imin = im;
 
  if (im == imax) {
    // saveFrame("image_" + hour() + minute() + second() + ".png"); // UNCOMMENT !
    noLoop();
  }
}

这个draw()乍一看很复杂。其实要了解流程,我们可以先剥离那些图片处理的部分,剩下就是下面这样的框架了。要记住,在Processing里,draw()函数不停被调用,好比是一个循环。

 if (first) {
    image(img, 0, 0);
    first = false;
  }
  img.loadPixels();
  im += 800;
  if (im > imax) im = imax;
   
  for (i = int(imin); i < im; i++) {
    //略去图像处理部分  
  }
  imin = im;
 
  if (im == imax) {
    noLoop();
  }

第一段if 是要在最开始显示原图,跑过一次之后,因为first变成了false,就不会再执行里面的语句了。

if (first) {
    image(img, 0, 0);
    first = false;
}

接着,读取当前屏幕的所有的像素到img变量。

img.loadPixels();

下面用到了im变量。im是个浮点数。之前没有设置,所以是0。执行了下面这句之后就是800。每次draw()函数被运行,im就会增加800。

im += 800;

下面用到了imax变量。

if (im > imax) im = imax;

我们往回看,imax最早是在setup()里面初始化的。

imax = img.width * img.height;

所以imax就是总的像素数量。这样上面的if语句的意思就是当im增长到超过所有像素数量的时候,im就是像素的数量,不变了。

接下去是一个for循环。这里面,i从imin增长到im-1。imin在setup()里初始化为0。所以第一次运行draw()的时候,i就从0变到799。

for (i = int(imin); i < im; i++) {
  //略去图像处理部分  
}

紧接着,imin的值变成im,也就是800。那样在下一次跑draw函数的时候,i就从800开始到1599,因为im变成了800+800=1600。

imin = im;

最后当im等于imax的时候,就执行noLoop(),告诉系统不再执行draw()函数来更新图片。

if (im == imax) {
  noLoop();
}

现在应该比较清晰了,这个程序的流程就是从图片的第一个像素开始。每次跑draw的时候,处理从imin到im之间的800个像素,直到所有的像素都处理完为止。这里有一个情况就是当图片的总像素数不是800的倍数时。如果一共有900个像素,第一次是0到799,第二次的时候imin=800, im=1600。这时候,程序就可能去读取不存在的像素。还记得之前有这么一句吗?

if (im > imax) im = imax;

这句就起到了防止这种情况的作用。im会重设成imax的值。所以i会在从800变到900。

© 2011, 视物 | 致知. All rights reserved.

Related Posts:

  1. 试运行源码
    对风景的处理比较好
    人物就算了,整个面目全非