QRcode (2)

Table of Contents

doc

前回からやってるQRコードを少し深堀してみる。

まずは開発物語の本。QRコードの奇跡

それから、楽しい対談。 QRコードはマジックで塗りつぶしても読める。油汚れに勝つために【QRコード1】#60

作成したQRコードを確認しよう という、QRコードの作成サイトが出てきた。 誰でもコードを作成できる。悪い事も思いのままだぞ。何も知らなくても出来 ちゃうってどうよ。

Read QRcode on Web 画像が有ればOKかな。

って事で、頑張っている高校生を応援キャンペーン。

あなたも描ける! JIS X0510準拠 QRコード®超入門

そして規格書です。 QRコード バーコードシンボル体系仕様 (JIS)

オイラーの理解

QRコードは通信媒体です。電波と違って記録にに残るんです。 正方形のキャンバス内に、ドット(ビット)を鏤めて記録します。 キャンバスのサイズは、1号から40号まで定義されます。1号のサイズは21X21 ドットです。40号サイズは177X177です。4づつ大きくなる計算です。 仕様書ではキャンバスの変わりにバージョンって語句を使ってるけど、それだ とプログラムのバージョンと混同するぞ。

同じ号でも、ドットの大きさによって、正方形のサイズは変わってきます。 よって、配置できる高さ(又は幅)に制限がある場合は、表示したいデータ(文字列)を適 当な大きさに分割し、それぞれのQRコードを作成。それを屏風のように並べら れるようになってます。さすが日本生まれ、和の技が導入されます(セグメン トなんて言う、それっぽい名称より、ずばり屏風にしたら)。 それから通信の常として、データは(なるべく)圧縮して小くすると言う事も行っ ています。

また、少々の汚れや傷で、読めなくならないような配慮(エラー検出と自動補 正)がなされています。だから、油汚れやチリ・ホコリにも強いんです。現場が必要にせまられて機能を設 計すると、本当に使える物ができあがる。温室育ちじゃない、野生児な貫禄が あるんです。

素早くコードを読みとれる事も現場では大事な要点。また、斜めや曲面とかの 歪みにより、正方形じゃなくなっても、それを補正して何とか使えるようにす る工夫が各種施されています。

数字、英数記号、バイナリ、換字と4種類のデータの区別l(modeって変な名前 で呼ぶみたい)、誤り訂正の程度等 の重要なデータ(いわゆるヘッダー情報)は、2箇所に分散して格納。また、デー タに偏り(0がずっと続く等)が有っても、01が適度に表われるような工夫(マス キング)が行われる。このマスクパターン(8種)も大事なヘッダー情報に含めて いる。

このデータがバラケる工夫ってのは、シリアル通信では極めて重要。だって、 どのタイミングでデータを取り込むかのストローブ・クロックがハード的に必 要。そのためのラインを用意するのは無駄。だから、データの変化を検出して、 自動的にクロックを作るのが得策。この場合、データに変化が無いと不利。 そんな理由で、データがバラケるような工夫をするんだ。

グダグダと書いてきたけど、一番素晴しいのは、特許フリーで公開された事だ ろう。大英断に感謝します。仕様が公開され、安心して使えるのは人類の宝で すよ。

xor の威力

マスキングの実現には、xorが使われている。

今更ながら xor演算の威力を確かめておく。unixなら bc とか dc なんて言う 電卓が用意されてるけど、残念ながらビット演算はサポートされていない。 そこで目を付けたのは、emacsですよ。これはeditorにあらず。開発環境です から。

GNU Calc - 貧乏人の Mathmatica

C-x * b でフルキーパッド画面になる。キーバッドの所でTABすれば専用のモー ドにトグルする。これは、X上のユーザー向けだな。 GUIで良ければWindowsに付属してる電卓さ。こいつ、プログラマー御用達モー ドもサポートしてるぞ。

CUI端末で使っているなら、C-x * c すれば、起動する。RPN式な電卓なんで、 左窓がスタックの状態。右側の窓は、計算履歴の表示用だ。昔良く見かけた、 プリンター付きの電卓風情だ。

2進数で、結果を表示して欲しいので、最初に d 2 しておく。 キーボードからも入力は2進数でしたいなら、データの前に 2# を付ける。

下記は利用サマリーから引いたもの。w は、ワードサイズだ。

a b      b x                     9  xor(a,b,w)

2#1111 SPACE 2#0101 SPACE b x と入力すると 1111 と 0101 をスタックに積 み、その2つのデータを取り出してxorを計算する。結果をスタックに残してくれる。

3:  2#101011                      |     2#10000001   ;; data
2:  2#101011                      |     2#10101010   ;; mask
1:  2#10000001                    | xor 2#00101011
    .                             |     2#10101010   ;; mask
                                  | xor>2#10000001

上の例だとMSB,LSBが1で途中がずっと0の不均衡のデータがある。それに市松 模様のマスク値でxorする。均衡度合いが良くなった。再び、市松模様でxorす ると、元のデータが復元された。(注 演算結果の上位の0は表示が抑制される みたい。桁数が合うように調整しました)

このxorの応用として、RC4と言う有名なストリーム暗号が有る。maskがランダ ムだと絶対に解読不能。勿論、暗号化する方、復号化する方で、同期したラン ダム値が必要だけど。。処理のコストは乱数の発生だけだ。

ああ、もう一つxorの有名な利用方法を思い出した。アセンブラを記述してる 時、任意のレジスターをクリアする定石。xor ax ax と言う奴。レジスターに どんな値が入っていても、一発でクリアできる。歳がバレるな。

haskell version

ChatGPTにQRcodeの紹介を頼んだら、haskellは難しいんでと軽くいなされてし まった。悔しいので、正統派の検索をやってみた。 Browse and search packages へ行って、qrcodeと入力。4個の候補が出てきた。 そのうちの、2個を遡上に載せてみる。 何故2個か? セカンド・オピニオンさ。

qrcode: QR Code library in pure Haskell

qrcode-core: QR code library in pure Haskell

qrcode

昔から有る奴。コンパイル出来るかな? ハラハラ・ドキドキ。

Codec/Binary/QRCode/GaloisField.hs:159:40: error:
    Ambiguous occurrence ‘readBin’
    It could refer to
       either ‘Numeric.readBin’,
              imported from ‘Numeric’ at Codec/Binary/QRCode/GaloisField.hs:6:1-14
           or ‘Codec.Binary.QRCode.GaloisField.readBin’,
              defined at Codec/Binary/QRCode/GaloisField.hs:19:1
    |
159 | gfpToBinaryRepr (GFPolynomial terms) = readBin bits
    |                                        ^^^^^^^

やっぱりエラーだ。これがhaskellの常態です。昔のオイラーならエラー撲滅 に取りかかるんでしょうけど、軽く流します。例が同梱されてた。

main :: IO ()
main = arrayToFile "hello.pgm"
     . toArray
     . fromJust
     . encode (fromJust $ version 1) M Alphanumeric
     $ "hello world"

ハロワをencodeに喰わせて、配列に結果を入れ、最終的にファイルに出力する んだな。

encode :: Version -> ErrorLevel -> Mode -> String -> Maybe Matrix
encode ver ecl mode input =
  :

肝心な部分はencodeだな。ああencodeって前回FT8の所でやったな。あの時は、 コールサインなる文字列を数字に変換した。今回は、文字列をデータの入いっ た配列にするとな。世の中、考え方は一緒だ。

全体を統合してるスペック表みたいな、Spec.hsを見ながら気の向くままに散 策すればいいんだな。

つきつめてみてもいいんだけど、最終成果物が得られないんで、これで打ち切 り。

qrcode-core

[sakae@deb qrcode-core-0.9.9]$ cabal install --lib
 :
Installing library in /home/sakae/.cabal/store/ghc-9.2.8/incoming/new-2150/home/sakae/.cabal/store/ghc-9.2.8/qrcode-core-0.9.9-af246d844a62b2a96aa4984f9d7a3e662563dbdd19a71400076892a984f953b7/lib

何か例があるかと思ったら何もなし。作者さんによって対応が違うな。まあ、 ソース嫁。それが何よりの資料。

[sakae@arch src]$ tree -L 3
.
└── Codec
    ├── QRCode
    │   ├── Base.hs
    │   ├── Code
    │   ├── Data
    │   ├── Intermediate
    │   ├── Intermediate.hs
    │   └── Mode
    └── QRCode.hs

こんな風になってるから、QRCode.hs からだな。自由に回れるように、 hasktags を使って TAGSを作成しておく。

-- | Generate a QR code representing the specified text string encoded in alphanumeric mode.
--
--    The alphanumeric encoding contains this characters: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".
--
--    When the input is case insensitive the chars are converted to uppercase since this alphabet contains only uppercase cha\
racters.
--    This can be archived by applying `Data.CaseInsensitive.mk` to the input.
encodeAlphanumeric :: ToText a => QRCodeOptions -> a -> Maybe QRImage
{-# INLINABLE encodeAlphanumeric #-}
encodeAlphanumeric opt = getResult . (fromIntermediate <$>) . (toIntermediate opt =<<) . alphanumeric

入力の文字種は限定的だな。どうややURLが表現出来れば満足っぽい。

出力の型を見ておくかな。qrcode-core-0.9.9/src/Codec/QRCode/Data/QRImage.hs

data QRImage
  = QRImage
    { qrVersion    :: !Int
    , qrErrorLevel :: !ErrorLevel
    , qrImageSize  :: !Int
    , qrImageData  :: !(UV.Vector Bool)
    }

手慰めにこんなコードを作成

import Codec.QRCode
import Data.Maybe
import qualified Data.Vector.Unboxed  as UV

mine :: QRImage
mine = do
  fromJust img
   where
    img = encodeAlphanumeric (defaultQRCodeOptions L) "HELLO QRCODE"

main :: IO ()
main = do
  let img = mine
  print (qrVersion img)
  print (qrImageSize img)
  print (UV.length (qrImageData img))
  print ((qrImageData img) UV.! 5)

最初こんなエラーを食った。

λ> main
 *** Exception: Maybe.fromJust: Nothing
CallStack (from HasCallStack):
  error, called at libraries/base/Data/Maybe.hs:149:21 in base:Data.Maybe
  fromJust, called at /tmp/myqr/app/Main.hs:10:10 in main:Main

どこかで失敗しててイメージが作成されていないと解釈できる。

しょうがないので、ドキュメントを作成して、しっかり読んでみた。

[sakae@fb /tmp/myqr]$ cabal  --haddock-all haddock
  :
Documentation created: dist/doc/html/qrcode-core/index.html

原因判明。文字列に小文字を使ってた。使えるのは、大文字だけだった。いき なりソースじゃなくて、ドキュメントを嫁だな。

ghci> main -- エラーレベルが L の場合
1
21
441
True
ghci> :r
[1 of 1] Compiling Main             ( app/Main.hs, interpreted )
Ok, one module loaded.
ghci> main -- エラーレベルを最高の H にした場合
2
25
625
True

後は、イメージデータをサイズ区切りに読み出して(true/false)それを表示さ せればいいんだな。

表示にErrorLevelが抜けてるぞ。そう、表示させようとすると

app/Main.hs:15:3: error:
    • No instance for (Show ErrorLevel) arising from a use of ‘print’
    • In a stmt of a 'do' block: print (qrErrorLevel img)
      In the expression:
        do let img = mine
           print (qrVersion img)
           print (qrErrorLevel img)
           :

こんなエラーになるのさ。そもそもの原因は、Data/ErrorLevel.hs の定義

-- | The error level of an QRCode
data ErrorLevel
  = L -- ^ Allows error recovery up to 7%
  | M -- ^ Allows error recovery up to 15%
  | Q -- ^ Allows error recovery up to 25%
  | H -- ^ Allows error recovery up to 30%
  deriving (Bounded, Enum, Eq)

ちょいと追加するだけなんだけど、そこは作者さんの主張を尊重しとく。そん なのイメージが出来あがったら関係無いしょ。

disp QRcode

凡その仕組みが分ったので、Vectorを表示してみる。

import Codec.QRCode
import Data.Maybe
import qualified Data.Vector.Unboxed  as UV

mine :: QRImage
mine = fromJust img
   where
    img = encode (defaultQRCodeOptions L) Iso8859_1  "Hello QRcode"

main :: IO ()
main = do
  let img = mine
  let vec = qrImageData img
  let sz = qrImageSize img
  disp (UV.toList vec) sz

disp :: [Bool] -> Int -> IO ()
disp lst sz = do
  let  (v,vs) = splitAt sz lst
  mapM_ (\a -> do  putStr (if a then "██" else "  ")) v
  putStr "\n"
  case vs of
    [] -> return ()
    _  -> disp vs sz

わざわざVectorをListに変換してるのは、Vectorの空の検出が上手く出来なかっ たから。Listなら、まあ手慣れたものだからからね。(修行が足りないのは承 知の介)

ghci> main
██████████████    ██        ██████████████
██          ██      ████    ██          ██
██  ██████  ██  ██    ████  ██  ██████  ██
██  ██████  ██  ██          ██  ██████  ██
██  ██████  ██  ██████████  ██  ██████  ██
██          ██  ██      ██  ██          ██
██████████████  ██  ██  ██  ██████████████
                ██  ██████
██  ██████████      ██  ██  ██████████
    ██  ████    ██  ██  ██        ████  ██
    ██      ██    ██  ██    ██    ██████
  ██  ████      ████      ██      ████
  ██    ██  ████████  ██    ████        ██
                ██  ██  ██        ██
██████████████          ██████      ████
██          ██  ████████████  ██  ████
██  ██████  ██  ██    ████  ████      ████
██  ██████  ██  ██        ██████████
██  ██████  ██  ████████████████    ██
██          ██    ██    ████    ██████
██████████████  ██    ████  ██  ██    ██

本当は、これだけじゃいかん。周りに空白エリアを4ドット分、付け加えなけ ればならない。

マザボのブロック図

配置と言えば、上のコード、mine,main,dispが順番に並んだ。中央に有るmain をCPUに見立てれば、その上と言うか北にあるのはグラボ相当。ノースブリッ ジト称される奴だ。南にあるttyとかのコントローラーは、サウスブリッジね。 意図した訳ではないのに、自然にこうなった。

まずmainを書いて、そこから北の方向にmineを置いて、そこを埋めた。って、 外注のencodeを呼ぶサブボードだけど。南に置いたdispもmainが良く見える位 置って事です。北との配線をもう一本追加しときたいな。今のままじゃ、テス トパターンだけだからね。土台が出来てしまえば、改造は簡単だ。

外注の石は、AMDのやつでもいいし、ググルのTPUでも、nvideaのA100みたいな 石でもいいし、最近ではこんなのが噂されてる。OpenAIも参入とうわさの「AIチップ」は何を行うチップなのか

そう言えば、AMDの会長と将棋の藤井君はツーカーみたいだね。石を作ってる 人と、それを使う人との、ウィン・ウィン関係。せいぜい楽しんでください。

楽しむといえば、NHKの、英雄の選択にたまたま出てくる、脳科学者の美魔女、 中野さんも、髪型をしょっちゅう替えて楽しんでいるな。脳の保護材を変化さ せて、効き目を試しているんだな。いや、何が受けるかの科学実験なんちゃっ てね。

etc

QRコードを生成するHaskellライブラリを試した

こちらにもご同胞がおられた。やっぱりPNGとかにするのが普通なんですかね。 オイラーは、どうもGUI系は苦手だなあ。

折角のコードなんで鑑賞しとけ。犬も歩けば棒に当たるぞ。 qrコードは2次元の配列に返ってくるんだな。こちらの方が素直な表現方法だ な。わざわざVectorにしたのは、スピードの関係? 後は、そのarrayをPNGに変換してるのか。分けて考えれば、そう難儀でもない か。

苦手と言えば、pythonもそうだな。

Pythonのここが嫌い、なぜわざわざ初心者に分かりにくい記法を採用するのか

フムフム、そうだよネェ。


This year's Index

Home