chaussen

创意艺术编程(Creative Coding)中如何做像素化过滤。作者Hacker Noon

原文链接: hackernoon.com

创意艺术编程(Creative Coding)中如何做像素化过滤

欢迎来到这一期的创意艺术编程基础,想要看之前的教程请看这里

如往常一样,所有代码都可以在我的github主页上找到: https://github.com/GeorgeGally/creative_coding

把Stevie像素化,她太棒了。

像素化过滤并不难。做创意艺术编程,懂得取样技术和公式会很有用,尤其是做电脑的。我马上就会讲到这些。

像素化过滤从实质上讲,只不过是把屏幕分成许多块,然后对每一块的颜色进行取样而已。以块为单位取样,而不是以像素为单位,是为了系统性能考虑。取样块越小,作品运行得越慢。

那么让我们开始吧,先把屏幕分块,可以逐行逐列分。这个过程慢动作看起来是这样的:

这里的公式就是一个循环套另一个循环:

_// 定义取样大小_
var sample_size = 20;

_// 从页面最顶部开始,逐行分割,一直到最底部_ 
for (var y= 0; y < h; y+= sample_size) {

 _// 从左边开始,逐列分割,一直到右边t_ 
  for (var x = 0; x < w; x+= sample_size) {

     _// 某些操作_ 

  }

}

这样,就很容易实现一些不错的效果,如:

var ctx = createCanvas("canvas1");

// 定义取样大小
var sample_size = randomInt(20, 80);

function draw(){

  if(chance(200)) sample_size = randomInt(10, 40);

  // 从页面最顶部开始,逐行分割,一直到最底部
  for (var y= 0; y < h; y+= sample_size) {

    // 从左边开始,逐列分割,一直到右边
    for (var x = 0; x < w; x+= sample_size) {
      ctx.fillStyle = rgb(random(205));
      ctx.fillRect(x, y, sample_size, sample_size);
    }

 }

}

这里只有一个新内容,就是那个chance()函数。这是个小小的随机化辅助函数,很有用,我一直在用,它可以让我基于概率随机触发事件。代码看这起来是这样的:

function chance(value){
 return (random(value) > value-1);
}

不过这不是重点。

既然取样公式已经有了,就让我们来对一个图片进行取样吧。这里就需要一点点小理论。

对画布(canvas)进行取样的JavaScript函数叫:_getImageData(start_x, start_y, sample_width, sample_height)_(获取图像数据,开始点x坐标,开始点y坐标,取样宽度,取样高度),所以对整个屏幕取样可以这样做:

var sample = ctx.getImageData(0,0,w,h);

这个函数返回一个数据列对象,含有每个点的数据。其中每个点数据又都有一个由RGBA颜色值组成的数列,我们所要做的就是从每个点的数据中,把像素颜色值提取出来。

要取得屏幕上任意一点的位置,我们可以用这个神奇的公式:

var pos = (x + y * w);

不过要记住,函数返回的一个像素点数据中颜色值要占数列的四个位置,所以位置值只要每次乘以四就可以得出下一个像素在数列中的位置了:

var sample_point = (x + y * w) * 4;

然后,数列中接下来的四个值应该就是下一个像素的RGBA颜色值了。

// 要取得对象中所有点数据元素
var sample = ctx.getImageData(0,0,w,h).data;

 for (var y= 0; y < h; y+= sample_size) {

   for (var x = 0; x < w; x+= sample_size) {

     **var pos = (x + y * w) * 4;
     var red   = sample[pos];
     var green = sample[pos + 1];
     var blue  = sample[pos + 2];
     var alpha  = sample[pos + 3];**

    _ // 用这些值进行操作_ 

    }

}

实际上,颜色值中的那个alpha值没什么用,可以无视。所以把函数写得简洁些,把所有的东西写成一个函数,这样就能够轻松运用,不用再次重新思考,可以写成这样:

function pixelate(sample_size){

  var imgData=this.getImageData(0,0,w,h).data;

  for (var y= 0; y < h; y+= sample_size) {

   for (var x = 0; x < w; x+= sample_size) {

     var pos = (x + y * w) * 4;
     var red   = sample[pos];
     var green = sample[pos + 1];
     var blue  = sample[pos + 2];

     ctx.fillStyle = rgb(red, blue, green);
     ctx.fillRect(x, y, sample_size, sample_size);

  }

}

然后还要这样:

/_/ create canvas_
var ctx = createCanvas("canvas1");

_// define the sample size_
var sample_size = randomInt(20, 80);

_//load an image_
var img = new Image();
img.src = 'img/stevie.jpg';

function draw(){

  if(chance(200)) sample_size = randomInt(10, 40);
  ctx.drawImage(img, 0, 0, w, h);
  pixelate(sample_size);

}


function pixelate(sample_size){

  var imgData=this.getImageData(0,0,w,h).data;

  for (var y= 0; y < h; y+= sample_size) {

   for (var x = 0; x < w; x+= sample_size) {

     var pos = (x + y * w) * 4;
     var red   = sample[pos];
     var green = sample[pos + 1];
     var blue  = sample[pos + 2];

     ctx.fillStyle = rgb(red, blue, green);
     ctx.fillRect(x, y, sample_size, sample_size);

   }

  }

}

这个函数我想做几个调整。第一是把背景(context)对象传进去,这样如果需要的话,可以在屏幕上建立多个画布(cavas),同时只针对其中一个处理,所以只要加一个参数就行了。这一行:

var context = _ctx || ctx;

是一个简写,意思是如果作为参数的背景(_ctx)没有定义,那就用默认的背景变量(ctx)。这样,只有一个画布(canvas)时就不必须传入这个参数了。

function pixelate(sample_size, **_ctx**){

  **var context = _ctx || ctx;**
  var sample = context.getImageData(0,0,w,h).data;

  for (var y= 0; y < h; y+= sample_size) {

    for (var x = 0; x < w; x+= sample_size) {

     var pos = (x + y * w) * 4;
     var red   = sample[pos];
     var green = sample[pos + 1];
     var blue  = sample[pos + 2];
     **context**.fillStyle = rgb(red, blue, green);
     **context**.fillRect(x, y, sample_size, sample_size);
    }
  }

}

作为程序的加分项,我们还有一招可以用来提高速度。上面的函数已经够可以了,但如往常一样,同一件事JavaScript总是有不只一种解决方法。

我们可以用无正负号的32位整数数列,配合位级运算符,我们可以让性能再提升一点。所以最后的函数应该是这样的:

function pixelate(sample_size, _ctx){

  var context = _ctx || ctx;
  **var sourceBuffer32 = new Uint32Array(context.getImageData(0,0,w,h).data.buffer);**

  for(var y = 0; y < h; y += sample_size){

    for(var x = 0; x < w; x += sample_size){

      **var pos = (x + y * w);
      var b = (sourceBuffer32[pos] >> 16) & 0xff;
      var g = (sourceBuffer32[pos] >> 8) & 0xff;
      var r = (sourceBuffer32[pos] >> 0) & 0xff;**
      context.fillStyle = rgb(r,g,b);
      context.centreFillRect(x, y, sample_size, sample_size);

   }

 }

}

至此,就可以发出来去做像素化了。


如往常一样,所有代码都可以在我的github主页上找到:https://github.com/GeorgeGally/creative_coding

想要看之前的教程请看这里