如何製作 QR Code #3:資料編碼

帶你實現屬於自己的 QR Code 產生器和解碼器

Yeecy
9 min readAug 21, 2020

我們會在這篇文章中討論如何根據原始訊息和容錯等級,決定使用何種編碼模式和 QR Code 的版本,並完成資料的編碼。

點此閱讀《如何製作 QR Code》其他文章

選擇編碼模式

正常來說,人們喜歡用最低的成本來完成某件事情,製作 QR Code 的道理也一樣,如果今天有個數字字串要編碼,我們肯定會優先採用數字模式來編碼,而不是文數字模式,因為前者字元的平均編碼長度接近 3.4,而後者的平均編碼長度則接近 5.5。

讓我們看看下面例子:

input        | 0123
-------------+----------------------------------
Numeric | 00000011000011
Alphanumeric | 0000000000100001011101
Byte | 00110000001100010011001000110011

很明顯,對於純數字字串,採用數字模式編碼是最優的選擇,當編碼後的字串長度越短,就代表我們所需要的碼元會越少,也就是說 QR Code 的版本可能會越小。

一般來說,我自己選擇編碼的方式是這樣的:

  1. 能用數字模式,就用數字模式,否則進入 2.
  2. 能用文數字模式,就用文數字模式,否則進入 3.
  3. 能用位元組模式,就用位元組模式,否則進入 4.
  4. 能用漢字模式,就用漢字模式,否則編碼失敗

雖然這看起來很像廢話,不過是最簡單的選擇方法,我敢說大部分的情況,照著上面的步驟可以得到最優的模式。

當然也有不那麼簡單的方法,請閱讀《ISO/IEC 18004:2015 Annex J》

選擇 QR Code 的版本

在確定編碼模式後,我們就要來選擇 QR Code 的版本了,在相同版本的情況下,因為容錯等級的不同,可供儲存的容量就會不同,所以容錯等級也是需要考量的因素之一。

為了選擇合適的版本,我們必須查表,表可以在下面三個地方找到:

題外話,在我研究 QR Code 的編碼和解碼時,標準文件和 thonky.com 幫了我相當多的忙,值得有時間的讀者閱讀(不過標準一本要價不斐就是了)。

相信讀者可以一眼看出表要怎麼查,如果不懂的話第二個連結裡有相關說明。

我們再次用 0123 舉例,設容錯等級為 H,並使用數字模式編碼。

因為 0123 的長度為 4,查表可以發現 1-H 的 QR Code 最大容量為 17,所以使用版本 1 的 QR Code 就可以滿足我們的需求。

指示器(indicator)

在決定編碼模式和版本後,我們需要在編碼後的字串前,加入相關的指示器,以供解碼時使用。

指示器共有兩種,第一個是模式指示器(mode indicator),第二個是字元長度指示器(character count indicator),表列如下。

mode indicatorMode              | Indicator
------------------+-----------
Numeric | 0001
Alphanumeric | 0010
Byte | 0100
Kanji | 1000
Structured Append | 0011
ECI | 0111
FNC1 1st | 0101
FNC1 2nd | 1001
Terminator | 0000
character count indicatorVersion | 1-9 | 10-26 | 27-40
-------------+---------+---------+---------
Numeric | 10 | 12 | 14
Alphanumeric | 9 | 11 | 13
Byte | 8 | 16 | 16
Kanji | 8 | 10 | 12

綜合一下目前為止我們得到的訊息:

Version : 1-H
Message : 0123
Mode : Numeric
Encoded : 00000011000011

一個正確的資料編碼包含:模式指示器、字元長度指示器和編碼後訊息,因為模式是數字模式,所以模式指示器的值為 0001,又因為版本為 1,所以字元長度指示器的長度為 10,說成白話就是我們要把 0123 的長度 4 轉換成長度為 10 的二進位數,也就是把 4 變成 0b0000000100。

所以根據上面的規則,我們的 0123 會變成:

0001、0000000100、00000011000011

以下提供些許範例供讀者參考,模式和版本選擇方法如先前所述,Encoded 為「模式指示器+字元長度指示器+編碼後訊息」,並且其中頓號僅為分隔之用,實際不需要加上。

EC level: M
Message : 8498929829
Encoded : 0001、0000001010、1101010001110111110011110101101001
EC level: Q
Message : BYTE
Encoded : 0010、000000100、0100001000110100100111
EC level: L
Message : Yeecy is the best!
Encoded : 0100、00010010、010110010110010101100101011000110111100100100000011010010111001100100000011101000110100001100101001000000110001001100101011100110111010000100001

終止符號(terminator)

接下來要先知道 QR Code 可以放入的碼元數量,我們可以透過查表得到,來源如下:

針對前面兩個來源,我們關注的是「data bits」欄位,針對後一個來源,我們須把「Total Number of … and EC Level」欄位的數值乘以 8(原因之後的文章會提到),才能得到可以放入的碼元數量。

查表後,對於 1-H QR Code,我們發現能放入的碼元數目為 72,觀察由 0123 得到的資料編碼:

0001000000010000000011000011

計算一下發現長度為 28,少於 72 許多,我們直接在該資料編碼的後方加入終止符號 0000,得到:

0001000000010000000011000011、0000

如果今天資料編碼的長度為 70,沒辦法放下 0000,根據標準,我們直接補兩個 0 就可以了,倘若長度為 72,那就不填入終止符號。

註:終止符號為模式指示器中的其中一個模式。

填充(padding)

假如進行到這裡,資料編碼的長度滿足「data bits」欄位的大小,那麼就不需要進行填充。

填充分成兩個部分,首先如果資料編碼的長度不為 8 的倍數,那麼就補 0 補到長度變 8 的倍數,接著再填充 11101100、00010001、11101100、00010001⋯⋯,直到長度滿足「data bits」欄位的值。

我們舉簡單一點的例子,假設資料編碼為:

010000

因為長度僅為 6,我們把他補到長度為 8:

01000000

程式的實現中,可以使用 8 - len(x)&7 來得到需要補多少個 0,這個寫法是我在 redis 的原始碼中看到的,非常厲害。

接著我們假設「data bits」欄位的值為 32,那麼填充完的結果會變成:

01000000、11101100、00010001、11101100

如此一來就完成資料編碼囉!

完整範例

假設容錯等級為 Q,欲編碼訊息為:

YEECY

觀察訊息,選擇使用文數字模式。

查表,發現 1-Q 的 QR Code 在文數字模式下可以容納 16 個字元,由於 YEECY 的長度為 5,所以確定採用的 QR Code 版本為 1。

文數字模式的模式指示器為:

0010

且字元長度指示器為:

9

所以我們把 YEECY 的長度 5 轉換成長度為 9 的二進位表示,得到:

000000101

再用文數字模式編碼 YEECY,得到:

1100000100001010000010100010

綜合起來:

00100000001011100000100001010000010100010

並且可以算出訊息長度為 41。

接著查詢版本 1 QR Code 的可容納碼元數,得到結果為:

104

由於長度為 41,小於 104,可以填入終止符號:

001000000010111000001000010100000101000100000

因為訊息長度為 45,不為 8 的倍數,我們補 0 得:

001000000010111000001000010100000101000100000000

現在的長度為 48,我們補 11101100、00010001 直到長度變成 104:

00100000001011100000100001010000010100010000000011101100000100011110110000010001111011000001000111101100

就這樣,資料編碼完成。

結語

這篇文章中的 0 和 1 有點多,寫到眼睛有點花,如果讀者發現內容有誤的話,請留言告訴我。

至於打算按照文章實現程式的讀者,我建議把需要的表格存成檔案,再寫一個類別來管理資料的讀取,不然寫到最後你的思緒和程式碼都會很亂。

下一篇文章將會介紹如何根據得到的資料編碼,計算出相應的錯誤校正碼。

感謝你的閱讀,我是 Yeecy,我們下一篇文章見。

--

--

Yeecy

"What I cannot create, I do not understand. Know how to solve every problem that has been solved." - Richard Feynman