Chapter 40

レイヤー

miku
miku
2021.11.15に更新

clear() を呼び出すと画面が全てクリアされるが、中には残しておきたい描画もあるだろう。これを実現するために階層を分けて描画を行うという方法がある。一般的にペイントソフトなどではこの階層のことをレイヤーと呼ぶのが一般的なので以降はその表記で扱う。

レイヤー

function setup() {
  createCanvas(windowWidth, windowHeight);

  const layer = createGraphics(width, height);
  layer.fill(240);
  layer.circle(width / 2, height / 2, 50);
  image(layer, 0, 0);
}

createGraphics(幅, 高さ) で新しいレイヤーを生成する。そのレイヤーから fill()circle() のような関数を呼び出すと、そのレイヤーにのみ塗りや描画が適用される。レイヤーの描画は image() から行う。扱い方は画像の描画と同じで、最低限の使い方は image(layer, x, y) になる。何も描いていない部分は透明なので上から描画しても下の描画が見えなくなったりすることはない。

レイヤーの順序

image() でレイヤーの内容が描画されるので、レイヤーの順序は image() を呼び出す順番によって決まる。なので、レイヤーの順序を制御する場合は、レイヤーと番号をセットで保存しておき、番号でソートした後、その番号順にレイヤーを描画すればいい。

function setup() {
  createCanvas(windowWidth, windowHeight);

  const n = 30;
  const layers = [];
  for (let i = 0; i < n; i++) {
    const layer = createGraphics(width, height);
    layer.colorMode(HSB);

    const h = random(360);
    const d = random(100, 300);
    layer.fill(h, 100, 100);
    layer.circle(random(width), random(height), d);
    layers.push({ layer, index: h });
  }

  layers.sort((a, b) => b.index - a.index);

  layers.forEach((data) => {
    image(data.layer, 0, 0);
  });
}

上記作例では、色相の値をレイヤーの番号にして降順に並べて描画している。つまり色相の値が小さいほど前に表示されることになる。

プレビュー

const d = 60;
let layer1, layer2;

function setup() {
  createCanvas(windowWidth, windowHeight);

  layer1 = createGraphics(width, height);
  layer2 = createGraphics(width, height);

  layer2.fill(255, 50);
}

function draw() {
  layer2.clear();
  layer2.circle(mouseX, mouseY, d);

  clear();
  image(layer1, 0, 0);
  image(layer2, 0, 0);
}

function mouseClicked() {
  layer1.circle(mouseX, mouseY, d);
}

2つのレイヤーである layer1, layer2 を作成する。layer2 のマウス座標にうっすらと円を描画しておき、マウスがクリックされると layer1 のマウス座標にくっきりとした円を描く。こうすると layer2 がプレビューの機能になる。

クリックすると前に持ってくる

複数のレイヤーを用意して、マウスで画面をクリックすると、その座標に何かが描かれていた場合、そのレイヤーを最前面に持ってくるようにしたい。該当するレイヤーが複数ある場合は最も前面にあるものを前に持ってくる。

get(x, y) でピクセルの色を [R, G, B, A] という形で取れるので、何かが描かれているかどうかはこれで判定ができる。何も描かれていないなら [0, 0, 0, 0] が返ってくるはずなので、配列の要素が全て 0 でないなら何かが描かれていると判定することができる。黒の描画をしないならば brightness() で明度が 0 かどうかを判定するという方法もある。下記作例では簡潔のため、後者の方向で判定を行っている。

let n, layers;

function setup() {
  createCanvas(windowWidth, windowHeight);

  n = 30;
  layers = [];
  for (let i = 0; i < n; i++) {
    const layer = createGraphics(width, height);
    layer.colorMode(HSB);

    const h = random(360);
    const d = random(100, 300);
    layer.fill(h, 100, 100);
    layer.circle(random(width), random(height), d);
    layers.push(layer);
  }

  drawLayers();
}

function drawLayers() {
  clear();

  layers.forEach((layer) => {
    image(layer, 0, 0);
  });
}

function mouseClicked() {
  for (let i = n - 1; i >= 0; i--) {
    const layer = layers[i];
    if (brightness(layer.get(mouseX, mouseY)) > 0) {
      layers.splice(i, 1);
      layers.push(layer);
      break;
    }
  }
  drawLayers();
}