jsQRを使ってブラウザQRコードリーダーを作る【技術メモ】【小ネタ】

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/b6d31427-3ef7-8a1a-d68e-896254c9be43.png)

# はじめに
株式会社マイスター・ギルド新規事業部のウサギーです。
弊社新規事業部では、新規サービスの立ち上げを目指して
日々、アイディアの検証やプロトタイプの作成を行っています。

今回はブラウザで使えるQRコードリーダーをさっくり作りたいな～と思いjsQRを使ってみました。

# jsQRとは？
javascriptで作られたQRコード読み取りライブラリです。
公式ページ https://github.com/cozmo/jsQR
デモページ https://cozmo.github.io/jsQR/

↓ デモページを試した図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/acc24c7f-5a9c-30a5-a7d9-cc36898c7268.png)


# 使用方法
公式のDemoページと同じようなものを作ってみます。

### 1 jsQR.jsを使う準備
[jsQR.js](https://github.com/cozmo/jsQR/blob/master/dist/jsQR.js)をDLしてプロジェクトに含めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/ea478191-133d-a4aa-4094-26fe79c61c9a.png)

HTMLにjsQR.jsを読み込みます。
```HTML:index.html
<script src="./jsQR.js"></script>
```

### 2 表示の準備
・QR読み込みのためのカメラ映像を表示する＜video＞要素
・＜video＞要素をもとにカメラ映像を表示する＜canvas＞要素
・QRコードの周りに赤枠を表示するための＜canvas＞要素
を準備します。

```HTML:index.html
<div id="wrapper">
    <video id="video" autoplay muted playsinline></video>
    <canvas id="camera-canvas"></canvas>
    <canvas id="rect-canvas"></canvas>
</div>
```
次で説明しますが、jsQRを使ってQRコードを検出するときのinputソースにvideo要素が使えないため
カメラ映像を表示用の＜canvas＞要素も用意しています。

これらを重ねて表示したい＆video要素は見えなくてもよいので、cssは次のように用意しました。
```css:style.css
#wrapper{
    position: relative;
}

#video{
    position: absolute;
    top: 0px;
    left: 0px;
    visibility: hidden;
}

#camera-canvas{
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 50;
}

#rect-canvas{
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 100;
}
```

### 3 カメラ映像の表示
```JavaScript:script.js
// Webカメラの起動
const video = document.getElementById('video');
let contentWidth;
let contentHeight;

const media = navigator.mediaDevices.getUserMedia({ audio: false, video: {width:640, height:480} })
   .then((stream) => {
      video.srcObject = stream;
      video.onloadeddata = () => {
         video.play();
         contentWidth = video.clientWidth;
         contentHeight = video.clientHeight;
         canvasUpdate(); // 次で記述
         checkImage(); // 次で記述
      }
   }).catch((e) => {
      console.log(e);
   });
```

```JavaScript:script.js
// カメラ映像のキャンバス表示
const cvs = document.getElementById('camera-canvas');
const ctx = cvs.getContext('2d');
const canvasUpdate = () => {
   cvs.width = contentWidth;
   cvs.height = contentHeight;
   ctx.drawImage(video, 0, 0, contentWidth, contentHeight);
   requestAnimationFrame(canvasUpdate);
}
```

### 4 QRコードの検出
```JavaScript:公式Usage
const code = jsQR(imageData, width, height, options?);

if (code) {
  console.log("Found QR code", code);
}
```

QRコードの検出は上記のコードを使います。
jsQRメソッドの第一引数が「 __imageData__ 」なので、＜canvas＞要素を用意した訳です。


```JavaScript:script.js
// QRコードの検出
const rectCvs = document.getElementById('rect-canvas');
const rectCtx =  rectCvs.getContext('2d');
const checkImage = () => {
   // imageDataを作る
   const imageData = ctx.getImageData(0, 0, contentWidth, contentHeight);
   // jsQRに渡す
   const code = jsQR(imageData.data, contentWidth, contentHeight);

   // 検出結果に合わせて処理を実施
   if (code) {
      console.log("QRcodeが見つかりました", code);
      drawRect(code.location);
   } else {
      console.log("QRcodeが見つかりません…", code);
      rectCtx.clearRect(0, 0, contentWidth, contentHeight);
   }
   setTimeout(()=>{ checkImage() }, 500);
}
```

```JavaScript:script.js
// 四辺形の描画
const drawRect = (location) => {
   rectCvs.width = contentWidth;
   rectCvs.height = contentHeight;
   drawLine(location.topLeftCorner, location.topRightCorner);
   drawLine(location.topRightCorner, location.bottomRightCorner);
   drawLine(location.bottomRightCorner, location.bottomLeftCorner);
   drawLine(location.bottomLeftCorner, location.topLeftCorner)
}

// 線の描画
const drawLine = (begin, end) => {
   rectCtx.lineWidth = 4;
   rectCtx.strokeStyle = "#F00";
   rectCtx.beginPath();
   rectCtx.moveTo(begin.x, begin.y);
   rectCtx.lineTo(end.x, end.y);
   rectCtx.stroke();
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/102f2dee-1856-da80-5805-c8321a642709.png)

これで、検出部はできました！

###  5 QRコードの情報取得
QRコードの情報も取り出してみます。

表示部を用意
```HTML:index.html
<span id="qr-msg">QRコード: 見つかりません</span>
```
```css:style.css
#qr-msg{
    position: absolute;
    top: 500px;
    left: 0px;
}
```

```diff_Javascript:script.js
// QRコードの検出
const rectCvs = document.getElementById('rect-canvas');
const rectCtx =  rectCvs.getContext('2d');
const checkImage = () => {
   // imageDataを作る
   const imageData = ctx.getImageData(0, 0, contentWidth, contentHeight);
   // jsQRに渡す
   const code = jsQR(imageData.data, contentWidth, contentHeight);

   // 検出結果に合わせて処理を実施
   if (code) {
      console.log("QRcodeが見つかりました", code);
      drawRect(code.location);
+     document.getElementById('qr-msg').textContent = `QRコード：${code.data}`;
   } else {
      console.log("QRcodeが見つかりません…", code);
      rectCtx.clearRect(0, 0, contentWidth, contentHeight);
+     document.getElementById('qr-msg').textContent = `QRコード: 見つかりません`;
   }
   setTimeout(()=>{ checkImage() }, 500);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/12228708-932c-a486-0bd7-26c58dfa2574.png)
情報も出るようになりました。

# コード全体
<details><summary>index.html</summary><div>

```HTML:index.html
<!DOCTYPE html>
<head>
    <title>QRコードのテスト</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
        <div id="wrapper">
            <video id="video" autoplay muted playsinline></video>
            <canvas id="camera-canvas"></canvas>
            <canvas id="rect-canvas"></canvas>
            <span id="qr-msg">QRコード: 見つかりません</span>
        </div>
    <script src="./jsQR.js"></script>
    <script src="./script.js"></script>
</body>
</html>
```
</div></details>

<details><summary>style.css</summary><div>

```css:style.css
#wrapper{
    position: relative;
}

#video{
    position: absolute;
    top: 0px;
    left: 0px;
    visibility: hidden;
}

#camera-canvas{
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 50;
}

#rect-canvas{
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 100;
}

#qr-msg{
    position: absolute;
    top: 500px;
    left: 0px;
}
```
</div></details>

<details><summary>script.js</summary><div>

```js:script.js
// Webカメラの起動
const video = document.getElementById('video');
let contentWidth;
let contentHeight;

const media = navigator.mediaDevices.getUserMedia({ audio: false, video: {width:640, height:480} })
   .then((stream) => {
      video.srcObject = stream;
      video.onloadeddata = () => {
         video.play();
         contentWidth = video.clientWidth;
         contentHeight = video.clientHeight;
         canvasUpdate();
         checkImage();
      }
   }).catch((e) => {
      console.log(e);
   });

// カメラ映像のキャンバス表示
const cvs = document.getElementById('camera-canvas');
const ctx = cvs.getContext('2d');
const canvasUpdate = () => {
   cvs.width = contentWidth;
   cvs.height = contentHeight;
   ctx.drawImage(video, 0, 0, contentWidth, contentHeight);
   requestAnimationFrame(canvasUpdate);
}

// QRコードの検出
const rectCvs = document.getElementById('rect-canvas');
const rectCtx =  rectCvs.getContext('2d');
const checkImage = () => {
   // imageDataを作る
   const imageData = ctx.getImageData(0, 0, contentWidth, contentHeight);
   // jsQRに渡す
   const code = jsQR(imageData.data, contentWidth, contentHeight);

   // 検出結果に合わせて処理を実施
   if (code) {
      console.log("QRcodeが見つかりました", code);
      drawRect(code.location);
      document.getElementById('qr-msg').textContent = `QRコード：${code.data}`;
   } else {
      console.log("QRcodeが見つかりません…", code);
      rectCtx.clearRect(0, 0, contentWidth, contentHeight);
      document.getElementById('qr-msg').textContent = `QRコード: 見つかりません`;
   }
   setTimeout(()=>{ checkImage() }, 500);
}

// 四辺形の描画
const drawRect = (location) => {
   rectCvs.width = contentWidth;
   rectCvs.height = contentHeight;
   drawLine(location.topLeftCorner, location.topRightCorner);
   drawLine(location.topRightCorner, location.bottomRightCorner);
   drawLine(location.bottomRightCorner, location.bottomLeftCorner);
   drawLine(location.bottomLeftCorner, location.topLeftCorner)
}

// 線の描画
const drawLine = (begin, end) => {
   rectCtx.lineWidth = 4;
   rectCtx.strokeStyle = "#F00";
   rectCtx.beginPath();
   rectCtx.moveTo(begin.x, begin.y);
   rectCtx.lineTo(end.x, end.y);
   rectCtx.stroke();
}
```
</div></details>

# おまけ
QRコードでなんか遊べないかなぁ、と思って
ドレミファソラシドの音が出るプログラムを作ってみました。
「C4」～「C5」のQRコードを作ってかざすと音が出ます。

```diff_Javascript:script.js
   // 検出結果に合わせて処理を実施
   if (code) {
      console.log("QRcodeが見つかりました", code);
      drawRect(code.location);
      document.getElementById('my-qr-msg').textContent = `QRコード：${code.data}`;
+     playPitche(code.data);
   } 
```

```diff_Javascript:script.js
+// 指定周波数音を出す
+let audioCtx = new AudioContext();
+const playMelody = (hz) => {
+    let osc = audioCtx.createOscillator();
+    osc.frequency.value = hz;
+    let audDes = audioCtx.destination;
+    osc.connect(audDes);
+    osc.start = osc.start || osc.noteOn;
+    osc.start();
+    setTimeout(function() { osc.stop(0);}, 200);
+}

+// ドレミファソラシドを鳴らす
+const playPitche = (name) => {
+    switch(name){
+        case 'C4':
+            playMelody(261);
+            break;
+        case 'D4':
+            playMelody(294);
+            break;
+        case 'E4':
+            playMelody(330);
+            break;
+        case 'F4':
+            playMelody(349);
+            break;
+        case 'G4':
+            playMelody(392);
+            break;
+        case 'A4':
+            playMelody(440);
+            break;
+        case 'B4':
+            playMelody(494);
+            break;
+        case 'C5':
+            playMelody(522);
+            break;
+    }
+}

```