[Node.js] デザインQRコードを作成する

今回はQRコードの真ん中あたりに画像を埋め込む「デザインQR」をNodeで作成してみます。

通常のQRでも機能自体に問題ありませんが、以下のような欲求が出てきます。

  • どんなデータが埋め込まれているか想像しづらい、
  • デザイン的に無粋(無機質)

今回はこれらを解消しようというお話です。

作成その前に

原理

QRコードの仕様を作成したデンソーウェーブ社によると、QRコードはその一部が欠損したとしても周辺の情報を利用して情報を復元する機能が備わっています。もともと屋外の広告など様々な場所に付けられることを想定しているようで完全な状態で読み取られることの方が少ないという前提なのかもしれませんね。

この復元機能のことを「誤り訂正機能」と呼び、レベルが4つに別れています。レベルが高いほど誤り訂正能力も高くなりますが、その分QRコード自体のサイズも大きくなってしまいます。

レベル 誤り訂正能力
L 約 7%
M 約15%
Q 約25%
H 約30%

デザインQRコードではこの機能を利用しQRコードの一部を別の画像に置き換えてしまおうというわけです。

基本的なQRコードの生成方法

過去記事にまとめてありますのでこちらを参考にしてください。 blog.katsubemakito.net

画像をQRコードの上に載せる

アイコン画像

このブログのfaviconを利用しています。

インストール

適当なディレクトリ作成し、package.jsonを作成後にnode-qrcodenode-canvasモジュールをインストールします。

$ mkdir qr && cd qr
$ npm init
$ npm install qrcode
$ npm install canvas

サンプルコード

原理としてはnode-qrcodeのtoCanvasメソッドを利用し、先にQRコードを生成したら、画像を読み込んでその上にcanvasの機能を使って貼り付けているだけです。

const { createCanvas, Image } = require('canvas');
const QRCode = require('qrcode');

// canvas準備
const canvas = createCanvas(300, 300);

// QRコードの設定準備
const segment = [
  { data:"ねこの足跡\n", mode:"kanji"},
  { data:"https://blog.katsubemakito.net/", mode:"byte"}  //alphanumericだと怒られる
];
const options = {};

// QRコード生成
QRCode.toCanvas(canvas, segment, options, (error) => {
  const icon = new Image();
  icon.onload = ()=>{
    const left = Math.floor((canvas.width - icon.width) / 2);    // x
    const top  = Math.floor((canvas.height - icon.height) / 2);  // y

    // 画像を載せる
    const ctx = canvas.getContext('2d');
    ctx.drawImage(icon, left, top);

    // 出力
    console.log(`<img src="${canvas.toDataURL()}" />`);
  };
  icon.src = './icon.png';  // 上に載せる画像
});

結果

最終的にこんな感じの画像が仕上がります。

実際に出力される内容は以下の通り。

<img src="data:image/png;base64,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" />

続き

blog.katsubemakito.net

参考ページ