Published Date : 2019年12月23日21:15

p5.jsとnumjs - Part 2
p5.js and numjs - Part 2


This blog has an English translation



p5.jsとnumjsを使って何かをやろうという記事 - Part 2です。

This blog post is about doing something with p5.js and numjs - Part 2.


何故P5なのか?Javascriptオンリーでやればいいのではないか? 単純にP5は可視化が簡単なので、P5を採用しています。

Why p5.js? Why don't you just use Javascript? You might think so. The reason is simple. Since p5.js is easy to visualize, I'm using p5.js this time.

もちろん、P5はJavascriptで作られているので、P5内でJavascriptそのものが使えます。 なので、基本P5で書いていきますが、P5では難しい文字列処理等はJavascriptでそのまま書いていきます。

Of course, p5.js is written in Javascript, so you can use it directly in p5.js. I usually write code in p5.js, but there many things that are difficult to process in p5.js (String operations, etc.), so if I encounter such a situation, I write code directly in Javascript.


目次

Table of Contents



概要
Summary


簡単な概要です。

Here's a quick overview.

まずこの記事の目的は、NumjsというNumpy(Python)のJavascriptヴァージョンを使用して、何か面白いことができるのでは無いかと模索してみることです。

First of all, the purpose of this blog post is to explore the possibility of doing something interesting with numjs. (Javascript version of the Python's module Numpy)

前回の記事ではNumjsの簡単な使い方を説明しました。 今回は、NumjsとP5を使って虹色画面を作っていきます。

In the previous my blog post, I explained how to use numjs easily. This time, I'm going to create a rainbow color screen using numjs and p5.

殆どのNumjsの説明は、こちらの本家のページに詳しく書いてあります。

Most numjs description are detailed on the original page here.




虹色画面を作る
Create a rainbow screen



NumjsはNumpy同様、行列を一度に計算するのが得意です。

Like numpy, numjs is good at calculating matrices all at once.

まず以下の図を見てください。 P5が描くキャンバスサイズが600X600だとすると、 一番左上が原点で(0、0)になり、 一番右下が(600、600)になります。

First, look at the following diagram. If p5 draws a canvas size of 600x600, The top-left corner is the origin and (0,0). The bottom right is (600, 600).


Responsive image

各々の座標(x、y)は1ピクセルを表していて、 それが横に600個、縦に600個あります。 つまりXとYの座標情報が「600×600」分あると考えることができます。

Each coordinate (x,y) represents 1 pixel. There are 600 horizontally and 600 vertically. In other words, It can be considered that one pair representing x and y coordinates is stored in an array of 600 rows and 600 columns.


Responsive image


試しにP5でこのピクセルの配列を作って、その一つ一つにある数字を足してみましょう。

Let's try out this array of pixels on p5 and add numbers in each one.



arrOfPixels.js
let canvasWidth = 600;
let canvasHeight = 600;
let arrayOfPixels = [];

function setup(){
    for (let i = 0; i < canvasHeight + 1; i++){
        let row = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            row.push([j, i]);
        }
        arrayOfPixels.push(row);
    }

    print(arrayOfPixels);
}
[0 … 99]
  0: Array(601)
    [0 … 99]
      0: (2) [0, 0]
      1: (2) [1, 0]
      2: (2) [2, 0]
      3: (2) [3, 0]
...............................
  600: Array(601)
    [0 … 99]
      0: Array(2)
      0: 0
      1: 600
      length: 2
      __proto__: Array(0)
      1: (2) [1, 600]
      2: (2) [2, 600]

配列に3を足していきます。

Add 3 to the array.



arrOfPixels.js
let canvasWidth = 600;
let canvasHeight = 600;
let arrayOfPixels = [];

function setup(){
    for (let i = 0; i < canvasHeight + 1; i++){
        let row = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            row.push([j, i]);
        }
        arrayOfPixels.push(row);
    }

    print(arrayOfPixels);

    let arrayOfPixelsPlus3 = [];

    for (let i = 0; i < canvasHeight + 1; i++){
        let row = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            row.push([j + 3, i + 3]);
        }
        arrayOfPixelsPlus3.push(row);
    }

    print(arrayOfPixelsPlus3);

}
[0 … 99]
  0: Array(601)
    [0 … 99]
      0: (2) [3, 3]
      1: (2) [4, 3]
      2: (2) [5, 3]
      3: (2) [6, 3]
...............................
  600: Array(601)
    [0 … 99]
      0: (2) [3, 603]
      1: (2) [4, 603]
      2: (2) [5, 603]
      3: (2) [6, 603]

ではこの計算をnumjsでやる前に、PythonのNumpyでどれだけ簡単にできるか試してみましょう。

Before using numjs, let's see how easy it is to compute these matrices using numpy in python.

python numpy
In [1]: import numpy as np

In [2]: zers = np.zeros(601)
In [3]: arr = np.arange(601, dtype='float32')
In [4]: array_of_pixels = [list(map(list, zip(np.add(zers, i), arr))) for i in range(601)]

In [5]: array_of_pixels[3][3]
Out[5]: [3.0, 3.0]

In [6]: x = 100
In [7]: y = 100

In [8]: array_of_pixels[x][y]
Out[8]: [100.0, 100.0]

In [9]: np.add(array_of_pixels, 3)
Out[9]: 
array([[[  3.,   3.],
        [  3.,   4.],
        [  3.,   5.],
        ...,
        [  3., 601.],
        [  3., 602.],
        [  3., 603.]],

       ...,

       [[603.,   3.],
        [603.,   4.],
        [603.,   5.],
        ...,
        [603., 601.],
        [603., 602.],
        [603., 603.]]])

ではnumjsでやってみましょう。

Let's try with numjs.

numjsTutorial.js
let canvasWidth = 600;
let canvasHeight = 600;
let arrayOfPixels = [];

function setup(){
    for (let i = 0; i < canvasHeight + 1; i++){
        let row = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            row.push([j, i]);
        }
        arrayOfPixels.push(row);
    }

    let njarry = nj.array(arrayOfPixels);
    print(njarry.shape)

    print(njarry.add(3).tolist())
}
(3) [601, 601, 2]

--------------------------------

  [0 … 99]
    0: Array(601)
      [0 … 99]
        0: (2) [3, 3]
        1: (2) [4, 3]
        2: (2) [5, 3]
        3: (2) [6, 3]

--------------------------------

少ないコードで数字の3を全ての要素に足すことができました。

I was able to add the number 3 to every element with less code.

さらにこの「arrayOfPixels」に色の情報を入れてみましょう。

Let's add color information to this "arrayOfPixels".

operationOfEachPixel.js
function setup(){
->  createCanvas(canvasWidth, canvasHeight);
->  background(255);

    for (let i = 0; i < canvasHeight + 1; i++){
        let row = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            // add black color
->          row.push([0, 0, 0]);
        }
        arrayOfPixels.push(row);
    }

    print(arrayOfPixels[canvasWidth/2][canvasHeight/2]);
    // [0, 0, 0]
  
    for (let i = 0; i < canvasHeight + 1; i++){
        for (let j = 0; j < canvasWidth + 1; j++){
            let r = arrayOfPixels[j][i][0];
            let g = arrayOfPixels[j][i][1];
            let b = arrayOfPixels[j][i][2];
            if (i > height/2 && j < width){
                stroke(r,g,b);
            } else {
                stroke(255,0,0);
            }
            point(j,i);
        }
    } 
}

Responsive image

これをするのに、わざわざこんな長いコードにする必要はありません。 2つrect関数を書くだけです。 では2つの色の領域を少しずつフェードアウトさせるとどうなるでしょうか?

You don't have to do this in such a long piece of code. Just write two rect functions. So What happens when you fade out the two color regions in small increments?

js
let canvasWidth = 600;
let canvasHeight = 600;
let arrayOfPixels = [];

let fps = 30;

function setup(){
    createCanvas(canvasWidth, canvasHeight);
    background(255);

    frameRate(fps);

    for (let i = 0; i < canvasHeight + 1; i++){
        let row = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            row.push([0, 0, 0]);
        }
        arrayOfPixels.push(row);
    }
}

let alpha1 = 255;
let adjustmentValue1 = 0.03;

let alpha2 = 0;
let adjustmentValue2 = 0.051;

function draw(){
    background(255);
    if (alpha1 < 0 || alpha1 > 255){
        adjustmentValue1 = adjustmentValue1 * -1;
    }
    if (alpha2 < 0 || alpha2 > 255){
        adjustmentValue2 = adjustmentValue2 * -1;
    }
    print(alpha1);
    print(alpha2);
    print(frameCount)
    for (let i = 0; i < canvasHeight + 1; i++){
        for (let j = 0; j < canvasWidth + 1; j++){
            let r = arrayOfPixels[j][i][0];
            let g = arrayOfPixels[j][i][1];
            let b = arrayOfPixels[j][i][2];
            if (i > height/2 && j < width){
                stroke(r,g,b,alpha2);
            } else {
                stroke(255,0,0,alpha1);
            }
            point(j,i);
        }
    }
    alpha1 -= frameCount * adjustmentValue1;
    alpha2 -= frameCount * adjustmentValue2;
}


ではある程度の数のピクセルをNumjsとZip関数を使って一気に作り上げてみましょう。 さらにRandom関数を使って色とピクセルの位置をずらして虹色の画面を作ります。

Let's use numjs and zip function to create some pixels at once. It also uses the Random function to shift the colors and pixels to create a rainbow screen.

js
let offset;
let zers;
let arr;

let printPixels = [];

const zip = (array1, array2) => array1.map((_, i) => [array1[i], array2[i]]);

function setup(){
    createCanvas(600, 600);
    colorMode(HSB, 600, 255, 255, 255)
    background(255)
    
    zers = nj.zeros(width+1)
    arr = nj.arange(width+1, dtype='float32')

    offset = 3

    for (let i = 0; i < 601; i += offset) {
        zip(zers.add(i).tolist(), arr.tolist()).forEach(([x,y]) => printPixels.push(new PrintPixel(x,y,i)))
    }

    for (let i = 0; i < printPixels.length; i++) {
        printPixels[i].display();
    }

    saveCanvas('myCanvas', 'png');
}


class PrintPixel{
    constructor(x, y, c){
        this.x = x;
        this.y = y;
        this.c = c;
    }
    display(){
        push();
        noStroke()
        fill(this.c, 255, 255, random(100,200))
        ellipse(this.x * 1.044, this.y * 1.065, this.c**0.1, this.c**0.1)
        fill(600 - this.c, 255, 255, random(100,200))
        ellipse(this.y * 1.065, this.x * 1.044, this.c**0.1, this.c**0.1)
        pop();
    }
}

Responsive image

XやYなどのパラメータを変化させると描画が変化します。

Changing parameters such as X and Y changes the drawing.


Responsive image

色々と試してみましょう。

Let's try various things.

js
let canvasWidth = 600;
let canvasHeight = 600;
let arrayOfPixelsColor = [];
let arrayOfPixelsCoordinate = [];
let numjsArrayOfPixelsColor;
let numjsArrayOfPixelsCoordinate;
let adjustc;
let adjustcd;

let fps = 60;

function preprocessing(){
    for (let i = 0; i < canvasHeight + 1; i++){
        let color_ = [];
        let coordinate_ = [];
        for (let j = 0; j < canvasWidth + 1; j++){
            color_.push([random(0,100),random(0,100),random(0,100),random(0,100)]);
            coordinate_.push([j, i])
        }
        arrayOfPixelsColor.push(color_);
        arrayOfPixelsCoordinate.push(coordinate_);
    }

    numjsArrayOfPixelsColor = nj.array(arrayOfPixelsColor, 'float32');
    numjsArrayOfPixelsCoordinate = nj.array(arrayOfPixelsCoordinate, 'float32');

    adjustc = numjsArrayOfPixelsColor.multiply(nj.random([601, 601, 4]));
    adjustcd = numjsArrayOfPixelsCoordinate.multiply(nj.random([601, 601, 2]));
}

function setup(){
    createCanvas(canvasWidth, canvasHeight);
    background(255);
    frameRate(fps);

    colorMode(RGB, 100.0, 100.0, 100.0, 100.0)

    preprocessing()
}


function draw(){
    background(255);

    if (frameCount % 60 === 0) {
        adjustc = numjsArrayOfPixelsColor.multiply(nj.random([601, 601, 4]));
        adjustcd = numjsArrayOfPixelsCoordinate.multiply(nj.random([601, 601, 2]));
    } else if (frameCount % 30 === 0) {
        numjsArrayOfPixelsColor = numjsArrayOfPixelsColor.add(adjustc);
        numjsArrayOfPixelsCoordinate = numjsArrayOfPixelsCoordinate.add(adjustcd);      
    } else {
        numjsArrayOfPixelsColor = numjsArrayOfPixelsColor.subtract(adjustc);
        numjsArrayOfPixelsCoordinate = numjsArrayOfPixelsCoordinate.subtract(adjustcd);
    }
    
    for (let i = 0; i < adjustc.shape[0]; i++) {
        for (let j = 0; j < adjustc.shape[1]; j++) {
            let r = numjsArrayOfPixelsColor.get(i, j, 0);
            let g = numjsArrayOfPixelsColor.get(i, j, 1);
            let b = numjsArrayOfPixelsColor.get(i, j, 2);
            let a = numjsArrayOfPixelsColor.get(i, j, 3);
            let x = numjsArrayOfPixelsCoordinate.get(i, j, 0);
            let y = numjsArrayOfPixelsCoordinate.get(i, j, 1);
            push();
            noFill();
            stroke(r, g, b, a);
            strokeWeight(random(1,2))
            bezier(x, y, x*0.5, y*0.6, x, y*-0.4, x*0.1, y*-0.1);
            pop();
        }
    }
}

出来上がったものはこちらです。

Here is the finished product.


Responsive image



何やら毛の様なものがバッサバッサと動いていますね。なんじゃこりゃ。

Something like hair is moving fast. It's weird.

ま、なにはともあれ上のコードはただ直感的に作り上げたものなので、各自もっと良いやりかたを試行錯誤してみてください。

Any way, the above code is just intuitive, so try something better on your own.



See You Next Page!