月別アーカイブ: 2012年12月

HTML5 Canvas:円を描く時のパフォーマンス

この記事はHTML5 Advent Calendar 2012の13日目のエントリーになります。

Canvasのクリスマスツリーもう少しでクリスマスなので、HTML5 の canvas でクリスマスツリーに飾り玉を描こうかなと思いました。色んな方法を考えながら、Stack Overflow に「円を円形グラデーションだけで描けるよ」というコメントを見つけました。

ご存知だと思いますが、canvas で円を描くには普通 arc()を使います:

// 普通の円の描き方
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, true);
ctx.fillStyle = 'rgba(195, 56, 56, 1)';
ctx.fill();
ctx.closePath();

例えば SVG と比べるとちょっと面倒くさい感じがするかも知れません。円形グラデーションを使うのが良いアイディアと思い、それぞれのやり方のパフォーマンスの違いが知りたくなりました。

// 円形グラデーションで円の描き方
var gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0.95, 'rgba(195, 56, 56, 1)');
gradient.addColorStop(1, 'rgba(195, 56, 56, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(x - radius, y - radius, x + radius, y + radius);

この canvas テストページで速さの違いが実際に見えます。

やっぱり arc() より円形グラデーションの方が遅いです。何倍も遅い!テストページを作る前、これに気付くべきでしたが、まぁ、どうせなので3Dっぽい球の描き方も調べる気になりました。

Canvas で球を描くには主に二つの方法があります:

  1. 円形グラデーションを利用する
  2. 既存の画像を利用する
// 円形グラデーションでの描き方
var gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.2, 'rgba(255, 85, 85, 1)');
gradient.addColorStop(0.95, 'rgba(128, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(128, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(x - radius, y - radius, x + radius, y + radius);
// 既存の画像での描き方
var img = new Image();
img.src = 'images/baubles.png';
ctx.drawImage(img, x, y, width, height);

先ほどと同じように円形グラデーションの方が数倍遅いみたいです。しかし円形グラデーションの利点はもちろんその効果が動的に作られているため、JavaScript で変更することができます。一方画像を使う場合、ソフトで画像を前もって作る必要があります。JavaScript での編集ができませんが、大きさなら変更できます。色を変更したい時はこの二つの方法のいずれかが使えます:

  1. 複数バージョンのある sprite 画像を利用する。
  2. 白黒の画像を利用し、arc() で半透明のオーバーレイを適用する。

ところで画像のダウンロード時間も忘れてはいけませんので、ユーザを待たせないように画像のプリロード(先読み)がおすすめです。

前述の canvas テストページでそれぞれの速さの比較ができます。

テストから分かるように、半透明オーバーレイの方法は少し遅いですが、色の変更がもっと自由にできます。ただ、元々の画像よりコントラストが少し悪くなります。我慢できる位であるかどうかは皆様にお任せします!。

まとめ

結局簡単なアプリや力のあるデバイスなら速さの違いは気付く程ではないかも知れませんが、アニメーションを使う時、高速ゲームを作る時、もしくはTV用のアプリを作る時、パフォーマンスが重要になります。色々な選択肢がありますので、私からのアドバイスは下記の通りです:

  • 円が描きたい時、arc()を使いましょう。
  • 3Dっぽい球が描きたい時、画像を使いましょう(プリロードを忘れずに)。
  • 色んな色の球が描きたい時、sprite 画像を使いましょう。
  • 動的に色が変わる球が描きたい時、半透明オーバーレイのある画像を使ってみましょう。
  • 円形グラデーションは必要な時だけに使いましょう。

最後にもう一つ学んだことですが、やっぱり数千個の飾り玉のあるクリスマスツリーはオシャレじゃない!

更新:

TwitterでMarceloさんが良いアイディアをつぶやきました。隠されているcanvasに円形グラデーションで円を描いて、それをdrawImage()で元々のcanvasに数回描くというアイディアでした。これで画像を前もって作る必要がないし、動的に色を変えることも可能です。しかも既存画像にdrawImage()を使うよりもパフォーマンスが良いです!(円形グラデーションの描く時間を除けば。)コードはこんな感じです:

// いわゆる「バッファー」canvasを作るけど、ページに追加しない
var tmpCanvas = document.createElement('canvas');
var tmpCtx = tmpCanvas.getContext('2d');

// ここで必要なグラデーションを用意する(上記のサンプルみたいに)

// さきほどの「バッファー」canvasから元々のcanvasに描く
ctx.drawImage(tmpCanvas, x, y, width, height);

つまり、円や玉をたくさん描く時はこの方法が一番お勧めです。Marceloさん、サンキュー!