HTML Canvasのインターフェイスのまとめ #2

HTMLのCanvasを触れ始めたので備忘録として記事に残しておきます。
本記事で2記事目となります（本記事でも触れきれないインターフェイスが多くあるので恐らく次の記事も執筆します）。前記事で触れた内容は割愛するので必要に応じて前記事もご確認ください。

前記事:

https://qiita.com/simonritchie/items/36e34bf409b6bd10f23a


# 描画系インターフェイス（追加分）

前記事で触れられなかった各描画系のインターフェイスについて以降の節で触れていきます。

## beginPathメソッド

beginPathは描画のためのパスを初期化します。初期化しなくとも単純な描画であれば出来るので使用は必須というわけではありませんが、beginPathを使わないと対応が面倒なことになったり厳しいケースがあります。

どのような時に役立つのか・・・のサンプルとして、以下のようなコードを考えてみます。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.fillStyle = "#0af";
        context.rect(50, 50, 50, 50);
        context.rect(150, 50, 50, 50);
        context.rect(250, 50, 50, 50);
        context.fill();
    </script>
</html>
```

内容としてはシンプルで、シアンの塗りの色をfillStyleで設定して3つ分の四角のパスをrectメソッドで設定し、最後にfillメソッドで描画を行っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/8963078c-cfd2-8ade-9c09-e679e6567035.png)

このような処理で各四角ごとに色を変えたいケースを考えてみます。fillRectを使えば解決できたりしますが、rectなどのパス設定を前提としたインターフェイスで考えてみます（fillRect的なインターフェイスの存在しないケースも多いので）。

試しにrectメソッドの前にそれぞれfillStyleで色を設定してみると想定した動きにはなりません（各四角が全て最後のfillStyle指定の色になってしまいます）。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.fillStyle = "#0af";
        context.rect(50, 50, 50, 50);

        context.fillStyle = "#f0a";
        context.rect(150, 50, 50, 50);

        context.fillStyle = "#fa0";
        context.rect(250, 50, 50, 50);
        context.fill();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/87b4f9a1-fb53-c1bb-23fb-7d9f8d803b20.png)

これはrectメソッドは四角のパス情報の設定のみで塗りの設定が反映されないことに起因します。fillメソッドを呼び出した時点で設定されているパス情報に対して塗りが設定されて描画されるため、そのタイミングではfillStyleの設定は上書きされて最後の設定のみが残るためです。

では都度fillメソッドを使えば良いか？ということでコードにfillメソッドを追加する形に調整してみます。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.fillStyle = "#0af";
        context.rect(50, 50, 50, 50);
        context.fill();

        context.fillStyle = "#f0a";
        context.rect(150, 50, 50, 50);
        context.fill();

        context.fillStyle = "#fa0";
        context.rect(250, 50, 50, 50);
        context.fill();
    </script>
</html>
```

こちらでも結果は変わらず最後のfillStyleでの色設定の四角のみが残ってしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/25eaf99d-0231-ec03-38dd-05d5af42eba8.png)

これは各色での描画は行われるのですが、パス情報は後のfillのタイミングでも過去のものが残った状態となる（fillメソッドの度にリセットされない）ため、最後のfillメソッドのタイミングでは3つ分の四角のパスが残った形となるため3つ分の四角の描画が実行され、結果的に描画が上書きされてしまい最後の塗りの色のみが（見かけ上）残るために発生します。

こういったケースを避けてfill毎にパスを分けたい場合にbeginPathが役立ちます。beginPathメソッドを呼び出すことでパス情報は初期化（リセット）されるため、fillの度にパスを分けるといった制御が可能になります。

以下の例ではbeginPathメソッドの呼び出しを追加しています。四角がそれぞれ別の色になっていることが確認できます。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.fillStyle = "#0af";
        context.rect(50, 50, 50, 50);
        context.fill();

        context.beginPath();
        context.fillStyle = "#f0a";
        context.rect(150, 50, 50, 50);
        context.fill();

        context.beginPath();
        context.fillStyle = "#fa0";
        context.rect(250, 50, 50, 50);
        context.fill();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ee050f1b-d364-6f9a-e50e-9a72b1c97283.png)

## closePathメソッド

closePathメソッドはパスの始点と終点を接続します。moveToやlineToなどを使って任意の図形を描画した際に終点から始点を追加で結ぶlineToの記述を省略することができます（他のパス描画関係のインターフェイスでも同様です）。

例えば以下のようなコードを考えてみます。3点分の座標を指定して線を描画しています。これだけだと結果は終点と始点は接続されないので三角形にはなりません。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.strokeStyle = "#0af";
        context.lineWidth = 5;
        context.moveTo(50, 100);
        context.lineTo(75, 50);
        context.lineTo(100, 100);
        context.stroke();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/607509e7-bee5-10ed-5f6f-4fae3251a704.png)

追加で`context.lineTo(50, 100);`という記述を追加して終点と始点を繋ぐことで三角形にすることはできます。しかし最初の座標と同じ値を再度指定するのは少し手間です。最初の座標が変わった際に同じ値に更新しないといけないため更新が漏れたり変数の定義が必要になったりもします。

そういった場合にclosePathメソッドを使うと終点と始点を座標値の指定無しに結んでくれます。以下のコードではstrokeメソッドの直前にclosePathメソッドの呼び出しを追加しています。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.strokeStyle = "#0af";
        context.lineWidth = 5;
        context.moveTo(50, 100);
        context.lineTo(75, 50);
        context.lineTo(100, 100);
        context.closePath();
        context.stroke();
    </script>
</html>
```

結果が三角形になったことを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/cbcb17b2-8c62-f592-8e9a-15bb5a6ad865.png)

## quadraticCurveToメソッド

quadraticCurveToメソッドは2次元のベジェ曲線（制御点が1つのみのベジェ曲線）による曲線を描画します。

第一引数に制御点のX座標、第二引数に制御点のY座標、第三引数に終点のX座標、第四引数に終点のY座標の指定が必要です。

以下のコード例では線の水平方向に中央、垂直方向に上の方に制御点を設定しています。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.strokeStyle = "#0af";
        context.lineWidth = 5;
        context.moveTo(50, 100);
        context.quadraticCurveTo(100, 0, 150, 100);
        context.stroke();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/94e0c0b2-5373-f8a6-f5aa-355803de33ee.png)

## bezierCurveToメソッド

bezierCurveToメソッドは3次元のベジェ曲線（制御点が2つあるベジェ曲線）による曲線を描画します。

第一引数には1つ目の制御点のX座標、第二引数には1つ目の制御点のY座標、第三引数には2つ目の制御点のX座標、第四引数には2つ目の制御点のY座標、第五引数には終点のX座標、第六引数には終点のY座標が必要になります。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.strokeStyle = "#0af";
        context.lineWidth = 5;
        context.moveTo(50, 50);
        context.bezierCurveTo(50, 150, 150, 0, 150, 100);
        context.stroke();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b4449488-f5ce-248c-3f80-f21d1b54bf20.png)

## arcToメソッド

arcToメソッドではベジェ曲線に少し近い感じですが2つの制御点と円弧の半径を指定して曲線を描画することができます。角丸的な表現をする時に便利かもしれません。

第一引数には1つ目の制御点のX座標、第二引数には1つ目の制御点のY座標、第三引数には2つ目の制御点のX座標、第四引数には2つ目の制御点のY座標、第五引数には円弧の半径の指定が必要になります。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.strokeStyle = "#0af";
        context.lineWidth = 5;
        context.moveTo(50, 50);
        context.arcTo(50, 150, 150, 150, 100);
        context.stroke();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/e144cd4f-4400-16cb-516b-a5eff7d1d8eb.png)

ここで記述した円弧は他のパスを繋げることが容易なため角丸表現などに向いています。例えば以下の例ではlineToの記述を追加しており、角丸的な表現からさらに線を繋げています。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.strokeStyle = "#0af";
        context.lineWidth = 5;
        context.moveTo(50, 50);
        context.arcTo(50, 150, 150, 150, 100);
        context.lineTo(250, 150);
        context.stroke();
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/24b77c4a-de19-5132-23ae-11cd16ae4dc7.png)


## clipメソッドで部分的に描画を削除する

clipメソッドは描画の切り抜き的な表現を行うことができます。クリッピングマスクのような制御を行うことができます。

clipの前にパスを記述し、その後にclipメソッドを呼び出すことで後に続く描画はclipメソッド前で指定していたパス領域のみに限定されます。描画処理の前にclipメソッドを設定する必要があります。

以下の例では事前に2つの小さな四角のパス領域をrectメソッドでclip用に設定しています。その後fillRectメソッドで大きな四角を1つ描画していますが、右上と左下部分はclipでのパスに指定されていないため表示されなくなります。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.rect(50, 50, 50, 50);
        context.rect(100, 100, 50, 50);
        context.clip();

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 100, 100);

    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/5426e751-610f-9f14-858f-831bf2644918.png)


# 影の設定

影の設定は`shadowColor`、`shadowOffsetX`、`shadowOffsetY`、`shadowBlur`の各属性を使います（インナーシャドウ的な表現をしたい場合には`globalCompositeOperation`や`clip`なども使う時があります）。

影の設定はCanvas全体ではなく各描画領域のみに対して反映されます。

※各属性の詳細は後の節で触れます。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = '#00000077';
        context.shadowOffsetX = 10;
        context.shadowOffsetY = 10;
        context.shadowBlur = 5;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);

        context.fillRect(100, 100, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/e9309f8c-1107-76ed-f5e8-673ddc1e4ee0.png)

## shadowColor設定

shadowColor属性では影の色を設定します。基本的には黒系の色、且つ半透明の色などを使う場合が多いと思いますので16進数であれば`#0000007f`のように最後の透明度指定を含む形とするか、もしくは`rgba(0, 0, 0, 0.5)`みたいなRGBA表記など透明度を含む指定が必要になります。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = 'rgba(0, 0, 0, 0.5)';
        context.shadowOffsetX = 10;
        context.shadowOffsetY = 10;
        context.shadowBlur = 5;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/17e4ac39-fb18-f23a-ca54-65fbf61edd72.png)

## shadowBlur設定

shadowBlur属性の設定では影のぼかし（ブラー）の強さを設定できます。小さい数字の方が強くくっきりとした影になり、数字が大きくなると優しく広範囲にぼける形になります。

`shadowBlur = 5`の例:

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = '#0000007f';
        context.shadowOffsetX = 10;
        context.shadowOffsetY = 10;
        context.shadowBlur = 5;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/a76933f2-aa2a-c337-518f-670f6bdd2b26.png)

`shadowBlur = 15`の例:

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = '#0000007f';
        context.shadowOffsetX = 10;
        context.shadowOffsetY = 10;
        context.shadowBlur = 15;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/e626b738-55c2-f8ca-50ff-82d51fd67939.png)

## shadowOffsetXとshadowOffsetYの設定

shadowOffsetXとshadowOffsetYの属性はそれぞれXとY軸方向の影の設定するオフセット（距離）の指定になります。正の値を設定すれば右下に影が付きます。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = '#0000007f';
        context.shadowOffsetX = 5;
        context.shadowOffsetY = 5;
        context.shadowBlur = 10;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/2a919810-c1c8-a2ff-2281-5f2281c17942.png)

もしそれぞれのオフセットに0を指定した場合には描画の周囲に影が付く形になります（デザインとかでは良く使われる設定かなと）。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = '#00000099';
        context.shadowOffsetX = 0;
        context.shadowOffsetY = 0;
        context.shadowBlur = 10;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/6b476604-7f10-88a2-bae6-1a1dcff8aac9.png)

負の値をオフセットに設定することもできます。その場合左上方向に影を付けることができます。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.shadowColor = '#00000099';
        context.shadowOffsetX = -5;
        context.shadowOffsetY = -5;
        context.shadowBlur = 10;

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/a69173c1-e032-b950-d298-07ab1aaa196b.png)

# 描画領域の画像データや描画設定の取得と設定

以降の節では描画データや描画設定の取得や設定（復元）などのインターフェイスについて触れていきます。

## getImageDataとputImageDataメソッド

getImageDataとputImageDataメソッドではそれぞれCanvas内の特定領域の画像データの取得と設定を扱うことができます。

getImageDataでは画像データの取得を行えます。第一引数には画像データ領域の開始位置のX座標、第二引数には画像データ領域の開始位置のY座標、第三引数には画像幅、第四引数には画像の高さの指定が必要になります。`(0, 0, canvas.width, canvas.height)`といった引数指定をすればCanvasの左上からCanvas全体に対する画像データの取得となります（canvas.widthなどの属性でCanvasのサイズが取れます）。

putImageDataでは第一引数に設定する画像データ、第二引数に設定開始位置のX座標、第三引数に設定開始位置のY座標ウ指定します。幅や高さは指定される画像データに依存します。

以下の例ではgetImageDataメソッドでCanvasの画像データを取得した後にclearRectメソッドでCanvas全体の描画を削除しています。これだけだと何も描写されなくなりますが、その後にputImageDataメソッドで画像データを再設定しているため再び四角が描画された状態に戻ります。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);

        let imageData = context.getImageData(
            0, 0, canvas.width, canvas.height);
        context.clearRect(0, 0, canvas.width, canvas.height);

        context.putImageData(imageData, 0, 0);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/57802387-127c-c1ee-b6f2-2c2a7cd31eed.png)

## saveとrestoreメソッド

コンテキストは設定値を保持するため後に続く描画処理ではその設定値が引き継がれます。この性質が便利なこともあれば不便なこともあります。例えば全然違う図形を描画するためにがらっと描画設定を変更した際に、再び別の画像を描画したくなって設定を戻したい・・・といった場合に設定が上書きされてしまっていて不便なことがあります。

そのような場合にはsaveメソッドとrestoreメソッドで解決することができます。

saveメソッドはコンテキストへ指定されている設定値（塗りの描画設定など）の（スナップショットの）保存を行います。restoreメソッドは保存されている設定値を復元します。それぞれ引数は指定せずに使うことができます。

以下の例では1つ目の四角はシアンの塗りの色を設定してます。その後2つ目の四角では一旦シアンの塗り設定をsaveメソッドで保存してからマゼンタの塗りの色を設定しています。3つ目の四角では設定をrestoreメソッドで復元してから四角を描画しています。3つ目の四角では塗りの設定はしていないものの設定が復元されてシアンの色に戻っています。

```html
<html>
    <body>
        <canvas id="canvas" width="500" height="350">
        </canvas>
    </body>

    <script type="text/javascript">
        let canvas = document.getElementById("canvas");
        let context = canvas.getContext("2d");

        context.fillStyle = "#0af";
        context.fillRect(50, 50, 50, 50);

        context.save();
        context.fillStyle = "#f0a";
        context.fillRect(150, 50, 50, 50);

        context.restore();
        context.fillRect(250, 50, 50, 50);
    </script>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ecdaeafa-fd1a-056a-4676-607ee384dd0a.png)

# 参考文献・参考サイト

https://www.amazon.co.jp/dp/B0085TJYKU/

https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/beginPath

https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/closePath

https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/arcTo

https://riptutorial.com/html5-canvas/example/19767/inner-shadows-with--source-atop-