Chapter 09

画像とレイアウト

miku
miku
2021.11.10に更新

画像の読み込み

let img;

function preload() {
  img = loadImage("0.png");
}

function setup() {
  // preload() の読み込みが終了次第呼ばれる
}

preload() を用意し、その関数内で loadImage(画像のURL) で画像の読み込みを開始する。読み込み完了後に setup() が呼ばれる。

loadImage() の戻り値は、画像のピクセルデータや画像に関する情報が入ったオブジェクトが返却される。ここでは便宜上、そのオブジェクトのことをImageオブジェクトと呼ぶ。

複数の loadImage() を記述した場合は、すべての画像を読み込み終えた後に setup() が呼ばれる。

画像の描画

let img;

function preload() {
  img = loadImage("0.png");
}

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

  image(img, 0, 0);
}

読み込んだ画像をCanvasに描画するには image(Imageオブジェクト, x座標, y座標) を利用する。画像の原点は左上になる。

中央に寄せる

配置のバランスを考えると中央に画像を描画をするケースは多い。画像に限らず他の描画でも同様なので、中央に寄せる方法は知っておいたほうがいい。

中央に寄せた場合の完成図を見てみると、赤丸の位置、つまり画像の原点は画面サイズの半分からオブジェクトの半分を引いた位置になる。

x軸で考えてみると、

width / 2 - img.width / 2; // 画像の幅はwidthで取得できる

共通の2で割る部分を括ると、

(width - img.width) / 2;

となる。y軸も合わせると、

x = (width - img.width) / 2;
y = (height - img.height) / 2;

となる。

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const x = (width - img.width) / 2;
  const y = (height - img.height) / 2;
  image(img, x, y);
}

読み込んだ画像を画面中央に貼り付ける作例。

画面サイズより画像の方が大きい場合

画面サイズより画像の方が大きいときに中央揃えをする場合でも、完成図の画像の原点は画面サイズの半分からオブジェクトの半分を引いた位置になる。

なのでこのような場合でも、先程の計算式で中央揃えができる。

imageMode()

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const x = width / 2;
  const y = height / 2;
  image(img, x, y);
}

画像の原点は左上だが、imageMode(CENTER) で中央に変えることができる。この状態で中央に配置するには (width / 2, height / 2) と、中央の座標を指定すればいい。

画面に合わせる

背景に使われるような画像はなるべく画面を覆うような形で描画されることが多い。これには色々な配置方法が考えられるが、画像のサイズを変更して描画することは共通なので、まずは画像の任意のサイズで描画する方法について学ぶ。

画像を任意のサイズで描画する

let img;

function preload() {
  img = loadImage("0.png");
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  image(img, 0, 0, 100, 50);
}

image(Imageオブジェクト, x, y, width, height) で、Imageオブジェクトを (x, y) の位置から (width, height) のサイズで描画する。

これで画像サイズを変更する方法がわかったので、画面に合わせて画像を配置する方法について考えていく。

縦横フルに合わせる

image(img, 0, 0, width, height);

画像を画面サイズぴったりに合わせて描画する場合。

もっとも単純な方法だが、画像と画面サイズの縦横比が一致していなければ縦横どちらかが引っ張られた形になり見栄えが悪い。単色画像などでない限り、あまり使いみちのない方法だといえるだろう。

次は縦横比を維持したケースについて考える。

縦横比を維持しながら横に合わせる

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const w = width;
  const h = img.height * (width / img.width);
  image(img, 0, 0, w, h);
}

画像の横幅を画面横幅にして、拡大縮小した分だけ画像の縦幅を変更する場合。

画像の横幅は画面横幅になる。この時、拡大縮小する比率は 画面横幅 / 画像の横幅 になる。あとはその比率を縦に掛けたものを画像の縦幅にすればいい。

縦横比を維持しながら縦に合わせる

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const h = height;
  const w = img.width * (height / img.height);
  image(img, 0, 0, w, h);
}

画像の縦幅を画面縦幅にして、拡大縮小した分だけ画像の横幅を変更する場合。

これは上の処理とアルゴリズムは同じで、幅と高さの計算が逆になっている。

画面に合わせていない方を中央に寄せる

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const w = width;
  const h = img.height * (width / img.width);
  image(img, 0, (height - h) / 2, w, h);
}

たとえば、画像を画面横幅に合わせて縦もその比率で拡大縮小した場合、縦の尺が足りないか逆にはみでる可能性があるが、その場合中央に寄せて見栄えをよくする方法がある。画面と画像の縦幅が同じだった場合、計算後の y0 になるので問題はない。

画面に収める

縦か横、一方に隙間ができてもいいので、どんな画像でも画面に画像全体が完全に収まるようにしたい場合について考える。一方を画面に合わせた場合に起きえるのは下記の5パターンである。

  • 画面に画像の縦横がぴったり一致している
  • 横に合わせた場合、縦が画面内に収まっている
  • 縦に合わせた場合、横が画面内に収まっている
  • 横に合わせた場合、縦が画面からはみ出ている
  • 縦に合わせた場合、横が画面からはみ出ている

この内、条件を満たしていないのは下の2パターンである。なので、縦にはみ出る場合は代わりに縦を合わせる、横にはみ出る場合は代わりに横を合わせて拡大縮小すればいい。

これは感覚的な理解なので、具体的な数字で考えてみる。

- 横(px) 縦(px)
画面 600 500
画像1 550 500
画像2 650 500

2枚の画像を画面の縦幅に合わせるために拡大縮小した結果が上記である。このとき、画像の横幅が画面の横幅以下なら縦横ともに画面に収まっていることになる。画像1は収まっているが、画像2は収まっていない。

もう少しわかりやすくするために、縦横共に縦幅で割り、縦1に対して横がいくらなのかを計算する。

-
画面 1.2 1
画像1 1.1 1
画像2 1.3 1

縦1に対して、横の比が画面以下であれば収まることになる。

では、収まらなかった画像2はどうすればいいのか。この場合、横が1になるように、つまり横を画面に合わせる形にしてみよう。

-
画面 1 0.83
画像2 1 0.77

画面より画像2の方が横の比が大きいので、横を1にすると縦の比は必ず画面以下になる。つまり、縦に合わせてはみ出る場合は、横に合わせればいいことがわかる。

縦1に対して横の比がいくらかは、横幅 / 縦幅で直接計算できるので、この比が、画面以下なら縦に、でなければ横に合わせればいい。

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const a = width / height; // 画面の縦1に対する横の比
  const b = img.width / img.height; // 画像の縦1に対する横の比

  let w, h;
  // 比が画面より画像の方が大きいなら横にはみ出ているので、代わりに横に合わせる
  if (a < b) {
    w = width;
    h = img.height * (width / img.width);
  }
  // 比が画面より画像の方が小さいなら横も画面内に収まっているので、縦に合わせる
  else {
    h = height;
    w = img.width * (height / img.height);
  }

  // 中央揃えで描画
  image(img, (width - w) / 2, (height - h) / 2, w, h);
}

隙間が出ないようにする

上とは逆に、縦か横のどちらかを画面に合わせた場合、もう一方が画面サイズ以上、つまり隙間が出ないように完全に画面を覆う形で画像を描画するケースについて考える。

これは上の処理とは逆で、縦に合わせて横が画面横幅以上ならそれで条件は満たしていて、そうでなければ横に合わせればいい。

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const a = width / height;
  const b = img.width / img.height;
  let w, h;
  if (a > b) {
    w = width;
    h = img.height * (width / img.width);
  } else {
    h = height;
    w = img.width * (height / img.height);
  }
  image(img, (width - w) / 2, (height - h) / 2, w, h);
}

スムージングを無効にする

let img;

function preload() {
  img = loadImage("0.png");
}

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

  const scale = 10;
  // 左の描画(スムージングあり)
  image(img, width / 3, height / 2, img.width * scale, img.height * scale);

  noSmooth();
  // 右の描画(スムージングなし)
  image(img, (width / 3) * 2, height / 2, img.width * scale, img.height * scale);
}

画像を拡大して描画すると、輪郭のギザギザ部分を目立たなくするためのスムージングという補間処理が自動で入る。ただし、上記結果のようにドット絵だと見た目が悪化してしまうので noSmooth() を呼び出すとスムージングを無効にできる。

画像の一部分を切り出す

ドット絵やボタン画像などは各動作のパーツ画像がまとまって1つの画像になっているケースが多く、その場合はそこからパーツごとに切り出さなければ利用できない。image() では画像の一部分だけを切り抜くことはできないので代わりに copy() を利用する。

copy(Imageオブジェクト, sx, sy, sw, sh, dx, dy, dw, dh);

第一引数に指定した画像の矩形領域 (sx, sy) ~ (sx + sw, sy + sh) を切り抜き、Canvasの矩形領域 (dx, dy) ~ (dx + dw, dy + dh) に描画する。

image() と違い、描画先の領域 (dw, dh) の指定は必須で、もとの領域 (sw, sh) とサイズが違う場合は自動で拡大縮小される。

const cw = 16;
const ch = 18;
const scale = 5;
let img;

function preload() {
  img = loadImage("0.png");
}

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

  for (let i = 0; i < 3; i++) {
    copy(img, cw * i, 0, cw, ch, cw * i * scale, 0, cw * scale, ch * scale);
  }
}

上記画像の1行目にある、3つの16x18pxの各パーツを切り出して、拡大して描画する作例。copy() をうまく利用すればドット絵のアニメーションを表現することも可能で、その方法は後の章で扱う。