入力フォームで選択した画像を送信前に QR コード判定する

はじめに

実務にて、入力フォームで選択した画像を送信前に QR コード判定して、送信後に URL と 画像ファイルを表示してほしいという小仕事の依頼があり、それを解決した情報になります。ソースコードをコピペ(コピー&ペースト)するだけで簡単に流用ができます。また、ライブラリとプラグインのバージョンにお気を付けください。

ソースコード

<form method="post">
  <input type="file" id="inputQRCode" name="inputQRCode" accept=".png,.jpg">
  <input type="hidden" id="inputQRCodeString" name="inputQRCodeString">
  <input type="submit">
</form>
<div id="error" style="color: red;"></div>
<p>File Name : <?= $_POST['inputQRCode'] ?></p>
<p>URL : <?= $_POST['inputQRCodeString'] ?></p>
<p>QR Code :</p>
<div id="outputQRCode"></div>
<script src="//cdn.jsdelivr.net/npm/jsqr@1.3.1/dist/jsQR.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="//cdn.jsdelivr.net/npm/jquery.qrcode@1.0.3/jquery.qrcode.min.js"></script>
<script>
const canvas = document.createElement('canvas')
const reader = new FileReader()
inputQRCode.addEventListener('change', e => {
  try {
    const file = e.target.files[0]
    reader.onload = function() {
      const image = new Image()
      image.onload = function() {
        canvas.width = this.width
        canvas.height = this.height
        const ctx = canvas.getContext('2d')
        ctx.drawImage(image, 0, 0)
        const imageData = ctx.getImageData(0, 0, this.width, this.height)
        const code = jsQR(imageData.data, imageData.width, imageData.height)
        if (!code) {
          inputQRCode.value = ''
          error.textContent = 'QR コードを選択してください。'
          return
        }
        inputQRCodeString.value = code.data
      }
      const src = reader.result
      if (!src) {
        inputQRCode.value = ''
        error.textContent = '画像の読み込みに失敗しました。'
        return
      }
      image.src = src
    }
    reader.readAsDataURL(file)
    error.innerHTML = ''
  } catch (e) {
    inputQRCode.value = ''
    error.textContent = e.message
  }
})
<?php if ($_POST['inputQRCodeString']): ?>
  $('#outputQRCode').qrcode({width: 128, height: 128, text: "<?= $_POST['inputQRCodeString'] ?>"})
<?php endif; ?>
</script>

検証環境

ライブラリ & プラグイン

解説

QR コード判定

選択ファイルの QR コード判定には JavaScript ライブラリ「 jsQR 」を利用します。ライブラリの読み込みは CDN を利用しました。

<script src="//cdn.jsdelivr.net/npm/jsqr@1.3.1/dist/jsQR.min.js"></script>

input 要素の change イベント発生後、addEventListener() メソッドで QR コード判定処理が走る仕掛けを作ります。

<input type="file" id="inputQRCode" name="inputQRCode" accept=".png,.jpg">
inputQRCode.addEventListener('change', e => {
  // 処理
})

例外処理文 を記述します。

try {
  // 処理
} catch (e) {
  inputQRCode.value = ''
  error.textContent = e.message
}

FileReader クラスのインスタンスを new 演算子 で生成、

const reader = new FileReader()

選択ファイルの FileList オブジェクトを変数に格納、FileReader.onload プロパティを使って、ファイルの読み込み完了後に処理が走る仕掛けを作ります。

const file = e.target.files[0]
reader.onload = function() {
  // 処理
}

Image() コンストラクタで img 要素を生成、load イベント発生後に処理が走る仕掛けを作ります。

const image = new Image()
image.onload = function() {
  // 処理
}

document.createElement() メソッドで canvas 要素を生成して、width 属性と height 属性に値を設定、getContext() メソッドで 2D レンダリングコンテキストを取得、drawImage() メソッドで canvas 要素に画像を描画します。

const canvas = document.createElement('canvas')
canvas.width = this.width
canvas.height = this.height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0)

描画した画像データから getImageData() メソッドで ImageData オブジェクトを取得します。

const imageData = ctx.getImageData(0, 0, this.width, this.height)

javascript ライブラリ jsQR の jsQR() メソッドに ImageData オブジェクトを渡してデコード、QR コード情報が詰まったオブジェクトを取得しています。

const code = jsQR(imageData.data, imageData.width, imageData.height)

ImageData オブジェクトのデコードに失敗した場合の処理を記述します。

if (!code) {
  inputQRCode.value = ''
  error.textContent = 'QR コードを選択してください。'
  return
}

QR コード文字列を送信する為に input 要素の value 属性に値を設定します。

<input type="hidden" id="inputQRCodeString" name="inputQRCodeString">
inputQRCodeString.value = code.data

FileReader.result プロパティで データ URL を取得します。

const src = reader.result

データ URL の取得に失敗した場合の処理を記述します。

if (!src) {
  inputQRCode.value = ''
  error.textContent = '画像の読み込みに失敗しました。'
  return
}

生成した img 要素の src 属性に データ URL を設定します。

image.src = src

FileReader.readAsDataURL() メソッドでファイルを読み込みます。

reader.readAsDataURL()

また、特定の URL のみ許可したい場合は、QR コード情報に判定を追加することで実現できます。

const services = [
  'http://webgroove.work',
  'https://webgroove.work'
]
if (!services.filter(service => code.data.startsWith(service))[0]) {
  inputQRCode.value = ''
  error.innerHTML = 'Web Groove の QR コードを選択してください。'
  return
}

以上です。

QR コード出力

送信後の QR コード出力には jquery プラグイン jquery.qrcode.js を利用しました。ライブラリとプラグインの読み込みは CDN を利用しました。

<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
</script><script src="//cdn.jsdelivr.net/npm/jquery.qrcode@1.0.3/jquery.qrcode.min.js"></script>

jquery プラグイン jquery.qrcode.js の qrcode() メソッドに受信データの文字列を渡して QR コードを出力します。今回、受信データは PHP タグ$_POST から取得しています。

<div id="outputQRCode"></div>
<?php if ($_POST['inputQRCodeString']): ?>
  $('#outputQRCode').qrcode({width: 128, height: 128, text: "<?= $_POST['inputQRCodeString'] ?>"})
<?php endif; ?>

以上です。

おわりに

QR コード判定の別解として、Google Cloud Vision API の AI に画像を解析させて実現することも可能です。