status-go/vendor/github.com/yeqown/go-qrcode/v2/mask_evaluation.go

173 lines
4.5 KiB
Go
Raw Normal View History

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
}