ハルゲルト・リタヒーの冒険

2022年12月15日木曜日

p5.js Processing 作例 初心者向け

t f B! P L

現代アートの巨匠と呼ばれるゲルハルト・リヒター。彼の作品の真似をしていて出来上がった(元とは全然違う)ものを、制作の過程とコードを交えて紹介します。

👉 Read this article in English.

ゲルハルト・リヒター 「4900の色彩」


絵の具の見本帳をヒントに、25色のカラーチップをランダムに配置して作られた「カラーチャート」シリーズ。ゲルハルト・リヒターのこの作品に興味を持ったのは、AI と創造性について書かれた本「レンブラントの身震い」の中で、ランダム性は創造性と同じなのか?という観点で紹介されていたのを読んだからでした。

25色の色を用意して、その中からランダムに選んで順番にグリッド上に置いていく。そんなことで面白いものが作れるのか?コードを書いて実行してみます。


結果、あまり楽しくない…。

これで面白いものが出来るかどうかは、「いかに 25色の良い色を選ぶか」で決まってくるのではないでしょうか?

良い色の組み合わせ、良いカラーパレットをどうコードで生成するか?突き詰め甲斐のある問いですが、これはかなり難しい問題です。


/** 
 * Color chips with 25 random colors.
 * ref. Gerhard Richter '4900 Farben'
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.5.0
 * created 2022.12.08
 */

const margin   = 20;
const chipNum  = 32;
const chipSiz  = 20;
const chipGap  = 0;
const colorNum = 25;
const canvasW  = chipSiz * chipNum + margin * 2;

function setup() {
  createCanvas(canvasW, canvasW);
  colorMode(HSB, 360, 100, 100, 100);

  // set random color palette
  const palette = new Array(colorNum);
  for (let i = 0; i < colorNum; i++) {
    palette[i] = color(random(360), random(40, 60), random(60, 80));
  }

  // draw
  background(0, 0, 90, 100);
  translate(margin, margin);
  stroke(0, 0, 90, 100);
  strokeWeight(chipGap);
  for (let chipY = 0; chipY < chipNum; chipY++) {
    for (let chipX = 0; chipX < chipNum; chipX++) {
      fill(palette[floor(random(colorNum))]);
      rect(
        chipX * chipSiz,
        chipY * chipSiz,
        chipSiz,
        chipSiz
      );
    }
  }
}

 

規則性を入れてみる

よい色の生成は今の私には荷が重いので、違う方向性を考えます。

ゲルハルト・リヒターの「4900の色彩」を見た時、私は、似た色や明度の低い部分を自然と目でなぞり、色の並びに規則性や構造を見出そうとしていました。だったら、最初から構造を埋め込んでみるのはどうでしょう?

例えば、あるパターンで大きな構造を作り、そこに同じパターンの小さな構造を入れていく。


パターンは、ゲルハルト・リヒターの作品と同じように 25色からランダムに選んだ色のパターンとします。

大きな構造

ここに同じパターンを小さくしたものを乗せていきますが、普通に乗せたら大きな構造の意味が無くなってしまいます。

大きな構造の上に小さな構造を重ねる

なので、小さなパターンは色を半透明にして乗せていきます。

大きな構造の上に小さな構造を重ねる

 

p5.js/Processing コード例

構造は再帰的だけれど、再帰のコードにするまでもないので、ベタで解りやすい 2重ループにします。大きな構造に BLUR をかけると、ちょっといい感じになりました。

大きな色の構造の中に同じ構造の小さなものを重ねる

p5.js ではキャンバスサイズに変数が使えること、二次元配列が面倒なこと、p5.Color を使ったときに fill() で透明度を指定できないらしいことから、p5.js と Processing で少し処理が異なるコードになっています。


/** 
 * Color patterns with 25 random colors.
 * ref. Gerhard Richter '4900 Farben'
 * 
 * @author @deconbatch
 * @version 0.1
 * @license GPL3
 * p5.js 1.5.0
 * created 2022.12.12
 */

const margin   = 20;
const chipNum  = 5;
const chipSiz  = 24;
const chipGap  = 2;
const colorNum = 25;
const palette  = new Array(colorNum);
const pattern  = new Array(chipNum * chipNum);
const canvasW  = chipSiz * chipNum * chipNum + margin * 2;

function setup() {
  createCanvas(canvasW, canvasW);
  colorMode(HSB, 360, 100, 100, 100);
  smooth();
  noLoop();

  // palette
  for (let i = 0; i < colorNum; i++) {
    let h = random(360);
    let s = random(40, 60);
    let b = random(60, 80);
    palette[i] = color(h, s, b, 100);
  }

  // make pattern
  for (let y = 0; y < chipNum; y++) {
    for (let x = 0; x < chipNum; x++) {
      pattern[x + y * chipNum] = palette[floor(random(colorNum))];
    }
  }

  // draw
  background(0, 0, 90, 100);
  translate(margin, margin);

  // large pattern
  noStroke();
  for (let y = 0; y < chipNum; y++) {
    for (let x = 0; x < chipNum; x++) {
      fill(pattern[x + y * chipNum]); // no transparent
      rect(
        x * chipSiz * chipNum,
        y * chipSiz * chipNum,
        chipSiz * chipNum,
        chipSiz * chipNum
      );
    }
  }
  filter(BLUR, 6);

  // small patterns
  strokeWeight(chipGap);
  stroke(0, 0, 90, 100);
  for (let largeY = 0; largeY < chipNum; largeY++) {
    for (let largeX = 0; largeX < chipNum; largeX++) {
      for (let y = 0; y < chipNum; y++) {
        for (let x = 0; x < chipNum; x++) {
          pattern[x + y * chipNum].setAlpha(50); // transparent
          fill(pattern[x + y * chipNum]);
          rect(
            (largeX * chipNum + x) * chipSiz,
            (largeY * chipNum + y) * chipSiz,
            chipSiz,
            chipSiz
          );
        }
      }
    }
  }
}


/**
 * Color patterns with 25 random colors.
 * ref. Gerhard Richter '4900 Farben'
 * 
 * @author @deconbatch
 * @version 0.1
 * @license GPL3
 * Processing 3.5.3
 * created 2022.12.12
 */

public void setup() {
  size(760, 760);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();

  int margin  = 20;
  int chipNum = 6;
  int chipGap = 2;
  int chipSiz = floor((width - margin * 2) / chipNum / chipNum);

  // palette
  int   colorNum  = 25;
  color palette[] = new color[colorNum];
  for (int i = 0; i < colorNum; i++) {
    float h = random(360.0);
    float s = random(40.0, 60.0);
    float b = random(60.0, 80.0);
    palette[i] = color(h, s, b);
  }

  // make pattern
  color pattern[][] = new color[chipNum][chipNum];
  for (int y = 0; y < chipNum; y++) {
    for (int x = 0; x < chipNum; x++) {
      pattern[x][y] = palette[floor(random(colorNum))];
    }
  }

  // draw
  translate(margin, margin);
  background(0.0, 0.0, 90.0, 100.0);

  // large pattern
  noStroke();
  for (int y = 0; y < chipNum; y++) {
    for (int x = 0; x < chipNum; x++) {
      fill(pattern[x][y], 100.0); // no transparent
      rect(
           x * chipSiz * chipNum,
           y * chipSiz * chipNum,
           chipSiz * chipNum,
           chipSiz * chipNum
           );
    }
  }
  filter(BLUR, 6);

  // small patterns
  strokeWeight(chipGap);
  stroke(0.0, 0.0, 90.0, 100.0);
  for (int largeY = 0; largeY < chipNum; largeY++) {
    for (int largeX = 0; largeX < chipNum; largeX++) {
      for (int y = 0; y < chipNum; y++) {
        for (int x = 0; x < chipNum; x++) {
          fill(pattern[x][y], 50.0); // transparent
          rect(
               (largeX * chipNum + x) * chipSiz,
               (largeY * chipNum + y) * chipSiz,
               chipSiz,
               chipSiz
               );
        }
      }
    }
  }
}

 

まとめ:模写も楽しいですよ

今回のような既存の美術作品をクリエイティブ・コーディングで模写することを時々やっています。やってみると、「こうしたらどうなるだろう?」という発想がわくことも多くて楽しいです。

先達の芸術から何かを学ぶとか硬い話抜きに、単純に遊びとして面白く、おすすめですよ。

色のチップを小さく丸くした作例

 

QooQ