如何製作 QR Code #9:資料遮罩與完成圖片

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

Yeecy
9 min readAug 25, 2020

本文是生成篇的最後一篇囉,我們會在這裡認識 8 種遮罩的樣式,了解如何挑選最適合的遮罩,並完成完整圖片。

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

資料遮罩(data mask)

遮罩樣式

這次我們要來認真講解上一篇文章沒講到的 condition 欄位。

mask pattern  mask |         condition
-------+----------------------------
000 | (r + c) % 2 = 0
001 | r % 2 = 0
010 | c % 3 = 0
011 | (r + c) % 3 = 0
100 | (c//3 + r//2) % 2 = 0
101 | (r * c)%2 + (r * c)%3 = 0
110 | ((r * c)%3 + r*c) % 2 = 0
111 | ((r * c)%3 + r + c) % 2 = 0

上面式子中的 // 表示整除,* 表示乘法,% 表示模除。

這時候可能有讀者會好奇,怎麼這張表對應到的式子,跟在其他網站上看到的不同。

原因很簡單,因為在上一篇文章中,我們提到格式資訊需要套用格式資訊的遮罩,上表的編號是未經過遮罩處理的,而其他網站上的是已經經過遮罩處理的,而容錯等級會不同也是一樣的原因。

我們看 010 對應的 c % 3 = 0 ,我們假設 (r, c, m) 的值為 (0, 0, 1),首先,我們看到 c 的值為 0,我們計算 0 % 3,得值為 0,接著再評估 0 = 0,可以知道這個敘述為真(true),所以我們應該把 m 的值翻轉,改成 0,也就是 (0, 0, 1),套用 010 遮罩後,會改變為 (0, 0, 0)。

因為敘述為真才翻轉,我們把真視為 1,假(false)視為 0,那麼經過處理的 (r, c, m) 可以敘述為:

(r, c, XOR(m, mask(r, c))

令 mask = (c % 3 = 0),我們重新描述前面的例子,可以得到:

(0, 0, XOR(1, (c % 3 = 0)(0, 0)))

整理一下得:

(0, 0, XOR(1, 0 % 3 = 0))

因為 0 % 3 為 0,且 0 = 0 為真,得:

(0, 0, XOR(1, 1))

又因為 XOR(1, 1) 為 0,我們得到套用遮罩後的新 (r, c, m) 為:

(0, 0, 0)

再舉一個例子,假設 (r, c, m) = (14, 23, 1),使用遮罩 100,我們知道:

(r, c, XOR(m, mask(r, c)) = (14, 23, XOR(1, ((c//3 + r//2) % 2 = 0)(14, 23)))

先整理可得:

(14, 23, XOR(1, ((23//3 + 14//2) % 2 = 0))

接著計算等號左邊:

(14, 23, XOR(1, (0 = 0))

接著判斷等式真假:

(14, 23, XOR(1, 1))

經 XOR 運算後可以得到:

(14, 23, 0)

雖然看起來很複雜,但是程式寫起來是很輕鬆的,只需要短短幾行而已,接下來讓我們看看各個遮罩本身的樣子吧!

000,(r + c) % 2 = 0
001,r % 2 = 0
010, c % 3 = 0
011,(r + c) % 3 = 0
100,(c//3 + r//2) % 2 = 0
101,(r * c)%2 + (r * c)%3 = 0
110,((r * c)%3 + r*c) % 2 = 0
111,((r * c)%3 + r + c) % 2 = 0

套用遮罩

要套用遮罩非常簡單,如果上面有看懂的話就會知道,只需要用雙重迴圈來執行異或運算就可以辦到了,記得不要把遮罩套用到功能圖案、版本資訊和格式資訊喔!

附圖為上一篇文章文末範例,套用遮罩 000 的情況。

選擇最合適遮罩

基本上,不管套用哪一個遮罩,製作出來的 QR Code 都是可以使用的,讀者可以試著掃描以下八張內容都為Yeecy is the best!,但是遮罩不同的 QR Code。

遮罩:000
遮罩:001
遮罩:010
遮罩:011
遮罩:100
遮罩:101
遮罩:110
遮罩:111

雖然八張 QR Code 都是合格的,不過標準規定了一個流程,來決定該使用哪張 QR Code。

要挑選出一張最好的 QR Code 的原因,主要是為了 QR Code 能被辨識的更加清楚。

那要怎麼挑選呢?我們要幫 QR Code 打分數。標準敘明,我們要針對四項特徵進行評分,對八個 QR Code 都評分後,挑選四項評分總和最低的 QR Code 做為最優的 QR Code。

特徵一:連續同色碼元

當水平或垂直方向出現連續五個同顏色的碼元,則分數一加 3,當出現超過五個連續同色碼元時,每多一個,則分數多加 1。

■ ■ ■ ■ ■ ■ ■ □ □ □ ■ ■ □ □ □
-------------

以上面的例子來說,因為出現了 7 個連續的 ■,所以分數為基本分的 3 分,加上超出兩個所得的額外 2 分,共取得 5 分。

特徵二:2×2 同色方塊

當我們在圖片中找到一個 2×2 的同色方塊,分數二就加 3 分,假設存在一個 2×3 的同色方塊,應該將其視為兩個 2×2 的同色方塊計算。

■ ■ □ □ □
■ ■ □ □ □
■ ■ □ □ □
□ ■ □ ■ ■
□ □ □ □ □

我們從上面的例子中,可以找到 2 個 2×2 的 ■ 方塊,和 4 個 2×2 的 □ 方塊,所以分數二為 6×3 = 18 分。

特徵三:■□■■■□■ 圖案

當我們在圖片的水平或垂直方向發現 □□□□■□■■■□■ 或 ■□■■■□■□□□□ 的圖案時,分數三加 40 分。

■ ■ □ □ □ □ □ □ ■ □ ■ ■ ■ □ ■ □ □ □ □ □ ■ ■
---------------------
---------------------

如上所示,因為出現了兩個指定圖案,所以分數三應為 40×2 = 80 分。

特徵四:黑色碼元佔比

首先,我們計算黑色碼元的總數和白色碼元的總數,然後計算黑色碼元佔全體碼元的百分比,接下來以 5% 為級距,計算該百分比與 50% 差了幾個級距,若差距級距數為小數,則無條件捨去,最後將相差級距的數量乘上 10,即為分數四。

■ ■ □ □ □
■ ■ □ □ □
■ ■ □ □ □
□ ■ □ ■ ■
□ □ □ □ □

我們以 ■ 為黑色碼元,□ 為白色碼元,則上圖的黑色碼元數為 9,總碼元數為 25,計算可得黑色碼元佔全體碼元的百分比為 36%,因為 36% 與 50% 相距 2.8 個級距,將 0.8 無條件捨去得到 2 ,再乘上 10,得到分數四為 20。

挑選 QR Code:以上面八張圖為例

為了不重複貼圖占版面,以下列出上面八張 QR Code 的各項分數:

Version: 2-L
Mode: Byte (Latin-1)
Message: Yeecy is the best!
Score
Mask 1 2 3 4 tot
000 211 219 80 0 500
001 209 159 120 0 488
010 246 171 160 0 577
011 242 198 40 0 480
100 245 174 0 0 419 ✓
101 241 195 120 0 556
110 248 195 40 0 483
111 247 228 40 0 515

從上表可知,我們應該挑選遮罩為 100 的 QR Code 作為最終圖片。

最終的贏家:遮罩 100

靜默區域(quiet zone)

選出最後的 QR Code 後,我們必須在其外圍加上寬度為 4 個白色碼元的靜默區域。

下圖即為加上靜默區域的最終成品,對於白色背景的讀者,可以將背景改成黑色,或是透過下載圖片來看到外圍加上的白色靜默區域。

結語

經過了九篇文章,從剛開始認識各個組成圖案,到完成添加靜默區域,現在,我們可以驕傲地跟別人說,我們完成屬於自己的 QR Code 產生器了!

希望讀者能喜歡這一系列文章,至此,跟產生 QR Code 相關的文章都已經完成了,至於解碼,事實上就是把產生 QR Code 的過程反著做而已,相信這個精神讀者已經在先前的基本編碼模式中就領略過了。

解碼篇目前安排有四篇,不過可能永遠都不會完成了,就我目前對 Medium 後臺數據的觀察,發現絕大多數人對 QR Code 的原理不感興趣,寫了大概也是白寫,說不定這整個系列文都是白寫吧,誰知道呢?

如果 Medium 看不到有多少人閱讀過文章,或許我還會充滿動力的把全部文章寫出來,看著花了自己這麼多時間的心血結晶卻乏人問津,這種感覺確實不好受,整個系列文截至目前為止,花了我大約 120 小時,其中不含自己實現 QR Code 產生器、解碼器和查閱資料的時間。耗費了這麼多時間的文章,沒人閱讀更沒人支持,任誰都很難再繼續寫下去吧。

說來也諷刺,一開始明明就是抱著沒人看也沒關係的心態在寫文章,沒想到現在發現其實事實並不是這樣,只要是人,都會希望得到別人肯定的,不過,這幾篇文章都是以 Yeecy 的最高規格呈現給來自四面八方的看客們,並沒有因此而讓文章品質下降,老實說,我還滿喜歡自己這幾篇文章的呢!

下一篇文章將會展示如何從圖片中辨識出 QR Code,並將其提取出來,雖然方法不怎麼好就是了。

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

--

--

Yeecy
Yeecy

Written by Yeecy

A Ph.D. student at NYCU CS and a compiler engineer at ICEshell Co., Ltd. You can find more information about me on my GitHub page github.com/ADNRs.

Responses (1)