WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

canvas の描画メソッド

本日は再び canvas ネタです。前回の記事で単純な矩形を描画する例は掲載しましたが、もちろん canvas で描画できるのは矩形だけではありません。 canvas のメソッドは大別して描画、スタイル指定、座標変換、クリッピングなどから構成されます。本日は、それらの中から描画メソッドについて詳細にご紹介したいと思います。

概要

canvas の描画機能は非常にシンプルです。基本的には、以下の 3 種類の図形しか用意されていません。

  • 矩形
  • パス
  • ビットマップ画像

少ないと思うかもしれませんが、パスには直線だけでなく円弧やベジェ曲線なども含められるので、ほぼすべての図形が描画できます。また、 canvas の描画メソッドはあくまで描画する形状の情報のみをパラメータとして持ち、描画色などの指定はありません。描画時のスタイル指定にはまた別のメソッドが用意されています。このような構成をとっているため、少ないメソッドでも組み合わせによって多彩な表現が可能となるわけです。

それでは、上記の 3 グループそれぞれに所属するメソッドをご紹介していこうと思います。

矩形

まずは最も基本的な矩形描画です。このグループには、輪郭のみ、塗り潰し、削除の 3 つのメソッドがあります(下表)。

strokeRect(x, y, width, height)
矩形の輪郭を描画します。
fillRect(x, y, width, height)
矩形を塗り潰します。
clearRect(x, y, width, height)
矩形の内部を削除し、完全な透明(α値= 0 )にします。

いずれもパラメータは共通で、矩形の左上座標と横・縦のサイズを指定します。前述のとおり、描画色などは他のメソッドであらかじめ設定しておく必要があります。この中で特殊なのが clearRect です。このメソッドは指定した矩形領域を完全透明の状態にリセットします。描画領域のα値をクリアする(たぶん)唯一の方法ですので覚えておいてください。他のメソッドを使って透明度 0 で描画しても、なにも描画されないだけですから(^^;

パス

矩形に比べてパスの描画は少々複雑です。パスを描画するためには、まずパスの形状を定義し、その上で輪郭の描画、もしくは塗りつぶしを実行します。具体的な手順は以下のようになります。

  1. beginPath() を呼び出し、パスの定義を開始する
  2. 直線、円弧などのメソッドを必要なだけ呼び出し、パスの形状を定義する。
  3. closePath() を呼び出し、パスを閉じる(これは必須ではありません)。
  4. fill(), stroke() のどちらか、もしくは両方を呼び出し、描画領域にパスを描画する。

それでは、それぞれの手順を詳しく見ていきましょう。

パス定義の開始・終了

パスの定義を開始するときは、必ず beginPath メソッドを呼び出します。そして終了時には必要に応じて closePath メソッドを呼び出します。

beginPath()
パスの定義を開始します。
closePath()
パスの始点と終点を結び、閉じた図形にします。

beginPath については必ず呼ばなければいけないので覚えてしまえばよいでしょう。ちょっとわかりにくいのが closePath を呼び出す条件です。このメソッドを呼んでも呼ばなくてもパスは描画できますが、輪郭を描画したときの結果が異なります。下図は、どちらも (10, 90) - (50, 10) - (90, 90) というパスの輪郭を描画したものですが、左は closePath の呼び出しを行い、右はそれを省略しています。

canvas 閉じた三角形 canvas 開いた三角形
closePath あり closePath なし

ご覧のように、左は始点と終点が直線で結ばれて閉じた図形になっているのに対して、右は開いたままになっています。ちなみに、塗りつぶしの場合は closePath の呼び出しに関わらずまったく同じ結果になります。

パスの定義

beginPath メソッドを呼び出した後は、以下のメソッドでパスを定義することができます。 canvas はパスの終端を「現在位置」として常に保存しており、ほとんどのメソッドはそれを始点として使用します。これにより、連続したパスを簡単に作成できます。

moveTo(x, y)
パスを生成せずに現在位置を (x, y) に移動します。
lineTo(x, y)
現在位置と (x, y) を結ぶ直線をパスに加えます。実行後、現在位置は (x, y) に移動します。
arc(x, y, radius, startAngle, endAngle, anticlockwise)
(x, y) を中心とする半径 radius 円弧をパスに加えます。 startAngle は開始角度、 endAngle は終了角度です。いずれもラジアン単位です。 anticlockwise は false なら時計回り、 true なら反時計回りにパスを生成します。
quadraticCurveTo(cp1x, cp1y, x, y)
現在位置を始点、 (x, y) を終点とする二次曲線をパスに追加します。 (cp1x, cp1y) は制御点です。実行後、現在位置は (x, y) に移動します。なお、 Internet Explorer 6 と ExplorerCanvas の組み合わせでは、このメソッドは正しい描画結果を生成しません。おそらく初期の Firefox と同じバグだと思われます。対処方法は こちらのページをご覧ください。
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
現在位置を始点、 (x, y) を終点とする三次ベジェ曲線をパスに追加します。 (cp1x, cp1y) と (cp2x, cp2y) は制御点です。実行後、現在位置は (x, y) に移動します。

以下、それぞれの描画例とソースです。左上から右へ順番に、直線、円弧、二次曲線、 Bezier 曲線です。前述のとおり、 Internet Explorer 6 (+ ExplorerCanvas)では、左下の二次曲線は正しく表示されません。おそらく、実際よりも急なカーブになっているはずです。

canvas いろいろなパス

ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(30, 70);
ctx.lineTo(60, 30);
ctx.lineTo(80, 90);
 
ctx.moveTo(190, 50);
ctx.arc(150, 50, 40, 0, Math.PI*1.5, false);
 
ctx.moveTo(10, 110);
ctx.quadraticCurveTo(50, 190, 90, 110);
 
ctx.moveTo(110, 110);
ctx.bezierCurveTo(130, 170, 160, 130, 180, 190);
 
ctx.stroke();

蛇足ですが、 quadraticCurveTobezierCurveTo が描く曲線には、以下の性質があります。覚えておくと、なにかと便利だと思いますよ。

  • カーブ全体は、始点、終点、すべての制御点を含む最小の凸な多角形(凸包と呼びます)の内部に必ず納まります。
  • 始点、終点での傾きはそれと隣接する制御点を結ぶ直線と等しくなります。
  • 上と同じことですが、 2 つのカーブを接続するときは、前のカーブの最後の制御点、前のカーブの終点(=次のカーブの始点)、次のカーブの最初の制御点をすべて同一直線上に並べると、滑らか(一次連続)に接続できます。

以上、ゲームプログラマーの戯言でした(^^;

パスの描画

パスを定義したら、最後に以下の 2 つのメソッドのどちらか、もしくは両方を呼び出して、パスを実際に canvas の描画領域に描画します。

fill()
直前に定義したパスを塗りつぶします。
stroke()
直前に定義したパスの輪郭を描画します。

下図は、円形のパスに対して、左から fill のみ、 stroke のみ、 fill + stroke を呼び出したときの描画結果です。わかりやすいように fillstroke の色の濃さを変えてあります。

canvas fill と stroke

ビットマップ画像

canvas はビットマップ画像を描画することもできます。画像を拡大/縮小したり、一部を切り取って描画することも可能です。他のメソッドを組み合わせればさらに多彩な表現も可能ですが、それは後々の記事に譲りましょう。今回はビットマップ画像の描画メソッドのみで可能な範囲の使い方をご紹介します。

canvas でビットマップ画像を描画するには、以下の 2 段階のプロセスを踏む必要があります。

  1. 描画する元画像の JavaScript オブジェクトを取得(もしくは作成)する。
  2. drawImage メソッドを使用して、描画領域に画像を描画する。

(1) の元画像としては、もともとページにある画像のほか、 JavaScript で画像をロードしたり、他の canvas 要素の画像を使用することも可能です。まずはそれらの画像のロード方法をご紹介し、その後に drawImage メソッドの詳細を見ていきましょう。

ページにある画像を利用する

もともとの HTML で配置されている画像は、普通に getElementById メソッドなどでオブジェクトを取得できます。

var image = document.getElementById("page_canvas_image");

ただし、ブラウザによっては以下の条件にあてはまる画像は使えないかもしれません。

  • 表示サイズ(img 要素の width, height 属性)が 0 に設定されている(代わりに 1 を設定すれば OK)。
  • CSS などによって非表示に設定されている。
  • 読み込みが完了していない(「 JavaScript で画像をロードする」と同じ方法で完了を待つことができます)。

公開ページで使用する際は、これらの条件を避けるようにしましょう。

JavaScript で画像をロードする

JavaScript で画像をロードするためには、まず Image クラスのインスタンスを作成し、その src 属性に画像の URL を指定します。ただし、「ページにある画像を利用する」と同様にロードの完了を待つ必要があります。そのためには、 onload イベントを使用して描画を行います。

var image;
image = new Image();
image.onload = function()
{
  // canvas 要素への描画処理
}
image.src = "http://example.com/example.png";

描画する画像がひとつのときは上記の単純な方法で処理できますが、複数の画像を使用する場合は工夫が必要です。ただし、この手の非同期処理はメモリリークなどの厄介なバグの原因になりやすいので、細心の注意が必要です。もし JavaScript のメモリリーク問題に詳しくなければ、画像を一枚にまとめて drawImage メソッドのパラメータで描画する範囲を指定するほうがお勧めです。場合によりますが、このほうが画像の読み込みも速いと思います。

Internet Explorer 6 + ExplorerCanvas の環境では、 Image オブジェクトのプロパティーを設定する順番にも注意が必要です。上記のように、 onload ハンドラを設定してから src を指定しなければなりません。逆にすると、画像が正常に読み込めないことがあります。私も最初はこれに気付かず、 IE6 で画像が表示されないまま公開してしまいました。現在は修正されています。 IE6 をご利用の皆さん、申し訳ありませんでした。 m(_ _)m

他の canvas 要素の画像を使用する

canvas 要素自体も画像オブジェクトとして使用できます。 img 要素と同様に、 getElementById メソッドなどで取得できます。

var image = document.getElementById("sw_example_canvas");

画像として描画する前に元の canvas オブジェクトの描画領域になんらかの画像を描画しておく必要があります。 img 要素と違い、 canvas オブジェクトが非表示になっていることは問題にならないはずです。

drawImage メソッドの使用方法

上記 3 つの方法のいずれかで描画すべき画像オブジェクトが取得できたら、 drawImage メソッドで描画領域に画像を秒ができます。 drawImage メソッドには、以下の 3 つの呼び出し方があります。

  • drawImage(image, dx, dy)
  • drawImage(image, dx, dy, dwidth, dheight)
  • drawImage(image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight)

元画像全体をそのままのサイズで描画するなら最初の書式で OK です。拡大 / 縮小が必要なら 2 番目、元画像の一部分だけを(必要に応じて拡大 / 縮小して)描画したいなら 3 番目を使うとよいでしょう。それぞれのパラメータの意味は以下のとおりです。

image
描画する画像オブジェクトです。前述の方法で取得したオブジェクトをしてください。
dx
描画位置の左上の X 座標です。
dy
描画位置の左上の Y 座標です。
dwidth
canvas の描画領域上での画像の横幅です。この値が swidth (もしくは元画像の横幅)と異なる場合は画像が拡大 / 縮小されます。
dheight
canvas の描画領域上での画像の高さ(縦幅)です。この値が sheight (もしくは元画像の高さ)と異なる場合は画像が拡大 / 縮小されます。
sx
元画像における描画対象範囲の左上 X 座標です。省略すると 0 が指定されます。
sy
元画像における描画対象範囲の左上 Y 座標です。省略すると 0 が指定されます。
swidth
元画像における描画対象範囲の横幅です。省略すると元画像の横幅が指定されます。
sheight
元画像における描画対象範囲の高さ(縦幅)です。省略すると元画像の高さが指定されます。

例えば、単純に (0, 0) の位置に画像を描画するなら、以下のように実行します。

ctx.drawImage(image, 0, 0);

元画像が 256x256 だったとして、それを縦横 2 倍に拡大して描画。

ctx.drawImage(image, 0, 0, 512, 512);

同じく元画像が 256x256 だったとして、その中心の縦横半分の矩形領域を縦横 2 倍に拡大して描画。

ctx.drawImage(image, 64, 64, 128, 128, 0, 0, 256, 256);

だいたい感じが掴めるでしょうか?(^^;

ただし、現状の drawImage メソッドはブラウザ側の実装が追いついていない部分があります。私が試した限りですが、 Firefox は問題なし、 Opera では 2 番目の呼び出し方が機能せず、 IE6 + ExplorerCanvas では canvas 要素に "position: relative" か "position: absolute" スタイルがほぼ必須、という感じです。今後のバージョンアップで改善されると思われますが、現状では注意が必要です。

描画例

以下の HTML は、哀れな私のサーバーの姿を表示し、画像がクリックされるとマザーボードの部分を拡大します。ただし、見通しが悪くなるので非対応ブラウザ、および IE6 + ExplorerCanvas への対応を省略しています。実行例では対処していますので、興味のある方はこのページのソースをご覧ください m(_ _)m

<script type="text/javascript">
var loaded = false;
var toggle = true;
var image = new Image();

function drawImage()
{
  if(!loaded)
    return;
  var canvas = document.getElementById("canvas_image");
  if(canvas.getContext)
  {
    var ctx = canvas.getContext("2d");
    var sx      = toggle ? 0 : 52;
    var sy      = toggle ? 0 : 28;
    var swidth  = toggle ? image.width : 160;
    var sheight = toggle ? image.height : 120;
    ctx.drawImage(image, sx, sy, swidth, sheight, 0, 0, canvas.width, canvas.height);
  }
}

image.onload = function() {
  loaded = true;
  drawImage();
}
image.src = "http://image.blog.livedoor.jp/sourcewalker/imgs/2/f/2ff6e712.JPG"
</script>

<canvas id="canvas_image" width="160" height="120"
    onclick="toggle = !toggle; drawImage();">
</canvas>

上記のコードを実際に実行するとこうなります。

これだけなら canvas 使わなくてもできるじゃん!という突っ込みが聞こえてきますが、そこはそれ、あくまで例ということでご了承ください。 drawImage 単体ではこの程度ですが、やはり他のメソッドと組み合わせることでさまざまな表現ができます。 canvas の真髄は組み合わせです(゜∀゜)

本日は canvas の描画メソッドをご紹介しました。まだまだ canvas の機能のごく一部ですが、ここまでの知識だけでもけっこうなことができると思います。最後の例のようにユーザー操作に反応させてみたり、タイマー割り込みでアニメーションさせてみたりなど、いろいろ試してみてください。次回は描画色をはじめとする描画スタイルの指定方法をご紹介しようと思っています。今回の描画メソッドと組み合わせれば、驚くほどの表現が可能ですよ。お楽しみに!

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles