HTML+JavaScriptだけでブラウザに図形描画(3) - Canvas API -

2012年10月11日(木)
山田 祥寛(YAMADA, Yoshihiro)

第2回第3回と続いてきたCanvas API編も、今回が最後。本稿では、キャンバスの設定を保存する方法やキャンバスの内容をデータ化する方法、ユーザーのマウス操作に応じて図形を描画する方法などなどについて紹介します。

[第4回目次]
  • TIPS 028:画像が重なった場合の処理方法を指定する
  • TIPS 029:キャンバスの設定を保存&復元する
  • TIPS 030:画像を拡大/回転/変形して表示する
  • TIPS 031:キャンバス上の図形の一部を切り出す/貼り付ける
  • TIPS 032:マウスポインタの座標に応じて図形を描画する
  • TIPS 033:キャンバス上の図形をData URL形式で保存する

サンプル一式は、会員限定特典としてダウンロードできます。記事末尾をご確認ください。

TIPS 028:画像が重なった場合の処理方法を指定する

globalCompositeOperationプロパティを利用することで、画像が重なった場合の処理方法を変更できます。

[リスト01]画像が重なった場合の処理方法の違いを確認するコード(composite.html)

window.addEventListener('DOMContentLoaded',
  function() {
    if (HTMLCanvasElement) {
      var cv = document.querySelector('#cv');
      var c = cv.getContext('2d');
      // 赤い四角を描画
      c.fillStyle = '#F00';
      c.fillRect(10, 10, 200, 200);
      // 画像が重なった場合の処理方法を指定
      c.globalCompositeOperation = 'source-atop';
      //c.globalCompositeOperation = 'source-in';
      //c.globalCompositeOperation = 'source-out';
      //c.globalCompositeOperation = 'source-over';
      //c.globalCompositeOperation = 'destination-atop';
      //c.globalCompositeOperation = 'destination-in';
      //c.globalCompositeOperation = 'destination-out';
      //c.globalCompositeOperation = 'destination-over';
      //c.globalCompositeOperation = 'lighter';
      //c.globalCompositeOperation = 'copy';
      //c.globalCompositeOperation = 'xor';
      // 青い四角を描画
      c.fillStyle = '#00F';
      c.fillRect(100, 100, 200, 200);
    }
  }
);
図1:画像が重なった場合のいろいろな処理(クリックで拡大)

globalCompositeOperationプロパティで指定できる値と、その結果は上の図のとおりです。サンプル上はひとつだけを残してコメントアウトしていますので、適宜、コメントイン/コメントアウトして、動作の変化を確認してみると良いでしょう。

なお、値の解釈はブラウザによって微妙に異なるようです。上の図で示しているのはGoogle Chromeでの結果です。実際に利用する際は、ブラウザごとの検証を欠かさないようにしてください。

TIPS 029:キャンバスの設定を保存&復元する

コンテキストオブジェクト(コンテキスト)は、キャンバスの設定(状態)を管理するためのオブジェクトです。Canvas APIでは、このコンテキストのある時点での設定を保存しておき、あとから復元して再利用することもできます。

以下は、透明度(globalAlphaプロパティ)を少しずつ変化させたコンテキストを保存した上で、これを順に復元しながら透明度の異なる四角形を描画するサンプルです。

[リスト02]透明度を徐々に変化させたコンテキストを保存し、順に適用(save.html)

window.addEventListener('DOMContentLoaded',
  function() {
    if (HTMLCanvasElement) {
      var cv = document.querySelector('#cv');
      var c = cv.getContext('2d');
      c.fillStyle = '#0FF';
      // 透明度を0.1、0.2...0.9と変化させた上で、それぞれの状態を保存(1)
      for (var i = 0; i < 10; i++) {
        c.globalAlpha = 0.1 * (i + 1);
        c.save();	// コンテキストを保存
      }
      // 保存した状態を取り出し、座標をずらしながら四角形を描画(2)
      for (var i = 0; i < 10; i++) {
        c.restore();
        c.fillRect(50 + (i * 25), 30 + (i * 25), 50, 30);
      }
    }
  }
);
図2:透明度が徐々に変化する10個の四角形を描画(クリックで拡大)

まず、コンテキストの状態を保存しているのは(1)のforブロックです。ここでは、透明度を0.1~0.9の範囲で0.1ずつ変化させながら、その状態をsaveメソッドで保存しています。saveメソッドは、繰り返し呼び出すと、その時の状態をスタックのように保存します。つまり、この場合であれば、透明度の高いものから低いものへと順に保存しているわけです。

saveメソッドで維持できる設定(プロパティ)は、以下のとおりです。

表1:saveメソッドで保存できるプロパティ

分類 プロパティ
基本 strokeStyle/fillStyle/globalAlpha
枠線 lineWidth/lineCap/lineJoin/miterLimit
shadowOffsetX/shadowOffsetY/shadowBlur/shadowColor
テキスト font/textAlign/textBaseline
その他 globalCompositeOperation

保存した状態は、restoreメソッドで復元できます(2)。この際、状態は保存の新しいものから取り出される点に注意してください。つまり、サンプルであれば透明度の低いものがあとに保存されていますので、透明度の低いものから高いものへと順に復元されます。

結果、四角形が左上→右下にだんだんと薄くなっていくような効果を表現できるのです。

TIPS 030:画像を拡大/回転/変形して表示する

Canvas APIでは、scale(拡大縮小)、rotate(回転)、translate(移動)、transform(変形)など、図形を変形/加工するためのさまざまなメソッドが用意されています。以下では、これらのメソッドを利用した例を見てみましょう。

[リスト03]画像を拡大/回転/変形するコード(transform.html)

window.addEventListener('DOMContentLoaded',
  function() {
    if (HTMLCanvasElement) {
      var cv = document.querySelector('#cv');
      var c = cv.getContext('2d');
      // 初期状態のコンテキストをあらかじめ保存
      for (var i = 0; i < 3; i++) { c.save(); }
      // 画像を準備
      var img = new Image();
      img.src = 'wings.jpg';
      img.addEventListener('load', function(e) {
        // 半分に縮小して描画
        c.scale(0.5, 0.5);
        c.drawImage(img, 0, 0);
 
        // 30°回転したものを描画
        c.restore();
        c.rotate(30 * Math.PI / 180);
        c.drawImage(img, 50, 50);
 
        // (180, 200)ずらした上で描画
        c.restore();
        c.translate(180, 200);
        c.drawImage(img, 0, 0);
 
        // 変形マトリクスで変換した上で描画
        c.restore();
        c.transform(1, 1, 1, -1, 0, 0);
        c.drawImage(img, 70, 100);
      });
    }
  }
);
図3:画像を加工した上で貼り付け(クリックで拡大)

それぞれの構文を見てみましょう。

[構文]scale/rotate/translate/transformメソッド

  • scale(横倍率, 縦倍率) … 拡大/縮小
  • rotate(角度) … 回転
  • translate(水平方向, 垂直方向) … 移動
  • transform(m11, m12, m21, m22, dx, dy) … 変形

transformメソッドは、scale/rotate/translateメソッドをひとつにまとめたメソッドと言っても良いでしょう。以下のような変換マトリクスを使って、図形を変形させます。

図4:図形を変形させる変換マトリクス(クリックで拡大)

単純な拡大/縮小、回転、移動には専用のメソッドの方が簡単ですが、より複雑な座標変換には、transformメソッドを利用します。以下に簡単な利用パターンをまとめます。

表2:transformメソッドの利用例

概要
transform(x, 0, 0, y, 0, 0) 横x倍、縦y倍に拡大
transform(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0) r(ラジアン)だけ回転
transform(1, 0, 0, 1, x, y) 水平方向にx、垂直方向にyだけ移動

なお、transformメソッドは、実行都度に変換マトリクスを累積していきます。

もしも新たに変換マトリクスを適用したい(または、状態を初期化したい)という場合には、setTransformメソッドを利用してください。setTransformメソッドは、変換マトリクスを累積するのではなく、新たに設定し直します。

サンプルでは、キャンバス状態の初期化にrestoreメソッドを利用していますが、代わりにsetTransformメソッドを使って、以下のように書いても良いでしょう。

  c.setTransform(1, 0, 0, 1, 0, 0);

TIPS 031:キャンバス上の図形の一部を切り出す/貼り付ける

getImageDataメソッドを利用すると、キャンバス上の特定の領域を切り出すことができます。また、切り出した画像はputImageDataメソッドを利用することで、同じキャンバス(または異なるキャンバス)に貼り付けることができます。

以下は、上のキャンバスに描画した画像の一部を切り出し、下のキャンバスに貼り付ける例です。

[リスト04]キャンバスに描画された図形の一部をコピーするコード(imageData.html)

window.addEventListener('DOMContentLoaded',
  function() {
    if (HTMLCanvasElement) {
      // 上下キャンバスのコンテキストを準備
      var cv = document.querySelector('#cv');
      var c = cv.getContext('2d');
      var cv2 = document.querySelector('#cv2');
      var c2 = cv2.getContext('2d');
      // 上のキャンバスに画像を貼り付け
      var img = new Image();
      img.src = 'rin.jpg';
      img.addEventListener('load', function(e) {
        c.drawImage(img, 0, 0, 400, 300);
        // 座標(180, 150)から150×150の大きさで画像を切り出し
        var d  = c.getImageData(180, 150, 150, 150);
        // 下のキャンバス(50, 50)の位置に切り出した画像を貼り付け
        c2.putImageData(d, 50, 50);
      });
    }
  }
);
...中略...
<canvas id="cv" width="400" height="300">
Canvas機能に対応したブラウザでアクセスしてください。
</canvas><br />
<canvas id="cv2" width="400" height="300">
Canvas機能に対応したブラウザでアクセスしてください。
</canvas>
図5:上キャンバスの一部を下キャンバスにコピー(クリックで拡大)

getImageDataメソッドの構文は、以下のとおりです。

[構文]getImageDataメソッド

  • getImageData(x, y, width, height)

これで座標(x, y)を基点としてwidth×heightのサイズで画像データを取得しなさいという意味になります。getImageDataメソッドの戻り値は、ImageDataオブジェクトです。

ImageDataオブジェクトは、そのままputImageDataメソッドに渡せます。

[構文]putImageDataメソッド

  • putImageData(ImageDataオブジェクト, x, y)

指定されたImageDataオブジェクトを、座標(x, y)に貼り付けます。

サンプルでは、ImageDataオブジェクトをそのまま貼り付けているだけですが、ImageDataオブジェクトはピクセルデータを配列として保持しています。

図6:画像の色味や透明度を変化させて貼り付けることができる(クリックで拡大)

ひとつのピクセルを赤、緑、青、不透明度という4つの要素で表現しているわけです。これらの値を操作することで、色味や透明度を変化させた上で、画像を貼り付けるということもできるでしょう。

TIPS 032:マウスポインタの座標に応じて図形を描画する

マウスイベントやタッチイベントを利用することで、ユーザーの操作に応じてキャンバスを操作することもできます。以下は、マウスポインタの動きに応じてキャンバス上に曲線を描く、簡易な落書き帳アプリの例です。

[リスト05]マウスポインタの動きに応じて曲線を描画するコード(pointer.html)

window.addEventListener('DOMContentLoaded',
  function() {
    if (HTMLCanvasElement) {
      // ひとつ前のマウス座標(o_x、o_y)、マウスボタンが押されているか(flag)
      var o_x = 0, o_y = 0, flag = false;
      var cv = document.querySelector('#cv');
      var c = cv.getContext('2d');
 
      // 現在のマウス座標を求めるcoords関数(1)
      var coords = function(e) {
        var r = e.target.getBoundingClientRect();
        return { x: e.clientX - r.left, y: e.clientY - r.top};
      };
 
      // キャンバスでマウスボタンが押下された場合の処理(2)
      cv.addEventListener('mousedown', function(e) {
        // マウスボタンが押された(フラグをオン)
        flag = true;
        // 現在のマウス位置を変数o_x、o_yに保存
        var cd = coords(e);
        o_x = cd.x;
        o_y = cd.y;
      }, false);
 
      // キャンバスでマウスボタンが離された場合の処理(3)
      cv.addEventListener('mouseup', function(e) {
        // マウスボタンを離した(フラグをオフ)
        flag = false;
      }, false);
 
      // キャンバスでマウスポインタが移動した時の処理(4)
      cv.addEventListener('mousemove', function(e) {
        // マウスボタンが押されている場合のみ処理
        if (flag) {
          // 現在の座標を変数x, yに設定
          var cd = coords(e);
          x = cd.x;
          y = cd.y;
          // (o_x, o_y)~(x, y)の直線を描画
          c.beginPath();
          c.moveTo(o_x, o_y);
          c.lineTo(x, y);
          c.stroke();
          // 現在の作業を変数o_x、o_yに退避
          o_x = x;
          o_y = y;
        }
      }, false);
    }
  }
);
図7:マウス操作に合わせてキャンバス上に曲線を描画(クリックで拡大)

マウス操作に合わせて曲線を描画するには、以下のようなしくみを準備しています。

  • (a)マウスボタン押下時に現在の座標を記録
  • (b)マウスポインタ移動時に、(a)で記録した基点から現在の座標位置まで直線を描画
  • (c)さらに移動したら、(b)の位置から現在位置まで直線を描画
  • (d)(b)~(c)を繰り返す

要は、内部的には細かな直線の集合として、疑似的に曲線を表現しているわけです。これを念頭に、サンプルの細かなコードを追っていきます。

まず、マウスボタンを押した時の処理です(2)。この時の座標を変数o_x、o_yに記録しておきます。変数flagは、マウスボタンが押された状態であるかを管理するためのフラグです。マウスボタンが押された時にフラグをオン(true)とし、離した時にオフ(false)とすることで(3)、「マウスボタンが押されている時だけ曲線を描画」できるようになります。こうしたフラグ管理をしていないと、マウスボタンを離した状態でポインタを動かしただけでも、キャンバスに曲線が描かれてしまうので要注意です。

なお、現在のマウス座標を取得する際には、やや注意が必要です。というのも、イベントオブジェクトeのclientX、clientYプロパティで取得できるのは、ページ左上からの座標なので、キャンバスの上/左余白の分だけ座標がずれてしまうのです。そこでキャンバスの左上の座標を取得するのが、e.target.getBoundingClientRectメソッドです。戻り値はオブジェクトで、X座標をleftプロパティ、Y座標をtopプロパティで取得できます。

この値をそれぞれclientX/clientYプロパティから差し引くことで、キャンバス上の正しいマウス座標を取得できるのです。この計算は典型的ですので、サンプルでもcoords関数として設定しています(1)。

そして、(4)が実際に曲線を描画する実処理です。ifブロックでflagのオンオフをチェックし、オンの場合のみ元の座標(o_x, o_y)から現在の座標(x, y)まで直線を描画します。描画のあとは、続けて次の直線をひくために、変数o_x、o_yに現在の座標をセットしておきます。

TIPS 033:キャンバス上の図形をData URL形式で保存する

キャンバスで描画した図形はData URLという形式に変換することで、ファイルやデータベースに保存したり、要素で表示したりすることが可能になります。

例えば、以下はTIPS 032で作成した落書き帳の内容をData URL形式に変換し、タグで再描画する例です。

[リスト06]落書き帳の内容を保存するコード(pointer.html)

window.addEventListener('DOMContentLoaded',
  function() {
    if (HTMLCanvasElement) {
      ...中略...
      // [コピー]ボタンクリック時の処理を定義
      document.querySelector('#save').addEventListener('click', function(e) {
  
        // キャンバスの内容をもとに画像を生成
        var img = new Image();
        img.src = cv.toDataURL();  // Data URL形式の取得(1)
        // 画像が生成できたら、ページ末尾にImageオブジェクトを貼り付け
        img.addEventListener('load', function(e) {
          document.body.appendChild(img);
        });
      }, false);
    }
  }
);
...中略...
<canvas id="cv" width="400" height="300">
Canvas機能に対応したブラウザでアクセスしてください。
</canvas>
<input id="save" type="button" value="コピー" />
図8:[コピー]ボタンをクリックすると、キャンバスの下に同じ画像が表示(クリックで拡大)

Data URL形式とは、URLに直接、画像や音声などのデータを埋め込むための表現で、一般的には以下のように表せます。

data:コンテンツタイプ;base64,データ本体

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEsCAYAAADtt+...

Data URL形式のデータは、要素のsrc属性、要素のhref属性にそのまま埋め込むことができますので、データをいちいちファイルに落とし込む必要がないのが特長です。

キャンバス上の図形をData URL形式に変換するには、toDataURLメソッドを呼び出すだけです(1)。取得したData URL形式のデータは、そのままImageオブジェクトのsrcプロパティにセットし、ページに追加できます。

サンプルを実行し、ページ下の画像がブラウザの保存機能で画像ファイルとしても保存できることを確認してみましょう。

  • Canvas APIを使ってブラウザに図形を描画するサンプル(3)

著者
山田 祥寛(YAMADA, Yoshihiro)
WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表:山田祥寛)。おもな活動は、Web開発分野の書籍/雑誌/Web記事の執筆。ほかに海外記事の翻訳、講演なども幅広く手がける。2011年3月時点での登録メンバは36名で、現在もプロジェクトメンバーを募集中。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています