前置き
※2019/05/22追記:以前のは修正しました~って報告記事っぽかったのでもっと技術的な記事に書き直しました
WEBアプリ、システム開発においてQRコードを動的に生成する場合、QRコードの仕様を理解し、それ用のコードを書いて、、、と皆さんは消耗していませんか・・・?
コレに便利なライブラリがありまして、ズバリそのまま。jQuery.qrcode.jsという、文字列を渡したらQRコードを生成してくれるライブラリがあるんです。
これは検索すると各所でも導入の紹介記事が多く出ているぐらいには汎用的に使用されています。結構便利みたいですね。
使い方の解説記事はこちら。わかりやすくてオススメです→https://on-ze.com/archives/6022
jQueryQRCode自体は作者のGitHubから入手できます。
このライブラリ、使用するときに文字列を渡すとSVGでQRコードを動的に生成してくれます。
しかしこのライブラリ、実はQRコードの仕様を一部満たしていないポイントがあります。
結構重要なもので、実際のプロダクトに組み込んだ場合、これは大きな問題として扱われる可能性があります。
QRコードの仕様
デンソーウェーブがQRコードについて、規格化・標準化に関するページを公開しています。
文字数に関わるところ、最大文字数は十分あり、また、誤り訂正についてはそこまで実は意識することなく、認識可能です。
ただし、重要なのはココ。
コードの大きさ
21セル×21セル〜177セル×177セル(4セル /辺毎に増加)
表示されるQRコードの問題点
実際に組み込んで使うとこんな感じで。
$("#tipQR").html("");
$("#tipQR").qrcode({ width: 240, height: 240, text: "Shino3(e)" });
画面表示してみるとこんなかんじに生成されます。width, heightも指定可能で、SVGの一枚画像として仕上げてくれるので実はかなりこのライブラリは使いやすいのです。
さて、試しにページに背景色をつけてみましょう。
ダメダメです。こんな感じになります。
こうなるとスマホなどのQRコードリーダーでは読めません。
QRコードを読み込むにあたって、リーダーは画像情報から3方向にある四角い点で、上下左右方向、1セルの大きさや、QRコード全体のサイズを判断します。
しかし、マーカーになる黒い部分が周囲の背景色とコントラストが近くなると、マーカーが指示した形ではなくなってしまうため、認識できない問題が発生してしまいます。
QRコードの仕様を再確認
本来、QRコードの仕様ではこの周りに4セルのマージンが必要になります。実際に背景色をつけたものではマージンが設定されていません。
QRコードの読み込みにあたって、3方向にある四角い点で位置を判断します。
背景色と似たようなコントラストだとコントラスト比を出せないため、読み込みが困難になってしまいます。
読み込めるスマホアプリは正直すごいと思います。
対応方法
本来は、周囲に必要な4セル以上のマージンが設定されていれば、普通に読むことができます。
対応方法は2種類
- 元のコードには手を加えずに、CSSレイアウト等でマージンを設定
- すでに作ってあるページに関して、デザイン部分手を入れる必要がある
- 規模が大きい場合、全ページを対象に見る必要がある
- 根本的な問題は解決されない
- QRコードの生成スクリプトに手を加える
- ある程度のロジック判断の知識は必要(言語知識はそこまで必要でもない。)
- ライブラリに対して手を加える→既存のページ回収をしなくていいため、他の箇所への影響は少ない
コードの修正方法も2種類あります。
- SVGの出力サイズに対して、大きめのマージンを指定して、SVGファイルの記述サイズを変更する (以前はこの方法で行っていました。
- QRコード生成時に、4セルのマージンをオフセットして出力。
前者の方法ですと、もとの掲載ページ側のコード箇所も直さなければならず、個人で使うならまだしも、先方の許可等も必要になるため非常に困難。
また、根本的原因の解決ではない。
実際にコードに手を加えていく
私は後者の方法で、根本的解決を試みることにしました。
ライブラリ自体に直接手を加えて4セルlのマージンを設定していきます。
コード自体の実装は単純で、要件を考えていきます。
- 上下左右に4セルずつ表示ブロックを拡大。(コードのセルサイズ+8で指定)
- 余白エリアは背景色で塗りつぶし
- 黒 or 白の描画開始は縦5セル目、横5セル目になるまでは背景色で塗りつぶし
実際のコードを見ながら、手を入れていきます。
まずは、jquery.qrcode.jsの33行目、34行目に1番目の記述を対応します
// compute tileW/tileH based on options.width/options.height
- var tileW = options.width / qrcode.getModuleCount();
- var tileH = options.height / qrcode.getModuleCount();
+ var tileW = options.width / (qrcode.getModuleCount() + 8);
+ var tileH = options.height / (qrcode.getModuleCount() + 8);
次に、塗りつぶしエリアを調整していきます。
// draw in the canvas
- for( var row = 0; row < qrcode.getModuleCount(); row++ ){
- for( var col = 0; col < qrcode.getModuleCount(); col++ ){
- ctx.fillStyle = qrcode.isDark(row, col) ? options.foreground : options.background;
- var w = (Math.ceil((col+1)*tileW) - Math.floor(col*tileW));
- var h = (Math.ceil((row+1)*tileH) - Math.floor(row*tileH));
- ctx.fillRect(Math.round(col*tileW),Math.round(row*tileH), w, h);
- }
- }
+ for (var row = 0; row < (qrcode.getModuleCount() + 8); row++) {
+ for (var col = 0; col < (qrcode.getModuleCount() + 8); col++) {
+ if ((row < 4) || (row >= (qrcode.getModuleCount() + 4))) {
+ ctx.fillStyle = options.background;
+ }
+ else if ((col < 4) || (col >= (qrcode.getModuleCount() + 4)))
+ {
+ ctx.fillStyle = options.background;
+ }
+ else
+ {
+ ctx.fillStyle = qrcode.isDark(row - 4, col - 4) ? options.foreground : options.background;
+ }
+ var w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW));
+ var h = (Math.ceil((row + 1) * tileH) - Math.floor(row * tileH));
+ ctx.fillRect(Math.round(col * tileW), Math.round(row * tileH), w, h);
+ }
+ }
こんな感じで、マージンが追加されました。
コレで確実にスマートフォンのQRリーダーでも読むことができます。
修正するにあたっては、ほぼそのまま使えるように、作者のGitHubからフォークしています
コードはこちらで公開中。→私のGitHub
ところで、フォーク元にプルリク出して約一年・・・音沙汰がない・・・