【入門者向け】Canvas入門講座#13 グレースケールしてみよう【JavaScript】

#問題13
問題13～15は問題9がベースとなっています。まず問題9を実装してください。

画像読み込み後、gray scale押下時にグレースケール処理をせよ。
グレースケールとは以下の式で表せる。
Gray = 0.299 * Red + 0.587 * Green + 0.114 * Blue;

以下のHTMLを使用すること。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題13</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {            
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#gray-button').click(e => {        
    });
    
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="gray-button" type="button" value="gray scale" />
</body>
</html>
```

#解答

問題9の分の回答も併せて貼っておきます。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題13</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {
            // コンテキストを取得
            const ctx = $('#my-canvas')[0].getContext('2d');

            // canvasを黒色で塗りつぶす
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);            

            // canvasと画像のアスペクト比を求める
            const canvasWidth = $('#my-canvas').prop('width'),
                canvasHeight = $('#my-canvas').prop('height');
                canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
                imgAspect = img.width / img.height; // imgのアスペクト比
            
            // canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
            let dstWidth, dstHeight;
            if(canvasAspect > imgAspect) {// canvasの方が横長
                dstHeight = canvasHeight;
                dstWidth = dstHeight * imgAspect;               
            } else {// canvasの方が縦長
                dstWidth = canvasWidth;
                dstHeight = dstWidth / imgAspect;
            }

            // 画像を貼り付ける
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#gray-button').click(e => {
        const canvas = $('#my-canvas')[0],
            ctx = canvas.getContext('2d');

        ctx.save();

        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
            data = imgData.data;

        for(let p = 0; p < data.length; p += 4) {
            // rgbを求める
            const red = data[p],
                green = data[p + 1],
                blue = data[p + 2],
                // グレースケールの値を求める
                gray = 0.299 * red + 0.587 * green + 0.114 * blue;

            // グレースケール化する(グレーで塗りつぶす)
            for (let i = 0; i < 3; i += 1) {
                data[p + i] = gray;
            }
        }

        // imageDataをcanvasに貼り付ける
        ctx.putImageData(imgData, 0, 0);

        ctx.restore();
    });
    
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="gray-button" type="button" value="gray scale" />
</body>
</html>
```

グレースケールできました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/110e24f0-72bc-ed52-0e44-b69e7c7fe918.png)


#解説
問題13から問題15まで、画像処理をやっていきます。
今回はグレースケールです。
問題文にもある通り式はGray = 0.299 * Red + 0.587 * Green + 0.114 * Blue;です。

いままで、線や円を描いたりしてきましたが、
画像処理はそのような処理とは異なり基本的に全ピクセルに対して処理を行います。

まずは問題9同様画像を貼り付けます。(問題9参照)
画像処理を行う準備をしましょう。
getImageDataメソッドでcanvasのピクセルデータを取得します。
実際の画像データはimgData.dataです。

```js
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
    data = imgData.data;
```

for文で全ピクセルを処理します。
各画素の赤、緑、青成分を取得しましょう。
そのあと、グレーの値を式に倣って計算して、その色で、赤緑青成分を変更します。
ちなみにdata.length === 500*300*4です。画素数x4の大きさになっています。
これは画素ごとにrgba(aはアルファ値で透明度)を保持しているためです。
for文のインクリメントがp += 4になっていますがこの4も画素毎にrgbaの4つ分保持しているためです。

```js
for(let p = 0; p < data.length; p += 4) {
    // rgbを求める
    const red = data[p],
        green = data[p + 1],
        blue = data[p + 2],
        // グレースケールの値を求める
        gray = 0.299 * red + 0.587 * green + 0.114 * blue;

    // グレースケール化する(グレーで塗りつぶす)
    for (let i = 0; i < 3; i += 1) {
       data[p + i] = gray;
    }
}
```

最後にimageDataをputImageDataメソッドでcanvasに貼り付けます。

```js
// imageDataをcanvasに貼り付ける
ctx.putImageData(imgData, 0, 0);
```

#画像処理の基本的な流れ
1: getImageDataで画素データを取得する
2: 画素データを加工する
3: putImageDataで画像データを貼り付ける