Skip to content

Commit

Permalink
refactor(qrcode): optimise masking stage-2 (#55)
Browse files Browse the repository at this point in the history
* feat: matrix.Iterate with COLUMN is preferred.

* docs(qrcode): add comment to qrcode masking evaluation conditions.

* styl(qrcode): split utilities functions

* styl(qrcode): reduce fillDataBinary calls
  • Loading branch information
yeqown authored Dec 23, 2021
1 parent 3bd5445 commit 2729998
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 250 deletions.
2 changes: 1 addition & 1 deletion debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func debugDrawTo(w io.Writer, mat matrix.Matrix) error {
// background
rectangle(0, 0, width, height, img, color.White)

mat.Iterate(matrix.ROW, func(x int, y int, v matrix.State) {
mat.Iterate(matrix.COLUMN, func(x int, y int, v matrix.State) {
sx := x*blockWidth + padding
sy := y*blockWidth + padding
es := (x+1)*blockWidth + padding
Expand Down
213 changes: 1 addition & 212 deletions mask.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,202 +27,6 @@ const (
modulo7
)

// calculateScore calculate the maskScore of masking result ...
func calculateScore(mat *matrix.Matrix) int {
debugLogf("calculate maskScore starting")
score1 := rule1(mat.Copy())
score2 := rule2(mat.Copy())
score3 := rule3(mat.Copy())
score4 := rule4(mat.Copy())

debugLogf("maskScore: %d", score1+score2+score3+score4)
return score1 + score2 + score3 + score4
}

// 第一条规则为一行(或列)中的每组五个或更多相同颜色的模块提供QR代码。
func rule1(mat *matrix.Matrix) int {
// Row socre
var (
score int
rowCurState matrix.State
rowCurColorCnt int

colCurState matrix.State
colCurColorCnt int
)

mat.Iterate(matrix.ROW, func(x, y int, value matrix.State) {
if x == 0 {
rowCurColorCnt = 0
rowCurState = value
return
}

if value == rowCurState {
rowCurColorCnt++
} else {
rowCurState = value
}

if rowCurColorCnt == 5 {
score += 3
} else if rowCurColorCnt > 5 {
score++
}
})

// column
mat.Iterate(matrix.COLUMN, func(x, y int, value matrix.State) {
if x == 0 {
colCurColorCnt = 0
colCurState = value
return
}

if value == colCurState {
colCurColorCnt++
} else {
colCurState = value
}

if colCurColorCnt == 5 {
score += 3
} else if colCurColorCnt > 5 {
score++
}
})
return score
}

// 第二个规则给出了QR码对矩阵中相同颜色模块的每个2x2区域的惩罚。
func rule2(mat *matrix.Matrix) int {
var (
score int
s0, s1, s2, s3 matrix.State
)
for x := 0; x < mat.Width()-1; x++ {
for y := 0; y < mat.Height()-1; y++ {
s0, _ = mat.Get(x, y)
s1, _ = mat.Get(x+1, y)
s2, _ = mat.Get(x, y+1)
s3, _ = mat.Get(x+1, y+1)

if s0 == s1 && s2 == s3 && s1 == s2 {
score += 3
}
}
}

return score
}

// 如果存在看起来类似于取景器模式的模式,则第三规则给QR码一个大的惩罚
// dark-light-dark-dark-dark-light-dark
// 1011101 0000 or 0000 1011101
//func rule3_backup(mat *matrix.Matrix) (score int) {
// for y := 0; y < mat.Height(); y++ {
// for x := 0; x < mat.Width()-11; x++ {
// stateSlice := make([]matrix.State, 0, 11)
// for i := 0; i < 11; i++ {
// s, _ := mat.Get(x+i, y)
// stateSlice = append(stateSlice, s)
// }
// if matrix.StateSliceMatched(statePattern1, stateSlice) {
// score += 40
// }
// if matrix.StateSliceMatched(statePattern2, stateSlice) {
// score += 40
// }
// }
// }
//
// for x := 0; x < mat.Width(); x++ {
// for y := 0; y < mat.Height()-11; y++ {
// stateSlice := make([]matrix.State, 0, 11)
// for i := 0; i < 11; i++ {
// s, _ := mat.Get(x, y+i)
// stateSlice = append(stateSlice, s)
// }
// if matrix.StateSliceMatched(statePattern1, stateSlice) {
// score += 40
// }
// if matrix.StateSliceMatched(statePattern2, stateSlice) {
// score += 40
// }
// }
// }
//
// return score
//}

// rule3 calculate punishment score in rule3, find pattern in QR Code matrix.
func rule3(mat *matrix.Matrix) (score int) {
var (
pattern1 = binaryToStateSlice("1011101 0000")
pattern2 = binaryToStateSlice("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")
}
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
}

// 如果超过一半的模块是暗的或轻的,则第四规则给QR码一个惩罚,对较大的差异有较大的惩罚
func rule4(mat *matrix.Matrix) int {
var (
totalCnt = mat.Width() * mat.Height()
darkCnt, darkPercent int
)
mat.Iterate(matrix.ROW, func(x, y int, s matrix.State) {
if s == matrix.StateTrue {
darkCnt++
}
})
darkPercent = (darkCnt * 100) / totalCnt
x := 0
if darkPercent%5 == 0 {
x = 1
}
last5Times := abs(((darkPercent/5)-x)*5 - 50)
next5Times := abs(((darkPercent/5)+1)*5 - 50)

// get the min maskScore
if last5Times > next5Times {
// scoreC <- next5Times / 5 * 10
return next5Times * 2
} else {
return last5Times * 2
}

}

func abs(x int) int {
if x < 0 {
return -x
}
return x
}

type mask struct {
mat *matrix.Matrix // matrix
mode maskPatternModulo // mode
Expand Down Expand Up @@ -275,7 +79,7 @@ func (m *mask) masking() {
panic("impossible panic, contact maintainer plz")
}

m.mat.Iterate(matrix.ROW, func(x, y int, s matrix.State) {
m.mat.Iterate(matrix.COLUMN, func(x, y int, s matrix.State) {
// skip the function modules
if state, _ := m.mat.Get(x, y); state != matrix.StateInit {
_ = m.mat.Set(x, y, matrix.StateInit)
Expand Down Expand Up @@ -336,18 +140,3 @@ func modulo6Func(x, y int) bool {
func modulo7Func(x, y int) bool {
return ((x+y)%2+(x*y)%3)%2 == 0
}

func binaryToStateSlice(s string) []matrix.State {
var states = make([]matrix.State, 0, len(s))
for _, c := range s {
switch c {
case '1':
states = append(states, matrix.StateTrue)
case '0':
states = append(states, matrix.StateFalse)
default:
continue
}
}
return states
}
Loading

0 comments on commit 2729998

Please sign in to comment.