173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
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
|
||
}
|