QRコードの一部を囲碁盤にしつつ読み込めるようにした

事の発端はこの配信。

このタイトルにこの配信貼ってること自体がネタバレなので隠さないで書くけど、
五目並べプレイでシオンちゃんがあまりに弱いので船長が忖度しまくった結果、盤面全部埋まって引き分けというオチになった。

で最後に二人が「QRコードじゃね?」的なことを言っていて、せっかくなら読み込めるようにしたら面白いのではと思って作り始めた。
ちなみに、そもそもQRコードは囲碁をヒントに作られたそうな。

最初に

解説を軽く交えていますが、詳細はかなり端折っているので、他のサイト等を参照してください。

最終目標としては、QRコードの一部を盤面と同じ配置にし、しかもちゃんと読み込めるものを作ること。

配信の切り抜き画像
クリックで拡大
配信の1:02:58より切り抜き
盤面をQRコードの風にした画像
クリックで拡大
これを含んだQRコードを作りたい

ちゃんとQRコードの一部として読み込めるのをコンセプトにしたので、たまに見かける中心にロゴが被っていて、QRコードの誤り訂正を利用して読み込めるようになっている形式にはしない。

また、読み込んでちゃんとしたものが表示されたほうが面白いので、ちゃんとしたレスポンスが返る値として、URLを採用することにした。
ただ、そのURLを成立させるには、変なデータがが混ざると上手くいかない可能性がある。
特に非表示文字とかがデバイスによってどのように扱われるのかがわからない。

なのでフラグメント識別子を採用することにした。
フラグメント識別子は、URLの最後の「#」以降の部分を表し、そのページ上のある部分を示すのに使われる。
この値については、無効な値が含まれた場合は、何も起こらず無視されるので、今回のようにランダムな値が含まれても成立させられる。
つまり読み込みには利用されているものの、実際のところ切り捨てられている。…やや片手落ち感あるけどこればっかりは仕方がないかと。

前半戦

任意の柄を作るにはQRコードメーカーなどは使えないので、アルゴリズムを理解して手書きで書くことにした。

ざっくりとした構造としては、

  • 四隅に固定パターン、左上と右上、左上と左下を繋ぐようにも固定パターン
    • 固定パターン付近に「誤り訂正レベル」と「マスクパターン参照子」を配置
  • データは右下から上方向に配置していき改行後は下方向に蛇行して配置していく
    • さらにざっくりした言い方をすると右から左に配置するといえる
  • データ構造は「モード指示子」「文字数指示子」「コード語」「誤り訂正語」の順に配置
    • 後ろに埋めデータを置いたりする場合もある
QRコードの例
クリックで拡大
サンプルとしてこのサイトのQRコード

上記のうち、任意の柄を書けるのは「コード語」だけ。
(誤り訂正語はコード語に合わせて変化はするが、計算が非常に難解なので任意の値を生成するのは現実的ではない)

  • 誤り訂正レベル

    • L(Low), M(Medium), Q(Quality?), H(High)の4段階
      どれを選んだかは後述。
  • マスクパターン参照子

    • 8パターンから選択
      選択方法に関しては、コードメーカーでは一番いい感じのマスクを採用するらしいんだけど、どのマスクを指定するかは自由っぽい 手書きする上では一番楽そうな0(000)を選択した。市松模様の範囲でマスクされる。
  • モード指定子

    • 数字モード、英数字モード、8ビットバイトモード、漢字モードの4種類
      1. 数字モード
        数字しか使えないが、最も文字数が多い。もちろんURLには使えない。
      2. 英数字モード
        大文字と一部記号のみ使用可能。用途によってはURLも作成できるが、今回の場合はURLに混ざっていけない文字があるため却下。
      3. 8ビットバイトモード
        0x00~0xFFをそのまま使用可能。今回はこれを使う。
      4. 漢字モード
        Shift_JISの全角文字の一部のみ記述できる方式。全角限定なのでURLには使えない。
  • 文字数指示子

    • コード語の文字数を表す
    • シンプルに数値を2進数変換したものを配置
  • コード語

    • 実際のデータをモード指定子に従って変換したものを配置
    • ここに今回五目並べの盤面を埋め込む
  • 誤り訂正語

    • 誤り訂正レベルとコード語の内容によって計算された値を配置
    • この部分の占める割合が案外大きく、目安だが以下の割合になる(復元可能な割合とは関係ありません
      • L: 約20%
      • M: 約40%
      • Q: 約55%
      • H: 約65%
    • Q以上だと実際のデータより、誤り訂正するデータの方が大きいのが興味深い。

上記を認識したうえで書き始める。

まずは大きさ。
盤面の15x15を真ん中に配置しつつ、固定の部分に干渉しないようにするには最低でもバージョン4(29x29)が必要だった。
ただ、計算したら29x29の大きさでは、誤り訂正レベル最低(L)でも盤面の一部が誤り訂正語の一部に干渉してしまったので、一つ大きいバージョン5(33x33)を選んだ。

コード語の値を求める。
最初はURLを成立させるために、正しいURLを配置。
次に”#“を置き、以降をフラグメント識別子として認識されるようにする。
それ以降のデータは適当で問題ないのだが、誤り訂正語を計算するのに必要なので、コード語全体の値を把握しなければいけない。
しかも、盤面の部分はマスクを考慮しなきゃならなかったのでかなり計算が面倒だった。

実際には以下のような感じになった。

https://youtu.be/-0bOqfsnQ7Q#\x00\x00\x00\x00\x00\x00\x28\x2a\xa8\xa8\x00\x00\x00
”\xNN”は特殊文字とか書くのに使う書き方
\x00が適当な値で、\x28\x2aとかの部分が盤面のところ

次に誤り訂正語を計算する。
リード・ソロモン符号化アルゴリズムというもので計算できるらしいので、Pythonライブラリで計算した。
ちなみにバージョン5で誤り訂正レベルLの場合、誤り訂正語の長さは26バイト。
(結果的にこのライブラリで計算した値でOKなのかどうかは、読み込めなかったので分からない。)

法則に従ってドットを配置し、マスクを反映して、読み込ませようとしたけど、残念ながら読み込めなかった…
ここで心が折れて手打ちはやめました。

後半戦

もういっそのこと総当たりで柄を探したほうが速いのではないかと。

  • 「URLの1文字を変更してQRコードの生成」
  • 「埋め込みたい部分だけ切り抜き」
  • 「目的の形の画像とのとの比較処理」

を総当たりで試し、一番近かったものを取得しURLに反映。これを繰り返した。

その結果、多分2時間くらいで全く同じ構造のものまで辿り着き、目的のQRコードが生成された。大成功!
生成されたQRコードの任意の柄の部分は、配信の盤面をそのまま貼り付けても読めるはずなので、編集して貼り付けた。

結果がこちら。

文の量を見ても分かるけど、最初からこっちでよかったなぁ…
前半戦については、多分10時間くらいかかっててあのオチという。