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

173 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}