【JavaScript】Canvasの基本【HTML5】

#はじめに
昔自分で書いた<a href="http://jstech.web.fc2.com/js0020/index.html">ブログ「Canvasメモ」</a>の転送です。
6,7年前に書いたので情報古いかもしれません。

#画像を読み込んでcanvas表示するまでの普通の流れ

```js
$(function() {
    var image = new Image();
    image.onload = onload;
    image.src = "image/image1.png"; // 画像パス
     
    function onload() {
        var canvas = document.getElementsByTagName('canvas')[0],
            context, imageData;
             
        // キャンバスサイズを画像サイズに合わせる
        canvas.width = image.width;
        canvas.height = image.height;
         
        // コンテキストを取得
        context = canvas.getContext('2d');
         
        // 画像をコンテキストに描画
        context.drawImage(image, 0, 0);
 
        ///////////////////////////////////
        // 以降は画像を加工する場合
        ///////////////////////////////////
         
        // 画像のバッファを取得(バッファはimageData.dataである)
//      imageData = context.getImageData(0, 0, canvas.width, canvas.height);
         
        // 画像加工プログラムをここに書く(画像にモザイク処理を施すなど)
         
        // 加工した画像を貼り付ける
//      context.putImageData(imageData, 0, 0);
    }
});
```

#画像のバッファについて
1pixelの色情報を保持するのに1byte(0～255)×4を必要とする。
4byteはRGBAに対応している。
例えば、1,000×1,000の画像のメモリ使用量は、4×1,000×1,000byte(約4MB)である。

画像の原点は左上であり、 幅がw, 高さがhの画像の(m,n)のピクセルの色情報は、
画像バッファをbufferとすると
R: buffer[(n * w + m) * 4]
G: buffer[(n * w + m) * 4 + 1]
B: buffer[(n * w + m) * 4 + 2]
A: buffer[(n * w + m) * 4 + 3]
である。
直線や曲線を描画するAPIは存在するが、それ以外の画像処理を行う場合
画像バッファを直接触ることになる

![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/4f969994-5d13-97f1-2e4a-53b6e30c76df.png)

#canvasのwidthとheightについて

```html
<canvas width="480" height="320"></canvas>
```

canvas.width,canvas.heightは画像のバッファサイズである。
canvas.style.width,canvas.style.heightは見た目のサイズである。
よくわからない場合はcanvas.width,canvas.heightのみ設定すると良い。
尚、canvas.width,canvas.heightにはpxは不要である。

#context.drawImage()の引数について
引数は３パターンあり、よく忘れるためメモしておく。

```js
context.drawImage(image, dx, dy)
context.drawImage(image, dx, dy, dw, dh)
context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
```

#context.getImageData()について
getとあるが、この関数は呼ばれる度にバッファを作成している。
その為、本メソッドは遅い。
また、戻り値をそのまま比較することは意味がない。

```js
context.getImageData(0, 0, 100, 100) === context.getImageData(0, 0, 100, 100) // false 
```

#ImageDataについて

```cpp
[Constructor(unsigned long sw, unsigned long sh),
 Constructor(Uint8ClampedArray data, unsigned long sw, optional unsigned long sh),
 Exposed=(Window,Worker)]
interface ImageData {
  readonly attribute unsigned long width;
  readonly attribute unsigned long height;
  readonly attribute Uint8ClampedArray data;
};
```

プロパティ全て(width,height,data)がreadonlyである為、バッファを保持するためには
ImageDataまるごと保持しなくてはならない。
dataもreadonlyであるが、配列の参照を変更できないのであって、配列の中身は変更して構わない。

###※ImageDataのコンストラクタは使えない。
以下のように定義すれば、第一引数がUint8ClampedArrayの時のheightがoptionalを除けば使える。
今のところはcontextからcreateImageDataするしかなさそうだ。

```js
function ImageData() {
    var i = 0;
    if(arguments[0] instanceof Uint8ClampedArray) {
        var data = arguments[i++];
    }
    var width = arguments[i++];
    var height = arguments[i];      
 
    var canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    var ctx = canvas.getContext('2d');
    var imageData = ctx.createImageData(width, height);
    if(data) imageData.data.set(data);
    return imageData;
}
var imgData1 = new ImageData(data, width, height);
var imgData2 = new ImageData(width, height);
```

#drawImage と putImageDataの速度比較
以下のプログラムをで測定を実施。
drawImage()速度測定プログラム

```js
var startTime = new Date();
for(var i = 0; i < 500; i += 1) {
    context.drawImage(image, 0, 0);
}
var endTime = new Date();
$('#timeSpan').html("context.drawImage is " + (endTime - startTime) + ' ms');
```

putImageData()速度測定プログラム

```js
var startTime = new Date();
for(var i = 0; i < 500; i += 1) {
    context.putImageData(imageData, 0, 0);
}
var endTime = new Date();
$('#timeSpan').html("context.putImageData is " + (endTime - startTime) + ' ms');
```
結果(500回の実行にかかった時間):
drawImage: 10ms
putImageData: 1900ms
drawImageが想像以上に速い。putImageDataが結構遅い。

#Uint8Array と Uint8ClampedArrayの違い
0から255の範囲外の任意の値をクランプされた配列に1要素を設定しようとしている場合、
それは単に（値が小さいか大きいかどうかに依存します）0または255にデフォルト設定されます。
通常のUint8Arrayはちょうど取る値の最初の8ビットが設定されます。

```js
var x = new Uint8ClampedArray([17, -45.3]);
console.log(x[0]); // 17
console.log(x[1]); // 0
console.log(x.length); // 2
 
var x = new Uint8Array([17, -45.3]);
console.log(x[0]); // 17
console.log(x[1]); // 211
console.log(x.length); // 2
```