-
Notifications
You must be signed in to change notification settings - Fork 89
/
mask_evaluation.go
172 lines (148 loc) · 4.5 KB
/
mask_evaluation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package qrcode
import (
"math"
)
// evaluation calculate a score after masking matrix.
//
// reference:
// - https://www.thonky.com/qr-code-tutorial/data-masking#Determining-the-Best-Mask
func evaluation(mat *Matrix) (score int) {
debugLogf("calculate maskScore starting")
score1 := rule1(mat)
score2 := rule2(mat)
score3 := rule3(mat)
score4 := rule4(mat)
score = score1 + score2 + score3 + score4
debugLogf("maskScore: rule1=%d, rule2=%d, rule3=%d, rule4=%d", score1, score2, score3, score4)
return score
}
// check each row one-by-one. If there are five consecutive modules of the same color,
// add 3 to the penalty. If there are more modules of the same color after the first five,
// add 1 for each additional module of the same color. Afterward, check each column one-by-one,
// checking for the same condition. Add the horizontal and vertical total to obtain penalty score
func rule1(mat *Matrix) (score int) {
// prerequisites:
// mat.Width() == mat.Height()
if mat.Width() != mat.Height() {
debugLogf("matrix width != height, skip rule1")
return math.MaxInt32
}
dimension := mat.Width()
scoreLine := func(arr []qrvalue) int {
lScore, cnt, cur := 0, 0, QRValue_INIT_V0
for _, v := range arr {
if !samestate(v, cur) {
cur = v
cnt = 1
continue
}
cnt++
if cnt == 5 {
lScore += 3
} else if cnt > 5 {
lScore++
}
}
return lScore
}
for cur := 0; cur < dimension; cur++ {
row := mat.Row(cur)
col := mat.Col(cur)
score += scoreLine(row)
score += scoreLine(col)
}
return score
}
// rule2
// look for areas of the same color that are at least 2x2 modules or larger.
// The QR code specification says that for a solid-color block of size m × n,
// the penalty score is 3 × (m - 1) × (n - 1).
func rule2(mat *Matrix) int {
var (
score int
s0, s1, s2, s3 qrvalue
)
for x := 0; x < mat.Width()-1; x++ {
for y := 0; y < mat.Height()-1; y++ {
s0, _ = mat.at(x, y)
s1, _ = mat.at(x+1, y)
s2, _ = mat.at(x, y+1)
s3, _ = mat.at(x+1, y+1)
if s0 == s1 && s2 == s3 && s1 == s2 {
score += 3
}
}
}
return score
}
// rule3 calculate punishment score in rule3, find pattern in QR Code matrix.
// Looks for patterns of dark-light-dark-dark-dark-light-dark that have four
// light modules on either side. In other words, it looks for any of the
// following two patterns: 1011101 0000 or 0000 1011101.
//
// Each time this pattern is found, add 40 to the penalty score.
func rule3(mat *Matrix) (score int) {
var (
pattern1 = binaryToQRValueSlice("1011101 0000")
pattern2 = binaryToQRValueSlice("0000 1011101")
pattern1Next = kmpGetNext(pattern1)
pattern2Next = kmpGetNext(pattern2)
)
// prerequisites:
//
// mat.Width() == mat.Height()
if mat.Width() != mat.Height() {
debugLogf("rule3 got matrix but not matched prerequisites")
return math.MaxInt32
}
dimension := mat.Width()
for i := 0; i < dimension; i++ {
col := mat.Col(i)
row := mat.Row(i)
// DONE(@yeqown): statePattern1 and statePattern2 are fixed, so maybe kmpGetNext
// could cache result to speed up.
score += 40 * kmp(col, pattern1, pattern1Next)
score += 40 * kmp(col, pattern2, pattern2Next)
score += 40 * kmp(row, pattern1, pattern1Next)
score += 40 * kmp(row, pattern2, pattern2Next)
}
return score
}
// rule4 is based on the ratio of light modules to dark modules:
//
// 1. Count the total number of modules in the matrix.
// 2. Count how many dark modules there are in the matrix.
// 3. Calculate the percent of modules in the matrix that are dark: (darkmodules / totalmodules) * 100
// 4. Determine the previous and next multiple of five of this percent.
// 5. Subtract 50 from each of these multiples of five and take the absolute qrbool of the result.
// 6. Divide each of these by five. For example, 10/5 = 2 and 5/5 = 1.
// 7. Finally, take the smallest of the two numbers and multiply it by 10.
//
func rule4(mat *Matrix) int {
// prerequisites:
//
// mat.Width() == mat.Height()
if mat.Width() != mat.Height() {
debugLogf("rule4 got matrix but not matched prerequisites")
return math.MaxInt32
}
dimension := mat.Width()
dark, total := 0, dimension*dimension
for i := 0; i < dimension; i++ {
col := mat.Col(i)
// count dark modules
for j := 0; j < dimension; j++ {
if samestate(col[j], QRValue_DATA_V1) {
dark++
}
}
}
ratio := (dark * 100) / total // in range [0, 100]
step := 0
if ratio%5 == 0 {
step = 1
}
previous := abs((ratio/5-step)*5 - 50)
next := abs((ratio/5+1-step)*5 - 50)
return min(previous, next) / 5 * 10
}