mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-12-15 01:43:49 +01:00
309 lines
6.8 KiB
Go
309 lines
6.8 KiB
Go
// go-qrcode
|
|
// Copyright 2014 Tom Harwood
|
|
|
|
package qrcode
|
|
|
|
// symbol is a 2D array of bits representing a QR Code symbol.
|
|
//
|
|
// A symbol consists of size*size modules, with each module normally drawn as a
|
|
// black or white square. The symbol also has a border of quietZoneSize modules.
|
|
//
|
|
// A (fictional) size=2, quietZoneSize=1 QR Code looks like:
|
|
//
|
|
// +----+
|
|
// | |
|
|
// | ab |
|
|
// | cd |
|
|
// | |
|
|
// +----+
|
|
//
|
|
// For ease of implementation, the functions to set/get bits ignore the border,
|
|
// so (0,0)=a, (0,1)=b, (1,0)=c, and (1,1)=d. The entire symbol (including the
|
|
// border) is returned by bitmap().
|
|
//
|
|
type symbol struct {
|
|
// Value of module at [y][x]. True is set.
|
|
module [][]bool
|
|
|
|
// True if the module at [y][x] is used (to either true or false).
|
|
// Used to identify unused modules.
|
|
isUsed [][]bool
|
|
|
|
// Combined width/height of the symbol and quiet zones.
|
|
//
|
|
// size = symbolSize + 2*quietZoneSize.
|
|
size int
|
|
|
|
// Width/height of the symbol only.
|
|
symbolSize int
|
|
|
|
// Width/height of a single quiet zone.
|
|
quietZoneSize int
|
|
}
|
|
|
|
// newSymbol constructs a symbol of size size*size, with a border of
|
|
// quietZoneSize.
|
|
func newSymbol(size int, quietZoneSize int) *symbol {
|
|
var m symbol
|
|
|
|
m.module = make([][]bool, size+2*quietZoneSize)
|
|
m.isUsed = make([][]bool, size+2*quietZoneSize)
|
|
|
|
for i := range m.module {
|
|
m.module[i] = make([]bool, size+2*quietZoneSize)
|
|
m.isUsed[i] = make([]bool, size+2*quietZoneSize)
|
|
}
|
|
|
|
m.size = size + 2*quietZoneSize
|
|
m.symbolSize = size
|
|
m.quietZoneSize = quietZoneSize
|
|
|
|
return &m
|
|
}
|
|
|
|
// get returns the module value at (x, y).
|
|
func (m *symbol) get(x int, y int) (v bool) {
|
|
v = m.module[y+m.quietZoneSize][x+m.quietZoneSize]
|
|
return
|
|
}
|
|
|
|
// empty returns true if the module at (x, y) has not been set (to either true
|
|
// or false).
|
|
func (m *symbol) empty(x int, y int) bool {
|
|
return !m.isUsed[y+m.quietZoneSize][x+m.quietZoneSize]
|
|
}
|
|
|
|
// numEmptyModules returns the number of empty modules.
|
|
//
|
|
// Initially numEmptyModules is symbolSize * symbolSize. After every module has
|
|
// been set (to either true or false), the number of empty modules is zero.
|
|
func (m *symbol) numEmptyModules() int {
|
|
var count int
|
|
for y := 0; y < m.symbolSize; y++ {
|
|
for x := 0; x < m.symbolSize; x++ {
|
|
if !m.isUsed[y+m.quietZoneSize][x+m.quietZoneSize] {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// set sets the module at (x, y) to v.
|
|
func (m *symbol) set(x int, y int, v bool) {
|
|
m.module[y+m.quietZoneSize][x+m.quietZoneSize] = v
|
|
m.isUsed[y+m.quietZoneSize][x+m.quietZoneSize] = true
|
|
}
|
|
|
|
// set2dPattern sets a 2D array of modules, starting at (x, y).
|
|
func (m *symbol) set2dPattern(x int, y int, v [][]bool) {
|
|
for j, row := range v {
|
|
for i, value := range row {
|
|
m.set(x+i, y+j, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// bitmap returns the entire symbol, including the quiet zone.
|
|
func (m *symbol) bitmap() [][]bool {
|
|
module := make([][]bool, len(m.module))
|
|
|
|
for i := range m.module {
|
|
module[i] = m.module[i][:]
|
|
}
|
|
|
|
return module
|
|
}
|
|
|
|
// string returns a pictorial representation of the symbol, suitable for
|
|
// printing in a TTY.
|
|
func (m *symbol) string() string {
|
|
var result string
|
|
|
|
for _, row := range m.module {
|
|
for _, value := range row {
|
|
switch value {
|
|
case true:
|
|
result += " "
|
|
case false:
|
|
// Unicode 'FULL BLOCK' (U+2588).
|
|
result += "██"
|
|
}
|
|
}
|
|
result += "\n"
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Constants used to weight penalty calculations. Specified by ISO/IEC
|
|
// 18004:2006.
|
|
const (
|
|
penaltyWeight1 = 3
|
|
penaltyWeight2 = 3
|
|
penaltyWeight3 = 40
|
|
penaltyWeight4 = 10
|
|
)
|
|
|
|
// penaltyScore returns the penalty score of the symbol. The penalty score
|
|
// consists of the sum of the four individual penalty types.
|
|
func (m *symbol) penaltyScore() int {
|
|
return m.penalty1() + m.penalty2() + m.penalty3() + m.penalty4()
|
|
}
|
|
|
|
// penalty1 returns the penalty score for "adjacent modules in row/column with
|
|
// same colour".
|
|
//
|
|
// The numbers of adjacent matching modules and scores are:
|
|
// 0-5: score = 0
|
|
// 6+ : score = penaltyWeight1 + (numAdjacentModules - 5)
|
|
func (m *symbol) penalty1() int {
|
|
penalty := 0
|
|
|
|
for x := 0; x < m.symbolSize; x++ {
|
|
lastValue := m.get(x, 0)
|
|
count := 1
|
|
|
|
for y := 1; y < m.symbolSize; y++ {
|
|
v := m.get(x, y)
|
|
|
|
if v != lastValue {
|
|
count = 1
|
|
lastValue = v
|
|
} else {
|
|
count++
|
|
if count == 6 {
|
|
penalty += penaltyWeight1 + 1
|
|
} else if count > 6 {
|
|
penalty++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for y := 0; y < m.symbolSize; y++ {
|
|
lastValue := m.get(0, y)
|
|
count := 1
|
|
|
|
for x := 1; x < m.symbolSize; x++ {
|
|
v := m.get(x, y)
|
|
|
|
if v != lastValue {
|
|
count = 1
|
|
lastValue = v
|
|
} else {
|
|
count++
|
|
if count == 6 {
|
|
penalty += penaltyWeight1 + 1
|
|
} else if count > 6 {
|
|
penalty++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return penalty
|
|
}
|
|
|
|
// penalty2 returns the penalty score for "block of modules in the same colour".
|
|
//
|
|
// m*n: score = penaltyWeight2 * (m-1) * (n-1).
|
|
func (m *symbol) penalty2() int {
|
|
penalty := 0
|
|
|
|
for y := 1; y < m.symbolSize; y++ {
|
|
for x := 1; x < m.symbolSize; x++ {
|
|
topLeft := m.get(x-1, y-1)
|
|
above := m.get(x, y-1)
|
|
left := m.get(x-1, y)
|
|
current := m.get(x, y)
|
|
|
|
if current == left && current == above && current == topLeft {
|
|
penalty++
|
|
}
|
|
}
|
|
}
|
|
|
|
return penalty * penaltyWeight2
|
|
}
|
|
|
|
// penalty3 returns the penalty score for "1:1:3:1:1 ratio
|
|
// (dark:light:dark:light:dark) pattern in row/column, preceded or followed by
|
|
// light area 4 modules wide".
|
|
//
|
|
// Existence of the pattern scores penaltyWeight3.
|
|
func (m *symbol) penalty3() int {
|
|
penalty := 0
|
|
|
|
for y := 0; y < m.symbolSize; y++ {
|
|
var bitBuffer int16 = 0x00
|
|
|
|
for x := 0; x < m.symbolSize; x++ {
|
|
bitBuffer <<= 1
|
|
if v := m.get(x, y); v {
|
|
bitBuffer |= 1
|
|
}
|
|
|
|
switch bitBuffer & 0x7ff {
|
|
// 0b000 0101 1101 or 0b10111010000
|
|
// 0x05d or 0x5d0
|
|
case 0x05d, 0x5d0:
|
|
penalty += penaltyWeight3
|
|
bitBuffer = 0xFF
|
|
default:
|
|
if x == m.symbolSize-1 && (bitBuffer&0x7f) == 0x5d {
|
|
penalty += penaltyWeight3
|
|
bitBuffer = 0xFF
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for x := 0; x < m.symbolSize; x++ {
|
|
var bitBuffer int16 = 0x00
|
|
|
|
for y := 0; y < m.symbolSize; y++ {
|
|
bitBuffer <<= 1
|
|
if v := m.get(x, y); v {
|
|
bitBuffer |= 1
|
|
}
|
|
|
|
switch bitBuffer & 0x7ff {
|
|
// 0b000 0101 1101 or 0b10111010000
|
|
// 0x05d or 0x5d0
|
|
case 0x05d, 0x5d0:
|
|
penalty += penaltyWeight3
|
|
bitBuffer = 0xFF
|
|
default:
|
|
if y == m.symbolSize-1 && (bitBuffer&0x7f) == 0x5d {
|
|
penalty += penaltyWeight3
|
|
bitBuffer = 0xFF
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return penalty
|
|
}
|
|
|
|
// penalty4 returns the penalty score...
|
|
func (m *symbol) penalty4() int {
|
|
numModules := m.symbolSize * m.symbolSize
|
|
numDarkModules := 0
|
|
|
|
for x := 0; x < m.symbolSize; x++ {
|
|
for y := 0; y < m.symbolSize; y++ {
|
|
if v := m.get(x, y); v {
|
|
numDarkModules++
|
|
}
|
|
}
|
|
}
|
|
|
|
numDarkModuleDeviation := numModules/2 - numDarkModules
|
|
if numDarkModuleDeviation < 0 {
|
|
numDarkModuleDeviation *= -1
|
|
}
|
|
|
|
return penaltyWeight4 * (numDarkModuleDeviation / (numModules / 20))
|
|
}
|