[JavaScript] ImageBitmap について

JavaScript の Canvas で画像を扱うとき、`ImageBitmap` にすることで描画の遅延を減らすことができます。

ただし、`ImageBitmap` はメモリ消費が大きい場合があるため、注意が必要です (※具体的な扱い方は後述) 。

`ImageData` は CORS によって取得できないことがありますが、`ImageBitmap` は取得できます。

参考「[ImageBitmap - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/ImageBitmap)」
参考「[OffscreenCanvas: transferToImageBitmap() method - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/transferToImageBitmap)」

# 1. 生成

## 1.1. `createImageBitmap()` 関数

一般的な画像ソースから `ImageBitmap` を生成するには `createImageBitmap()` 関数を用います。

```js:OffscreenCanvas の場合
const offscreenCanvas = new OffscreenCanvas(300, 150);

// ...

const imageBitmap = await createImageBitmap(offscreenCanvas);
```

```js:HTMLCanvasElement の場合
const canvas = document.createElement('canvas');

// ...

const imageBitmap = await createImageBitmap(canvas);
```

※他にも `createImageBitmap()` 関数が使える型があります。

位置や大きさ等を指定することもできます。

```js:OffscreenCanvas の場合
const offscreenCanvas = new OffscreenCanvas(300, 150);

// ...

const imageBitmap = await createImageBitmap(offscreenCanvas, 100, 0, 100, 75);
```

※他の操作に関しては公式ドキュメント等参照。

参考「[createImageBitmap() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/createImageBitmap)」

## 1.2. `transferToImageBitmap()` メソッド

`OffscreenCanvas` から `ImageBitmap` を取得して `OffscreenCanvas` を初期化するには `transferToImageBitmap()` メソッドを用います。

```js
const offscreenCanvas = new OffscreenCanvas(300, 150);

// ...

const imageBitmap = offscreenCanvas.transferToImageBitmap();
```

参考「[OffscreenCanvas: transferToImageBitmap() method - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/transferToImageBitmap)」

※後述する `transferFromImageBitmap()` メソッドは `transferToImageBitmap()` メソッドと異なり、通常の `HTMLCanvasElement` でも使用可能です。
※ `transferToImageBitmap()` は `OffscreenCanvas` 型のメソッドですが、`transferFromImageBitmap()` は `ImageBitmapRenderingContext` 型のメソッドです。

## 1.3. `createImageBitmap()` 関数と `transferToImageBitmap()` メソッドの違い

`OffscreenCanvas` に関しては `createImageBitmap()` 関数でも `transferToImageBitmap()` メソッドでも `ImageBitmap` を得ることができますが、以下の表のような違いがあります。

||`createImageBitmap()` 関数|`transferToImageBitmap()` メソッド|
|-|-|-|
|画像ソースの型|`HTMLImageElement`<br>`SVGImageElement`<br>`HTMLVideoElement`<br>`HTMLCanvasElement`<br>`Blob`<br>`ImageData`<br>`ImageBitmap`<br>`OffscreenCanvas`|`OffscreenCanvas`|
|画像ソースの扱い|そのまま|初期化|
|位置や大きさ等を指定|可|不可|
|処理方法|非同期|同期|

# 2. 描画および破棄

## 2.1. `transferFromImageBitmap()` メソッド

`HTMLCanvasElement` または `OffscreenCanvas` に `ImageBitmap` 全体を描画して `ImageBitmap` を破棄するには `transferFromImageBitmap()` メソッドを用います。

```js:HTMLCanvasElement の場合
const context = canvas.getContext('bitmaprenderer');

context.transferFromImageBitmap(imageBitmap);
```

```js:OffscreenCanvas の場合
const offscreenContext = offscreenCanvas.getContext('bitmaprenderer');

offscreenContext.transferFromImageBitmap(imageBitmap);
```

ちなみに、Canvas と大きさが一致しない `ImageBitmap` を `transferFromImageBitmap()` メソッドで描画すると内部的には `ImageBitmap` の大きさで扱われますが、Canvas の `width` および `height` は更新されないため注意が必要です。

```js
// 
const offscreenCanvasA = new OffscreenCanvas(300, 300);

const imageBitmapA = await createImageBitmap(offscreenCanvasA);
console.log(imageBitmapA.width, imageBitmapA.height);

// 
const offscreenCanvasB = new OffscreenCanvas(300, 150);
const offscreenContextB = offscreenCanvasB.getContext('bitmaprenderer');

offscreenContextB.transferFromImageBitmap(imageBitmapA);
console.log(offscreenCanvasB.width, offscreenCanvasB.height);

const imageBitmapB = await createImageBitmap(offscreenCanvasB);
console.log(imageBitmapB.width, imageBitmapB.height);
```

```text:実行結果
300 300
300 150
300 300
```

※ `transferFromImageBitmap()` メソッドは `transferToImageBitmap()` メソッドと異なり、通常の `HTMLCanvasElement` でも使用可能です。
※ `transferToImageBitmap()` は `OffscreenCanvas` 型のメソッドですが、`transferFromImageBitmap()` は `ImageBitmapRenderingContext` 型のメソッドです。

参考「[ImageBitmapRenderingContext: transferFromImageBitmap() method - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmapRenderingContext/transferFromImageBitmap)」

## 2.2. `close()` メソッド

`drawImage()` メソッド等、`transferFromImageBitmap()` メソッド以外でも `ImageBitmap` を描画することができますが、その場合は `ImageBitmap` が破棄されないため、`close()` メソッドを呼ぶことで明示的に破棄します。

```js:HTMLCanvasElement の場合
const context = canvas.getContext('2d');

context.drawImage(imageBitmap, 0, 0);
imageBitmap.close();
```

```js:OffscreenCanvas の場合
const offscreenContext = offscreenCanvas.getContext('2d');

offscreenContext.drawImage(imageBitmap, 0, 0);
imageBitmap.close();
```

参考「[CanvasRenderingContext2D: drawImage() メソッド - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/drawImage)」
参考「[WebGLRenderingContext: texImage2D() method - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D)」

参考「[ImageBitmap.close() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/ImageBitmap/close)」

## 2.3. `transferFromImageBitmap()` メソッドと `drawImage()` メソッドの違い

`transferFromImageBitmap()` メソッドでも `drawImage()` メソッドでも `ImageBitmap` を描画することができますが、以下の表のような違いがあります。

||`transferFromImageBitmap()` メソッド|`drawImage()` メソッド|
|-|-|-|
|Context の型|`ImageBitmapRenderingContext`|`CanvasRenderingContext2D`|
|画像ソースの型|`ImageBitmap`|`HTMLImageElement`<br>`SVGImageElement`<br>`HTMLVideoElement`<br>`HTMLCanvasElement`<br>`ImageBitmap`<br>`OffscreenCanvas`<br>`VideoFrame`|
|画像ソースの扱い|破棄|そのまま|
|位置や大きさ等を指定|不可|可|

# 3. `ImageBitmap` を使うべきでない状況

頻繁に `ImageBitmap` を生成しつつ `drawImage()` メソッドで描画するような使い方だと `ImageBitmap` の恩恵を得にくいため、直接 Canvas を `drawImage()` メソッドで描画した方が良いです。

```js
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');

const offscreenCanvas = new OffscreenCanvas(300, 150);

// ...

context.drawImage(offscreenCanvas, 0, 0);
```

参考「[CanvasRenderingContext2D: drawImage() メソッド - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/drawImage)」
参考「[WebGLRenderingContext: texImage2D() method - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D)」