【JavaScript】ブラウザ上でQRコードを生成/解析

ブラウザでQRコードが弄れるらしいので、試してみました。

# QR解析
- [MediaDevices - MDN](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia)
- [jsQR - GitHub](https://github.com/cozmo/jsQR)

カメラやマイクなどの入力デバイスを扱えるMediaDevicesインターフェイスを使用し、カメラから静止画を取得します。
そして静止画をjsQRというQR解析ライブラリへ通すことで、QRコードを読み取ることができます。

```js
function qrParse(video){
    const canvas = new OffscreenCanvas(240, 320);
    const render = canvas.getContext("2d");

    return new Promise((res)=>{
        const loop = setInterval(()=>{
            render.drawImage(video, 0, 0, canvas.width, canvas.height);

            const img = render.getImageData(0, 0, canvas.width, canvas.height);
            const result = jsQR(img.data, img.width, img.height);

            if(result){
                clearInterval(loop);
                return res(result.data);
            }
        }, 100);
    });
}
```
まず、このメソッドの引数として`HTMLVideoElement`を受け取ります。
そして、映像から定期的に静止画を抽出する必要があるため、1秒間に10回の間隔で`HTMLVideoElement`を`OffscreanCanvas`へ流し込みスナップショットを作成します。

スナップショットから`ImageData`を出力し、解析メソッド`jsQR()`へ渡します。
解析結果が存在したら、繰り返し処理の解除を行い、QRデータを解決します。


# QR生成
- [qrcode - GitHub](https://github.com/soldair/node-qrcode)

qrcodeという、いかにもという名前のQRコード生成ライブラリを使用します。

```js
function qrGenerate(data){
    const canvas = new OffscreenCanvas(1, 1);

    return new Promise((res, rej) => QRCode.toCanvas(canvas, data, {}, err => !err ? res(canvas) : rej(err)));
}
```
QRのバージョン、誤り訂正レベル、モードなどは、入力されるデータによってライブラリが自動で決定してくれます。

ライブラリがコールバック仕様なので、利便性のために`Promise`でラップしています。


# サンプルHTML
```html:qr.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">

        <script src="https://cdn.jsdelivr.net/npm/qrcode@latest/build/qrcode.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/jsqr@latest/dist/jsQR.min.js"></script>

        <title>QR</title>
    </head>

    <body>
        <video id="video" width="320" height="480" autoplay></video>
        <textarea id="result" readonly></textarea>
        <canvas id="canvas" width="240" height="240"></canvas>
        <textarea id="data"></textarea>
    </body>

    <script>
        function qrParse(video){
            const canvas = new OffscreenCanvas(240, 320);
            const render = canvas.getContext("2d");

            return new Promise((res)=>{
                const loop = setInterval(()=>{
                    render.drawImage(video, 0, 0, canvas.width, canvas.height);

                    const img = render.getImageData(0, 0, canvas.width, canvas.height);
                    const result = jsQR(img.data, img.width, img.height);

                    if(result){
                        clearInterval(loop);
                        return res(result.data);
                    }
                }, 100);
            });
        }

        function qrGenerate(data){
            const canvas = new OffscreenCanvas(1, 1);

            return new Promise((res, rej) => QRCode.toCanvas(canvas, data, {}, err => !err ? res(canvas) : rej(err)));
        }

        document.getElementById("data").addEventListener("change", async({target})=>{
            document.getElementById("canvas").getContext("bitmaprenderer").transferFromImageBitmap((await qrGenerate(target.value)).transferToImageBitmap());
        });

        (async()=>{
            const video = document.getElementById("video");
            video.srcObject = await navigator.mediaDevices.getUserMedia({
                audio: false,
                video: {
                    facingMode: "environment"
                }
            });

            document.getElementById("result").value = await qrParse(video);
        })();
    </script>
</html>
```

私のGitHub Pagesで試せます。

- [QR Parser/Generator - GitHub Pages](https://dojyorin.github.io/webapps/experimental/qr_parser_generator.html)