【入門者向け】Canvas入門講座#9 画像を描画しよう【JavaScript】

#問題9

画像が読み込まれたときに、その画像をアスペクト比を保ったまま
画像全体が見えるようにcanvasに貼り付けなさい。
画像の左上隅はcanvasの左上隅に一致させること。
画像が貼り付けられていない箇所は黒色(#000000)で塗りつぶすこと。
読み込んだファイルのチェックは行わないものとする。
以下のHTMLを使うこと。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題9</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]);
    });
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
</body>
</html>
```

#補足
canvasは幅が500,高さが300で固定です。
画像は幅、高さがいくつのものが来るかわかりません。
canvasが画像より横長、縦長の場合を考慮する必要があります。

#答え

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題9</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]);
    });
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
</body>
</html>
```

画像が縦長の場合、横長の場合でもうまく表示できました！

canvasが画像より横長の場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/eff0c442-a039-557d-ee33-1eff0f61637c.png)

canvasが画像より縦長の場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/327389f1-b093-b662-01c2-786ac7ccc6e9.png)


#解説
まずはファイルが読み込まれたイベントをハンドリングします。
changeイベントをハンドリングします。

```js
$('#my-file').on('change', e => {
    // ここにファイルが読み込まれた処理を書く
});
```

次に画像をロードする必要があります。
Imageを作成し、srcに画像のurlを指定します。
読み込みが完了したら、loadイベントが発生するので、それをハンドリングします。

```js
const img = new Image();
$(img).on('load', e => {
     // ここに画像読み込み完了後の処理を書く
});
img.src = URL.createObjectURL(e.target.files[0]);
```

さあ、画像もロードできたのでcanvasに描画しましょう。
いつもどおり、コンテキストを取得します。

```js
// コンテキストを取得
const ctx = $('#my-canvas')[0].getContext('2d');
```

そして、canvasを黒色で塗りつぶしましょう。

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

canvasと画像のアスペクト比を求めましょう。

```js
// canvasと画像のアスペクト比を求める
const canvasWidth = $('#my-canvas').prop('width'),
    canvasHeight = $('#my-canvas').prop('height');
    canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
    imgAspect = img.width / img.height; // imgのアスペクト比
```

canvasに貼り付ける幅と高さを計算しましょう。
canvasが縦長か横長かで場合分けすればうまくいきそうです。

canvasが画像より横長の場合、
貼り付ける高さはcanvasの高さを採用します。
貼り付ける幅は貼り付ける高さに画像のアスペクト比を掛けることで求まります。

canvasが画像より縦長の場合、
貼り付ける幅はcanvasの幅を採用します。
貼り付ける高さは貼り付ける幅を画像のアスペクト比で割ることで求まります。

```js
// canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
let dstWidth, dstHeight;
if(canvasAspect > imgAspect) {// canvasの方が横長
    dstHeight = canvasHeight;
    dstWidth = dstHeight * imgAspect;               
} else {// canvasの方が縦長
    dstWidth = canvasWidth;
    dstHeight = dstWidth / imgAspect;
}
```

あとはcanvasに画像を貼り付けましょう。
<b>drawImage</b>メソッドで貼り付けます。このメソッドは<b>超超重要</b>です。
<b>ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)</b>
image: 画像オブジェクト or canvasオブジェクト or 動画オブジェクト
sx, sy, sw, sh: 貼り付け元の左上隅座標とサイズ
dx, dy, dw, dh: 貼り付け先の左上隅座標とサイズ

今回画像全体を表示するので
sx, sy, sw, shは0, 0, img.width, img.heightです。
画像の左上隅はcanvasの左上隅に一致するので
dx, dy, dw, dhは0, 0, dstWidth, dstHeightです。

```js
// 画像を貼り付ける
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);
```

#最後に
今回の問題はややこしく感じたかもしれませんが、この程度の計算はできるようにしておきましょう。
canvasを扱う時に計算はつきものです。
余裕のあった人は、canvasの中央に画像を貼り付けてみましょう。
これについては答えは示しませんが、以前<a href="https://qiita.com/PG0721/items/599ba2921b8339700fe3">記事</a>を書いたことがあるのでそれが参考になるかもしれません。