Canvasの「ブレンドモード」と「toDataURL」を利用した画像の重なりを判定する処理

iOS/Androidのスマートフォン向けのWebアプリケーションにおいて、
画像を合成した際に合成した画像同士が重なっているかをチェックする必要があり
Canvasを利用すれば出来そうだと思ったため、その時の実装メモとして記載しておきます。

# 前提条件
- 必要なパーツの画像を合成して表示するWebのシステム
   - 着せ替えゲームをイメージして頂ければと...
- 各パーツは全て同じサイズの画像で書き出しがされている
- 各パーツの描画位置については、画像に描画されている位置
   - 左上を基点として画像を合成すれば基本的には綺麗に合成される
- 描画されているパーツ以外の余白部分は透過である

## 画像のイメージ

![Qiita01.png](https://qiita-image-store.s3.amazonaws.com/0/17346/daa2da8e-1a3e-42d7-2445-255f69baa520.png)![Qiita02.png](https://qiita-image-store.s3.amazonaws.com/0/17346/1d0b83d0-5274-d5f3-6842-738d47e4d84d.png)![Qiita03.png](https://qiita-image-store.s3.amazonaws.com/0/17346/6b26b448-6252-c334-563b-6a899b7c25b3.png)



上記のように同じ画像サイズではあるが、
各パーツの表示位置や大きさなどは画像の中に埋め込みで描画されている。
（※ 画像はあくまでイメージです）

このようなパーツの画像を合成し表示するシステムがある。

# やりたいこと
- **合成する各パーツの画像に重なりがあるかどうかをチェックしたい**

## 重なりの判定例

- 合成した際に重なっていない

![Qiita01.png](https://qiita-image-store.s3.amazonaws.com/0/17346/daa2da8e-1a3e-42d7-2445-255f69baa520.png) **+** ![Qiita02.png](https://qiita-image-store.s3.amazonaws.com/0/17346/1d0b83d0-5274-d5f3-6842-738d47e4d84d.png) **=**  ![Qiita01_02.png](https://qiita-image-store.s3.amazonaws.com/0/17346/8190120b-9c7f-c53f-afb8-45282c04e04b.png)

- 合成した際に重なっている

![Qiita02.png](https://qiita-image-store.s3.amazonaws.com/0/17346/1d0b83d0-5274-d5f3-6842-738d47e4d84d.png) **+** ![Qiita03.png](https://qiita-image-store.s3.amazonaws.com/0/17346/6b26b448-6252-c334-563b-6a899b7c25b3.png) **=** ![Qiita02_03.png](https://qiita-image-store.s3.amazonaws.com/0/17346/759b7785-b105-81c4-8fac-43b817c41f50.png)

このように画像同士の重なり判定をcanvasを利用して実施する

# Canvasの「ブレンドモード」

HTML5のcanvasには、「globalCompositeOperation」というブレンドモードを指定するオプションがあり、
canvasに描画する要素同士が重なる部分の描画をどのように扱うのかを決めることが出来ます。

例えば
・「重なる部分だけを描画する」
・「重なった部分は描画しない」
・「色値を加算して描画」など
重なった部分の挙動を決めることが出来ます。

```HTML
<canvas id="js-canvas01" width="200" height="200">
<script>
  var img = new Image();
  img.src = '描画したい画像のURL';
  img.onload = function() {
    var ctx = $('#js-canvas01')[0].getContext('2d');
    ctx.drawImage(this, 0, 0, 200, 200);
    ctx.save();
    // ここでブレンドモードをセットする
    ctx.globalCompositeOperation = 'source-in';  // 重なる部分だけを描画するというモード
    ctx.drawImage(this, 50, 0, 200, 200);        // 同じ画像を50ずらして描画させる
    // すると重なっている部分だけがcanvasに描画されている
  };
</script>
```

描画モードの詳しい説明については、「[[HTML5] Canvasで描画する際に、要素同士が重なる部分のブレンドモードを指定する)](http://www.yoheim.net/blog.php?q=20121206)」の記事が分かりやすいです。

上記記事を参考にし、画像の重なり判定を行うには、
この機能の「重なる部分だけを描画」とすることで出来ると考えました。
ただしこのままだと、視覚的な判断となってしまうため、
プログラム的に判断する方法を次に記載します。


# Canvasの「toDataURL()」

toDataURLメソッドは、canvasに描画されている画像を
Base64エンコードされた形で抜き出す処理です。

クロスオリジンの画像をcanvasに描画している場合で、このメソッドを利用するためには
画像を取得したImageオブジェクトに対して、
クロスオリジンのファイル参照を許可する設定を行っておく必要がある点に気をつけましょう。

```HTML
<canvas id="js-canvas01" width="200" height="200">
<script>
  var img = new Image();
  img.crossOrigin = 'Anonymous';  // クロスオリジンのファイル参照を許可する設定

  img.src = '描画したい画像のURL';
  img.onload = function() {
    var ctx = $('#js-canvas01')[0].getContext('2d');
    ctx.drawImage(this, 0, 0, 200, 200);
    ctx.save();
    ctx.globalCompositeOperation = 'source-in';
    ctx.drawImage(this, 50, 0, 200, 200);
  };
</script>
```

そして取得した画像が合成されて描画されているcanvas要素に対して

```JavaScript
   $('#js-canvas01')[0].toDataURL();
```

とすることで、Base64エンコードされた画像を取得することが可能になります。

この機能を利用して、「何も表示されていない真っさらなcanvas」と「「ブレンドモード」を利用して重なる部分だけを描画したcanvas」のBase64の画像データを比較し、
同じであれば「重なっていない」、差分があれば「重なっている」と判断できると考えました。



# 実装

1. 2つのcanvasを用意する
   - a. 重なりを判定するために描画するcanvas
   - b. 何も描画されていないcanvas
   
      ```index.html
      // a. 重なりを判定するために描画するcanvas
      <canvas id="js-canvas01" width="200" height="200">
      // b. 何も描画しないcanvas
      <canvas id="js-canvas02" width="200" height="200">
      ```
2. aのcanvasにパーツを描画する
   - 重なりを判定するために予め合成用の画像を描画する

      ``` index.js
         var imageUrl = '画像のURL';
         var img = new Image();
         img.crossOrigin = 'Anonymous';
         img.src = imageUrl;
         img.onload = function() {
           var ctx = $('#js-canvas01')[0].getContext('2d');
           ctx.drawImage(this, 0, 0, 200, 200);
           ctx.save();
         }
      ```
3. aのcanvasのブレンドモードを「source-in」とし、重なる領域のみを描画するように設定する

      ``` index.js
         var ctx = $('#js-canvas01')[0].getContext('2d');
         ctx.globalCompositeOperation = 'source-in';
      ```

4. aのcanvasに重なり判定をしたいパーツを描画する

      ``` index.js
         var imageUrl2 = '別の画像のURL';
         var img2 = new Image();
         img2.crossOrigin = 'Anonymous';
         img2.src = imageUrl;
         img2.onload = function() {
           var ctx = $('#js-canvas01')[0].getContext('2d');
           ctx.drawImage(this, 0, 0, 200, 200);
           ctx.save();
         }
      ```
5. aとbのcanvasからtoDataURLメソッドを利用してbase64へ変換し比較処理を行う
   - 同じであれば、2つのパーツ画像は重なっていない
   - 違っていれば、2つのパーツ画像は重なっている

   ``` index.js
      if ($('#js-canvas01')[0].toDataURL() === $('#js-canvas02')[0].toDataURL()) {
         console.log('パーツ画像は重なっていない');
      } else {
         console.log('パーツ画像は重なっている');
      }
   ```

実装内容については、Githubの方にプログラムをアップしておりますので、
興味のある方は一度お試し頂ければと思います。

- Github: Canvasの「ブレンドモード」と「toDataURL」を利用した画像の重なりを判定する処理
   - https://github.com/megadreams14/canvasImageOverlap

# まとめ

このように画像のみで構成されたパーツ情報であってもCanvasの「ブレンドモード」と「toDataURL」を組み合わせて利用することで重なっているかどうかの重なり判定が出来ることがわかりました。

ただし、検証した環境が「MaxOS Sierra(10.12.3)の GoogleChrome（バージョン 58.0.3029.110 (64-bit)）だけであるため、ブラウザ依存の事象が発生したり、
スマートフォン向けだと端末のスペックによっては処理が重くて少し画面がカクカクするといった事象も考えられるため、
本番運用するにはまだまだ検証が必要だと感じております。
（そもそも重なり判定をするのにちゃんとデータ構造見直しましょうようという話なのですが...）


他にも画像を合成する際に重なりを判定する方法などありましたらご指摘頂けますと幸いです。


# 参考
- [W3C, HTML Canvas 2D Context, 4.Compositing](https://www.w3.org/TR/2011/WD-2dcontext-20110405/#dom-context-2d-globalcompositeoperation)
- [[HTML5] Canvasで描画する際に、要素同士が重なる部分のブレンドモードを指定する](http://www.yoheim.net/blog.php?q=20121206)
- [crossorigin=state - CORS指定](http://www.tohoho-web.com/html/attr/crossorigin.htm)
- [CORS を有効にした画像を HTML5 Canvas で使う方法](http://phiary.me/html5-canvas-cors/)