Telegram authentication requires you to open a chat on the phone. It's convenient to have a QR code for the case when you want to log in on the computer but have Telegram only on your phone and would be able to scan the QR instead of copy-pasting the link from the computer to the phone any other way. Originally we thought of generating QR on the client but found backend-generated QR a better alternative because we avoid adding one more JavaScript dependency to the frontend that way.
310 lines
6.8 KiB
Go
310 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))
|
|
}
|