890 lines
27 KiB
Python
890 lines
27 KiB
Python
|
#!/usr/bin/env python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
import numpy as np
|
||
|
import pygame
|
||
|
import time
|
||
|
|
||
|
S_EMPTY = 1
|
||
|
S_SEP = 2
|
||
|
S_FINDER = 4
|
||
|
S_ALIGN = 8
|
||
|
S_TIMING = 16
|
||
|
S_RESERVED = 32
|
||
|
S_DATA = 64
|
||
|
S_BYTES = 128
|
||
|
S_MASK = 256
|
||
|
|
||
|
STEPS = S_MASK|S_DATA|S_BYTES|S_RESERVED|S_ALIGN|S_TIMING|S_FINDER|S_SEP|S_EMPTY
|
||
|
#STEPS = S_MASK|S_RESERVED|S_ALIGN|S_TIMING|S_FINDER|S_EMPTY
|
||
|
#STEPS = 0
|
||
|
#STEPS = S_MASK|S_DATA
|
||
|
|
||
|
pygame.init()
|
||
|
font = pygame.font.SysFont("ubuntu", 16)
|
||
|
win = None
|
||
|
coords = True # turn on coordinates
|
||
|
|
||
|
def log(msg):
|
||
|
print(f"<[---]> {msg} <[---]>")
|
||
|
|
||
|
class GF:
|
||
|
"""Galois field element"""
|
||
|
|
||
|
def __init__(self, val):
|
||
|
self.val = val
|
||
|
|
||
|
def copy(self):
|
||
|
return GF(self.val)
|
||
|
|
||
|
# Addition
|
||
|
def __add__(self, n):
|
||
|
return GF(self.val ^ n.val)
|
||
|
|
||
|
# Subtraction
|
||
|
def __sub__(self, n):
|
||
|
return GF(self.val ^ n.val)
|
||
|
|
||
|
# Multiplication
|
||
|
def __mul__(self, n):
|
||
|
if self.val == 0 or n.val == 0:
|
||
|
return GF(0)
|
||
|
|
||
|
return GF.EXP[GF.LOG[self.val].val + GF.LOG[n.val].val].copy()
|
||
|
|
||
|
# Division
|
||
|
def __truediv__(self, n):
|
||
|
if n.val == 0:
|
||
|
raise ZeroDivisionError
|
||
|
if self.val == 0:
|
||
|
return GF(0)
|
||
|
|
||
|
return GF.EXP[(GF.LOG[self.val].val + 255 - GF.LOG[n.val].val)%255].copy()
|
||
|
|
||
|
# Power
|
||
|
def __pow__(self, n):
|
||
|
return GF.EXP[(GF.LOG[self.val].val * n.val)%255].copy()
|
||
|
|
||
|
# Representation -> string
|
||
|
def __repr__(self):
|
||
|
return self.val.__repr__()
|
||
|
|
||
|
# Compute exponents and logs for all element of the Galois field
|
||
|
GF.EXP = [GF(0)]*512
|
||
|
GF.LOG = [GF(0)]*256
|
||
|
value = 1
|
||
|
for exponent in range(255):
|
||
|
GF.LOG[value] = GF(exponent)
|
||
|
GF.EXP[exponent] = GF(value)
|
||
|
value = ((value << 1) ^ 285) if value > 127 else value << 1
|
||
|
|
||
|
for i in range(255, 512):
|
||
|
GF.EXP[i] = GF.EXP[i-255].copy()
|
||
|
|
||
|
|
||
|
class Poly:
|
||
|
"""
|
||
|
Polynomial
|
||
|
|
||
|
Coefficients are in the order of largest to lowest degree:
|
||
|
ax^2 + bx + c -> coefs = [a, b, c]
|
||
|
"""
|
||
|
|
||
|
def __init__(self, coefs):
|
||
|
self.coefs = coefs.copy()
|
||
|
|
||
|
@property
|
||
|
def deg(self):
|
||
|
return len(self.coefs)
|
||
|
|
||
|
def copy(self):
|
||
|
return Poly(self.coefs)
|
||
|
|
||
|
# Addition
|
||
|
def __add__(self, p):
|
||
|
d1, d2 = self.deg, p.deg
|
||
|
deg = max(d1,d2)
|
||
|
result = [GF(0) for i in range(deg)]
|
||
|
|
||
|
for i in range(d1):
|
||
|
result[i + deg - d1] = self.coefs[i]
|
||
|
|
||
|
for i in range(d2):
|
||
|
result[i + deg - d2] += p.coefs[i]
|
||
|
|
||
|
return Poly(result)
|
||
|
|
||
|
# Multiplication
|
||
|
def __mul__(self, p):
|
||
|
result = [GF(0) for i in range(self.deg+p.deg-1)]
|
||
|
|
||
|
for i in range(p.deg):
|
||
|
for j in range(self.deg):
|
||
|
result[i+j] += self.coefs[j] * p.coefs[i]
|
||
|
|
||
|
return Poly(result)
|
||
|
|
||
|
# Division
|
||
|
def __truediv__(self, p):
|
||
|
dividend = self.coefs.copy()
|
||
|
dividend += [GF(0) for i in range(p.deg-1)]
|
||
|
quotient = []
|
||
|
|
||
|
for i in range(self.deg):
|
||
|
coef = dividend[i] / p.coefs[0]
|
||
|
quotient.append(coef)
|
||
|
|
||
|
for j in range(p.deg):
|
||
|
dividend[i+j] -= p.coefs[j] * coef
|
||
|
|
||
|
while dividend[0].val == 0:
|
||
|
dividend.pop(0)
|
||
|
|
||
|
return [Poly(quotient), Poly(dividend)]
|
||
|
|
||
|
# Representation -> string
|
||
|
def __repr__(self):
|
||
|
return f"<Poly {self.coefs}>"
|
||
|
|
||
|
# Inspired by nayuki's Creating a QR Code step by step
|
||
|
# bibtex key: nayuki_qr_js
|
||
|
# https://github.com/nayuki/Nayuki-web-published-code/blob/dfb110475327271e3b7279a432e2d1a1298815ad/creating-a-qr-code-step-by-step/creating-qr-code-steps.js
|
||
|
class History:
|
||
|
"""Widths history for mask evaluation, crit. 3"""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.widths = [0]*7
|
||
|
self.widths[-1] = 4
|
||
|
self.colors = [0]*4
|
||
|
self.color = 0
|
||
|
|
||
|
# Add module to history, returns number of patterns found
|
||
|
def add(self, col):
|
||
|
s = 0
|
||
|
self.colors.append(col)
|
||
|
if col != self.color:
|
||
|
self.color = col
|
||
|
s = self.check()
|
||
|
self.widths.pop(0)
|
||
|
self.colors = self.colors[-sum(self.widths)-1:]
|
||
|
self.widths.append(0)
|
||
|
|
||
|
self.widths[-1] += 1
|
||
|
return s
|
||
|
|
||
|
# Check for patterns in the history
|
||
|
def check(self):
|
||
|
n = self.widths[1]
|
||
|
|
||
|
# Only black on white
|
||
|
if self.colors[self.widths[0]] != 1: return 0
|
||
|
|
||
|
# if 1:1:3:1:1
|
||
|
if n > 0 and self.widths[2] == n and self.widths[3] == n*3 and self.widths[4] == n and self.widths[5] == n:
|
||
|
# check if 4:1:1:3:1:1 + check if 1:1:3:1:1:4
|
||
|
return int(self.widths[0] >= 4) + int(self.widths[6] >= 4)
|
||
|
|
||
|
return 0
|
||
|
|
||
|
# Final check
|
||
|
def final(self):
|
||
|
for i in range(4):
|
||
|
self.add(0)
|
||
|
|
||
|
return self.check()
|
||
|
|
||
|
class QR:
|
||
|
TYPES = ["numeric", "alphanumeric", "byte", "kanji", "?"]
|
||
|
LEVELS = ["L","M","Q","H", "?"]
|
||
|
MODES = ["0001", "0010", "0100", "1000"]
|
||
|
ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
||
|
VERSIONS = []
|
||
|
ERROR_CORRECTION = []
|
||
|
FINDER = ["1111111","1000001","1011101","1011101","1011101","1000001","1111111"]
|
||
|
FINDER = np.array([list(map(int, _)) for _ in FINDER])
|
||
|
|
||
|
ALIGNMENT_PATTERN_LOCATIONS = [
|
||
|
[],
|
||
|
[6, 18],
|
||
|
[6, 22],
|
||
|
[6, 26],
|
||
|
[6, 30],
|
||
|
[6, 34],
|
||
|
[6, 22, 38],
|
||
|
[6, 24, 42],
|
||
|
[6, 26, 46],
|
||
|
[6, 28, 50],
|
||
|
[6, 30, 54],
|
||
|
[6, 32, 58],
|
||
|
[6, 34, 62],
|
||
|
[6, 26, 46, 66],
|
||
|
[6, 26, 48, 70],
|
||
|
[6, 26, 50, 74],
|
||
|
[6, 30, 54, 78],
|
||
|
[6, 30, 56, 82],
|
||
|
[6, 30, 58, 86],
|
||
|
[6, 34, 62, 90],
|
||
|
[6, 28, 50, 72, 94],
|
||
|
[6, 26, 50, 74, 98],
|
||
|
[6, 30, 54, 78, 102],
|
||
|
[6, 28, 54, 80, 106],
|
||
|
[6, 32, 58, 84, 110],
|
||
|
[6, 30, 58, 86, 114],
|
||
|
[6, 34, 62, 90, 118],
|
||
|
[6, 26, 50, 74, 98, 122],
|
||
|
[6, 30, 54, 78, 102, 126],
|
||
|
[6, 26, 52, 78, 104, 130],
|
||
|
[6, 30, 56, 82, 108, 134],
|
||
|
[6, 34, 60, 86, 112, 138],
|
||
|
[6, 30, 58, 86, 114, 142],
|
||
|
[6, 34, 62, 90, 118, 146],
|
||
|
[6, 30, 54, 78, 102, 126, 150],
|
||
|
[6, 24, 50, 76, 102, 128, 154],
|
||
|
[6, 28, 54, 80, 106, 132, 158],
|
||
|
[6, 32, 58, 84, 110, 136, 162],
|
||
|
[6, 26, 54, 82, 110, 138, 166],
|
||
|
[6, 30, 58, 86, 114, 142, 170]
|
||
|
]
|
||
|
|
||
|
MASKS = [
|
||
|
lambda x,y: (x+y)%2 == 0,
|
||
|
lambda x,y: y%2 == 0,
|
||
|
lambda x,y: (x)%3 == 0,
|
||
|
lambda x,y: (x+y)%3 == 0,
|
||
|
lambda x,y: (y//2+x//3)%2 == 0,
|
||
|
lambda x,y: ((x*y)%2 + (x*y)%3) == 0,
|
||
|
lambda x,y: ((x*y)%2 + (x*y)%3)%2 == 0,
|
||
|
lambda x,y: ((x+y)%2 + (x*y)%3)%2 == 0
|
||
|
]
|
||
|
|
||
|
def __init__(self, data, level=0):
|
||
|
pygame.display.set_caption("QR Gen - Init")
|
||
|
log(f"Content: {data}")
|
||
|
log(f"EC Level: {self.LEVELS[level]}")
|
||
|
self.bits = ""
|
||
|
self.data = data
|
||
|
self.level = level
|
||
|
self.type = -1
|
||
|
self.version = -1
|
||
|
self.analyse_type()
|
||
|
self.compute_version()
|
||
|
self.build_char_count_indicator()
|
||
|
self.encode()
|
||
|
self.separate_codewords()
|
||
|
self.create_matrix()
|
||
|
|
||
|
def load_versions():
|
||
|
with open("qr_versions.txt", "r") as f:
|
||
|
versions = f.read().split("\n\n")
|
||
|
for v in versions:
|
||
|
lvls = [list(map(int, lvl.split("\t"))) for lvl in v.split("\n")]
|
||
|
QR.VERSIONS.append(lvls)
|
||
|
|
||
|
QR.VERSIONS = np.array(QR.VERSIONS)
|
||
|
|
||
|
def load_ec():
|
||
|
with open("error_correction.txt", "r") as f:
|
||
|
ecs = f.read().split("\n\n")
|
||
|
for ec in ecs:
|
||
|
lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n")]
|
||
|
lvls = [lvl + [0]*(6-len(lvl)) for lvl in lvls]
|
||
|
|
||
|
QR.ERROR_CORRECTION.append(lvls)
|
||
|
|
||
|
QR.ERROR_CORRECTION = np.array(QR.ERROR_CORRECTION)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "<QR: {} - {} (V{})>".format(
|
||
|
QR.LEVELS[self.level],
|
||
|
QR.TYPES[self.type].title(),
|
||
|
"?" if self.version == -1 else self.version+1
|
||
|
)
|
||
|
|
||
|
def analyse_type(self):
|
||
|
pygame.display.set_caption("QR Gen - Type analysis")
|
||
|
if self.data.isnumeric():
|
||
|
self.type = 0
|
||
|
|
||
|
elif set(self.data).issubset(set(QR.ALPHANUM)):
|
||
|
self.type = 1
|
||
|
|
||
|
else:
|
||
|
try:
|
||
|
self.data.encode("ISO-8859-1")
|
||
|
self.type = 2
|
||
|
|
||
|
except:
|
||
|
self.type = 3
|
||
|
|
||
|
self.bits += self.MODES[self.type]
|
||
|
log(f"Type: {self.TYPES[self.type]}")
|
||
|
|
||
|
def compute_version(self):
|
||
|
pygame.display.set_caption("QR Gen - Version computation")
|
||
|
self.version = min(np.where(QR.VERSIONS[:, self.level, self.type] >= len(self.data))[0])
|
||
|
log(f"Version: {self.version+1}")
|
||
|
|
||
|
def get_char_count_len(self):
|
||
|
if 0 <= self.version < 9:
|
||
|
return [10,9,8,8][self.type]
|
||
|
elif 9 <= self.version < 26:
|
||
|
return [12,11,16,10][self.type]
|
||
|
elif 26 <= self.version < 40:
|
||
|
return [14,13,16,12][self.type]
|
||
|
|
||
|
def build_char_count_indicator(self):
|
||
|
pygame.display.set_caption("QR Gen - Char count ind")
|
||
|
length = self.get_char_count_len()
|
||
|
indicator = f"{{:0{length}b}}".format(len(self.data))
|
||
|
self.bits += indicator
|
||
|
log(f"Char count indicator: {indicator}")
|
||
|
|
||
|
def encode(self):
|
||
|
pygame.display.set_caption("QR Gen - Encoding")
|
||
|
if self.type == 0:
|
||
|
groups = [self.data[i:i+3] for i in range(0,len(self.data),3)]
|
||
|
|
||
|
for group in groups:
|
||
|
group = int(group)
|
||
|
sgroup = str(group)
|
||
|
|
||
|
if len(sgroup) == 3:
|
||
|
s = "{:010b}"
|
||
|
elif len(sgroup) == 2:
|
||
|
s = "{:07b}"
|
||
|
else:
|
||
|
s = "{:04b}"
|
||
|
|
||
|
self.bits += s.format(group)
|
||
|
|
||
|
elif self.type == 1:
|
||
|
data = self.data
|
||
|
last = None
|
||
|
if len(data)%2 == 1:
|
||
|
last = data[-1]
|
||
|
data = data[:-1]
|
||
|
|
||
|
for i in range(0, len(data), 2):
|
||
|
val1 = self.ALPHANUM.index(data[i])
|
||
|
val2 = self.ALPHANUM.index(data[i+1])
|
||
|
val = val1*45 + val2
|
||
|
self.bits += f"{val:011b}"
|
||
|
|
||
|
if not last is None:
|
||
|
self.bits += "{:06b}".format(self.ALPHANUM.index(last))
|
||
|
|
||
|
elif self.type == 2:
|
||
|
data = self.data.encode("ISO-8859-1")
|
||
|
self.bits += "".join(list(map("{:08b}".format, data)))
|
||
|
|
||
|
elif self.type == 3:
|
||
|
data = list(self.data.encode("shift_jis"))
|
||
|
|
||
|
#Combine double bytes
|
||
|
data = [data[i*2]<<8 | data[i*2+1] for i in range(len(data)//2)]
|
||
|
|
||
|
for dbyte in data:
|
||
|
if 0x8140 <= dbyte <= 0x9ffc:
|
||
|
dbyte = dbyte - 0x8140
|
||
|
|
||
|
elif 0xe040 <= dbyte <= 0xebbf:
|
||
|
dbyte = dbyte - 0xc140
|
||
|
|
||
|
msb = dbyte >> 8
|
||
|
lsb = dbyte & 0xff
|
||
|
|
||
|
val = msb * 0xc0 + lsb
|
||
|
self.bits += f"{val:013b}"
|
||
|
|
||
|
log(f"Encoded: {[self.bits[i:i+8] for i in range(0,len(self.bits),8)]}")
|
||
|
|
||
|
ec = self.ERROR_CORRECTION[self.version, self.level]
|
||
|
req_bits = ec[0]*8
|
||
|
|
||
|
#Terminator
|
||
|
self.bits += "0"*(min(4, req_bits-len(self.bits)))
|
||
|
|
||
|
#Pad to multiple of 8
|
||
|
if len(self.bits) % 8 != 0:
|
||
|
self.bits += "0"*(8-len(self.bits)%8)
|
||
|
|
||
|
#Pad to required bits
|
||
|
if len(self.bits) < req_bits:
|
||
|
for i in range((req_bits-len(self.bits))//8):
|
||
|
self.bits += ["11101100","00010001"][i%2]
|
||
|
|
||
|
log(f"Padded: {[self.bits[i:i+8] for i in range(0,len(self.bits),8)]}")
|
||
|
|
||
|
def separate_codewords(self):
|
||
|
pygame.display.set_caption("QR Gen - Separating codewords")
|
||
|
ec = self.ERROR_CORRECTION[self.version, self.level]
|
||
|
blocks = []
|
||
|
ec_codewords = []
|
||
|
|
||
|
codeword = 0
|
||
|
gen_poly = self.get_generator_poly(ec[1])
|
||
|
log(f"Gen poly: {gen_poly}")
|
||
|
|
||
|
#print(self.bits)
|
||
|
for i in range(ec[2]):
|
||
|
block = []
|
||
|
for j in range(ec[3]):
|
||
|
block.append(self.bits[codeword*8:codeword*8+8])
|
||
|
codeword += 1
|
||
|
|
||
|
blocks.append(block)
|
||
|
msg_poly = Poly(list(map(lambda b: GF(int(b,2)), block)))
|
||
|
log(f"Msg poly (1-{i}): {msg_poly}")
|
||
|
|
||
|
quotient, remainder = msg_poly / gen_poly
|
||
|
log(f"EC poly (1-{i}): {remainder}")
|
||
|
ec_cwds = [f"{c.val:08b}" for c in remainder.coefs]
|
||
|
ec_codewords.append(ec_cwds)
|
||
|
|
||
|
#If group 2
|
||
|
if ec[4] != 0:
|
||
|
for i in range(ec[4]):
|
||
|
block = []
|
||
|
for j in range(ec[5]):
|
||
|
block.append(self.bits[codeword*8:codeword*8+8])
|
||
|
codeword += 1
|
||
|
|
||
|
blocks.append(block)
|
||
|
msg_poly = Poly(list(map(lambda b: GF(int(b,2)), block)))
|
||
|
log(f"Msg poly (2-{i}): {msg_poly}")
|
||
|
|
||
|
quotient, remainder = msg_poly / gen_poly
|
||
|
log(f"EC poly (2-{i}): {remainder}")
|
||
|
ec_cwds = [f"{c.val:08b}" for c in remainder.coefs]
|
||
|
ec_codewords.append(ec_cwds)
|
||
|
|
||
|
self.final_data_bits = ""
|
||
|
|
||
|
if len(blocks) == 1:
|
||
|
dbits = "".join(["".join(block) for block in blocks])
|
||
|
ec_bits = "".join(["".join(cwd) for cwd in ec_codewords])
|
||
|
log(f"EC bits: {[ec_bits[i:i+8] for i in range(0,len(ec_bits),8)]}")
|
||
|
self.final_data_bits = dbits + ec_bits
|
||
|
|
||
|
else:
|
||
|
#Interleave data codewords
|
||
|
for i in range(max(ec[3], ec[5])):
|
||
|
for block in blocks:
|
||
|
if i < len(block):
|
||
|
self.final_data_bits += block[i]
|
||
|
|
||
|
#Interleave error correction codewords
|
||
|
for i in range(ec[1]):
|
||
|
for block in ec_codewords:
|
||
|
self.final_data_bits += block[i]
|
||
|
|
||
|
#Add remainder bits
|
||
|
if 1 <= self.version < 6:
|
||
|
self.final_data_bits += "0"*7
|
||
|
log(f"Add 7 remainder bits")
|
||
|
|
||
|
elif 13 <= self.version < 20 or 27 <= self.version < 34:
|
||
|
self.final_data_bits += "0"*3
|
||
|
log(f"Add 3 remainder bits")
|
||
|
|
||
|
elif 20 <= self.version < 27:
|
||
|
self.final_data_bits += "0"*4
|
||
|
log(f"Add 4 remainder bits")
|
||
|
|
||
|
print_bytes(self.final_data_bits)
|
||
|
|
||
|
def get_generator_poly(self, n):
|
||
|
poly = Poly([GF(1)])
|
||
|
|
||
|
for i in range(n):
|
||
|
poly *= Poly([GF(1), GF(2)**GF(i)])
|
||
|
|
||
|
return poly
|
||
|
|
||
|
def get_alignment_pattern_locations(self):
|
||
|
return QR.ALIGNMENT_PATTERN_LOCATIONS[self.version]
|
||
|
|
||
|
def create_matrix(self):
|
||
|
size = self.version*4+21
|
||
|
log(f"Size: {size}")
|
||
|
self.matrix = np.zeros([size, size])-1 #-1: empty | -0.5: reserved | 0: white | 1: black
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Matrix")
|
||
|
if STEPS & S_EMPTY: self.show(step=True)
|
||
|
|
||
|
#Add separator
|
||
|
self.matrix[0:8, 0:8] = 0
|
||
|
self.matrix[-8:, 0:8] = 0
|
||
|
self.matrix[0:8, -8:] = 0
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Separator")
|
||
|
if STEPS & S_SEP: self.show(step=True)
|
||
|
|
||
|
#Place finders
|
||
|
self.matrix[0:7, 0:7] = QR.FINDER
|
||
|
self.matrix[-7:, 0:7] = QR.FINDER
|
||
|
self.matrix[0:7, -7:] = QR.FINDER
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Finder patterns")
|
||
|
if STEPS & S_FINDER: self.show(step=True)
|
||
|
|
||
|
#Add alignment patterns
|
||
|
locations = self.get_alignment_pattern_locations()
|
||
|
log(f"Alignment patterns: {locations}")
|
||
|
|
||
|
if self.version > 0:
|
||
|
for y in locations:
|
||
|
for x in locations:
|
||
|
#Check if not overlapping with finders
|
||
|
if np.all(self.matrix[y-2:y+3, x-2:x+3] == -1):
|
||
|
self.matrix[y-2:y+3, x-2:x+3] = 1
|
||
|
self.matrix[y-1:y+2, x-1:x+2] = 0
|
||
|
self.matrix[y, x] = 1
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Alignment patterns")
|
||
|
if STEPS & S_ALIGN: self.show(step=True)
|
||
|
|
||
|
#Add timing patterns
|
||
|
timing_length = size-2*8
|
||
|
self.matrix[6, 8:-8] = np.resize([1,0],timing_length)
|
||
|
self.matrix[8:-8, 6] = np.resize([1,0],timing_length)
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Timing patterns")
|
||
|
if STEPS & S_TIMING: self.show(step=True)
|
||
|
|
||
|
#Add reserved areas
|
||
|
self.matrix[self.version*4+13,8] = 1 #Black module
|
||
|
self.matrix[:9, :9] = np.maximum(self.matrix[:9, :9], -0.5) #Top-left
|
||
|
self.matrix[-8:, 8] = np.maximum(self.matrix[-8:, 8], -0.5) #Bottom-left
|
||
|
self.matrix[8, -8:] = np.maximum(self.matrix[8, -8:], -0.5) #Top-right
|
||
|
|
||
|
if self.version >= 6:
|
||
|
self.matrix[-11:-8, :6] = -0.5
|
||
|
self.matrix[:6, -11:-8] = -0.5
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Reserved areas")
|
||
|
if STEPS & S_RESERVED: self.show(step=True)
|
||
|
|
||
|
#Place data
|
||
|
dir_ = -1 #-1 = up | 1 = down
|
||
|
x, y = size-1, size-1
|
||
|
i = 0
|
||
|
zigzag = 0
|
||
|
|
||
|
mask_area = self.matrix == -1
|
||
|
pygame.display.set_caption("QR Gen - Data layout")
|
||
|
print(self.matrix.tolist())
|
||
|
|
||
|
while x >= 0:
|
||
|
if self.matrix[y,x] == -1:
|
||
|
self.matrix[y,x] = self.final_data_bits[i]
|
||
|
|
||
|
i += 1
|
||
|
|
||
|
if STEPS & S_DATA:
|
||
|
if not (STEPS & S_BYTES) or i%8==0:
|
||
|
self.show()
|
||
|
time.sleep(0.01)
|
||
|
|
||
|
if ((dir_+1)/2 + zigzag)%2 == 0:
|
||
|
x -= 1
|
||
|
|
||
|
else:
|
||
|
y += dir_
|
||
|
x += 1
|
||
|
|
||
|
if y == -1 or y == size:
|
||
|
dir_ = -dir_
|
||
|
y += dir_
|
||
|
x -= 2
|
||
|
|
||
|
else:
|
||
|
zigzag = 1-zigzag
|
||
|
|
||
|
#Vertical timing pattern
|
||
|
if x == 6:
|
||
|
x -= 1
|
||
|
|
||
|
if STEPS & S_DATA: self.show(step=True)
|
||
|
|
||
|
score, mask, matrix = self.try_masks(mask_area)
|
||
|
|
||
|
self.matrix = np.where(mask_area, matrix, self.matrix)
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Mask")
|
||
|
if STEPS & S_MASK: self.show(step=True)
|
||
|
|
||
|
#Format string
|
||
|
format_str = f"{(5-self.level)%4:02b}{mask:03b}"
|
||
|
format_str += "0"*10
|
||
|
format_str.lstrip("0")
|
||
|
log(f"Format str: {format_str}")
|
||
|
|
||
|
gen_poly = 0b10100110111
|
||
|
format_poly = int(format_str,2)
|
||
|
|
||
|
while format_poly.bit_length() > 10:
|
||
|
g = gen_poly << (format_poly.bit_length()-gen_poly.bit_length())
|
||
|
format_poly ^= g
|
||
|
|
||
|
log(f"Remainder: {format_poly:b}")
|
||
|
format_data = int(format_str,2) + format_poly
|
||
|
format_data ^= 0b101010000010010
|
||
|
format_data = f"{format_data:015b}"
|
||
|
log(f"XORed: {format_data}")
|
||
|
|
||
|
for i in range(15):
|
||
|
y1, x1 = min(8,15-i), min(7,i)
|
||
|
if i >= 6:
|
||
|
x1 += 1
|
||
|
if i >= 9:
|
||
|
y1 -= 1
|
||
|
y2, x2 = self.matrix.shape[0]-i-1 if i < 7 else 8, 8 if i < 7 else self.matrix.shape[1]+i-15
|
||
|
|
||
|
self.matrix[y1, x1] = format_data[i]
|
||
|
self.matrix[y2, x2] = format_data[i]
|
||
|
|
||
|
#Version information
|
||
|
if self.version >= 6:
|
||
|
gen_poly = 0b1111100100101
|
||
|
version_info_poly = int(self.version+1)<<12
|
||
|
|
||
|
while version_info_poly.bit_length() > 12:
|
||
|
g = gen_poly << (version_info_poly.bit_length()-gen_poly.bit_length())
|
||
|
version_info_poly ^= g
|
||
|
|
||
|
version_info_data = ((self.version+1)<<12) + version_info_poly
|
||
|
version_info_data = f"{version_info_data:018b}"
|
||
|
|
||
|
ox1, oy1 = 5, self.matrix.shape[0]-9
|
||
|
ox2, oy2 = self.matrix.shape[1]-9, 5
|
||
|
for i in range(18):
|
||
|
self.matrix[oy1 - i%3, ox1 - i//3] = version_info_data[i]
|
||
|
self.matrix[oy2 - i//3, ox2 - i%3] = version_info_data[i]
|
||
|
|
||
|
def try_masks(self, mask_area):
|
||
|
best = [None,None,None] #score, i, matrix
|
||
|
|
||
|
for i in range(8):
|
||
|
mask = QR.MASKS[i]
|
||
|
mat = self.matrix.copy()
|
||
|
|
||
|
for y in range(self.matrix.shape[0]):
|
||
|
for x in range(self.matrix.shape[1]):
|
||
|
if mask_area[y,x] and mask(x,y):
|
||
|
mat[y,x] = 1-mat[y,x]
|
||
|
|
||
|
|
||
|
#Format string
|
||
|
format_str = f"{(5-self.level)%4:02b}{i:03b}"
|
||
|
format_str += "0"*10
|
||
|
format_str.lstrip("0")
|
||
|
|
||
|
gen_poly = 0b10100110111
|
||
|
format_poly = int(format_str,2)
|
||
|
|
||
|
while format_poly.bit_length() > 10:
|
||
|
g = gen_poly << (format_poly.bit_length()-gen_poly.bit_length())
|
||
|
format_poly ^= g
|
||
|
|
||
|
format_data = int(format_str,2) + format_poly
|
||
|
format_data ^= 0b101010000010010
|
||
|
format_data = f"{format_data:015b}"
|
||
|
|
||
|
for j in range(15):
|
||
|
y1, x1 = min(8,15-j), min(7,j)
|
||
|
if j >= 6:
|
||
|
x1 += 1
|
||
|
if j >= 9:
|
||
|
y1 -= 1
|
||
|
y2, x2 = mat.shape[0]-j-1 if j < 7 else 8, 8 if j < 7 else mat.shape[1]+j-15
|
||
|
|
||
|
mat[y1, x1] = format_data[j]
|
||
|
mat[y2, x2] = format_data[j]
|
||
|
|
||
|
score = self.evaluate(mat.copy(), i)
|
||
|
|
||
|
if best[0] is None or score < best[0]:
|
||
|
best = [score, i, mat]
|
||
|
|
||
|
return best
|
||
|
|
||
|
def evaluate(self, matrix, i):
|
||
|
score = 0
|
||
|
|
||
|
matrix = np.where(matrix < 0, 0, matrix)
|
||
|
|
||
|
s1, s2, s3, s4 = 0, 0, 0, 0
|
||
|
|
||
|
#Condition 1 (horizontal)
|
||
|
for y in range(matrix.shape[0]):
|
||
|
col, count = -1, 0
|
||
|
for x in range(matrix.shape[1]):
|
||
|
if matrix[y,x] != col:
|
||
|
count = 0
|
||
|
col = matrix[y,x]
|
||
|
count += 1
|
||
|
|
||
|
if count == 5:
|
||
|
score += 3
|
||
|
s1 += 3
|
||
|
elif count > 5:
|
||
|
score += 1
|
||
|
s1 += 1
|
||
|
|
||
|
#Condition 1 (vertical)
|
||
|
for x in range(matrix.shape[1]):
|
||
|
col, count = -1, 0
|
||
|
for y in range(matrix.shape[0]):
|
||
|
if matrix[y,x] != col:
|
||
|
count = 0
|
||
|
col = matrix[y,x]
|
||
|
count += 1
|
||
|
|
||
|
if count == 5:
|
||
|
score += 3
|
||
|
s1 += 3
|
||
|
elif count > 5:
|
||
|
score += 1
|
||
|
s1 += 1
|
||
|
|
||
|
#Condition 2
|
||
|
for y in range(matrix.shape[0]-1):
|
||
|
for x in range(matrix.shape[1]-1):
|
||
|
zone = matrix[y:y+2, x:x+2]
|
||
|
if np.all(zone == zone[0,0]):
|
||
|
score += 3
|
||
|
s2 += 3
|
||
|
|
||
|
#Condition 3 (horizontal)
|
||
|
for y in range(matrix.shape[0]):
|
||
|
hist = History()
|
||
|
for x in range(matrix.shape[1]):
|
||
|
s = hist.add(matrix[y,x])
|
||
|
score += s*40
|
||
|
s3 += s*40
|
||
|
|
||
|
s = hist.final()
|
||
|
score += s*40
|
||
|
s3 += s*40
|
||
|
|
||
|
#Condition 3 (vertical)
|
||
|
for x in range(matrix.shape[1]):
|
||
|
hist = History()
|
||
|
for y in range(matrix.shape[0]):
|
||
|
s = hist.add(matrix[y,x])
|
||
|
score += s*40
|
||
|
s3 += s*40
|
||
|
|
||
|
s = hist.final()
|
||
|
score += s*40
|
||
|
s3 += s*40
|
||
|
|
||
|
#Condition 4
|
||
|
total = matrix.shape[0]*matrix.shape[1]
|
||
|
dark = np.sum(matrix == 1)
|
||
|
percent = 100*dark//total
|
||
|
p1 = percent-(percent%5)
|
||
|
p2 = p1+5
|
||
|
p1, p2 = abs(p1-50)/5, abs(p2-50)/5
|
||
|
score += min(p1,p2)*10
|
||
|
s4 += min(p1,p2)*10
|
||
|
|
||
|
log(f"mask {i}: {s1} + {s2} + {s3} + {s4} = {score}")
|
||
|
|
||
|
return score
|
||
|
|
||
|
def show(self, pos=None, step=False):
|
||
|
global win
|
||
|
|
||
|
events = pygame.event.get()
|
||
|
|
||
|
for event in events:
|
||
|
if event.type == pygame.KEYDOWN:
|
||
|
if event.key == pygame.K_s:
|
||
|
pygame.image.save(win, "/tmp/qr.jpg")
|
||
|
|
||
|
m = ((self.matrix.copy()+2)%3)*127
|
||
|
mat = np.ones((m.shape[0]+8, m.shape[1]+8))*255
|
||
|
mat[4:-4, 4:-4] = m
|
||
|
|
||
|
if not pos is None:
|
||
|
mat[pos[1]+4, pos[0]+4] = 50
|
||
|
|
||
|
size = 15
|
||
|
if win is None:
|
||
|
win = pygame.display.set_mode([mat.shape[0]*size, mat.shape[0]*size])
|
||
|
|
||
|
win.fill((255,255,255))
|
||
|
|
||
|
for y in range(mat.shape[0]):
|
||
|
for x in range(mat.shape[1]):
|
||
|
col = mat[y, x]
|
||
|
col = (col, col, col)
|
||
|
pygame.draw.rect(win, col, [x*size, y*size, size, size])
|
||
|
|
||
|
if coords:
|
||
|
N = 6
|
||
|
space = (mat.shape[0]-8)/(N-1)
|
||
|
margin = 4*size
|
||
|
SIZE = (mat.shape[0]-8)*size
|
||
|
pygame.draw.lines(win, (0,0,0), True, [
|
||
|
(margin, margin),(margin+SIZE, margin),
|
||
|
(margin+SIZE, margin+SIZE),(margin, margin+SIZE)
|
||
|
])
|
||
|
for i in range(N):
|
||
|
n = int(round(space*i))
|
||
|
d = size * n
|
||
|
pygame.draw.line(win, (0,0,0), [margin+d, margin], [margin+d, margin-15])
|
||
|
pygame.draw.line(win, (0,0,0), [margin, margin+d], [margin-15, margin+d])
|
||
|
pygame.draw.line(win, (0,0,0), [margin+d, margin+SIZE], [margin+d, margin+SIZE+15])
|
||
|
pygame.draw.line(win, (0,0,0), [margin+SIZE, margin+d], [margin+SIZE+15, margin+d])
|
||
|
text = font.render(str(n), True, (0,0,0))
|
||
|
win.blit(text, [margin+d-text.get_width()/2, margin-30-text.get_height()/2])
|
||
|
win.blit(text, [margin-30-text.get_width()/2, margin+d-text.get_height()/2])
|
||
|
win.blit(text, [margin+d-text.get_width()/2, margin+SIZE+30-text.get_height()/2])
|
||
|
win.blit(text, [margin+SIZE+30-text.get_width()/2, margin+d-text.get_height()/2])
|
||
|
|
||
|
pygame.display.flip()
|
||
|
|
||
|
if step:
|
||
|
input("Press Enter to continue")
|
||
|
|
||
|
def print_bytes(bytes_, int_=False):
|
||
|
result = ""
|
||
|
for i in range(len(bytes_)//8):
|
||
|
if int_:
|
||
|
result += str(int(bytes_[i*8:i*8+8],2)) + " "
|
||
|
else:
|
||
|
result += bytes_[i*8:i*8+8] + " "
|
||
|
|
||
|
result += bytes_[-(len(bytes_)%8):]
|
||
|
|
||
|
print(result.strip())
|
||
|
|
||
|
QR.load_versions()
|
||
|
QR.load_ec()
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
np.set_printoptions(linewidth=200)
|
||
|
pygame.display.set_caption("QR Gen")
|
||
|
#qr = QR("8675309", 0)
|
||
|
#qr = QR("HELLO WORLD", 2)
|
||
|
qr = QR("Hello, World!", 1)
|
||
|
#qr = QR("茗荷", 2)
|
||
|
#qr = QR("Hello, world! How are you doing ? I'm doing great, thank you ! Today is quite a sunny day, isn't it ?", 3)
|
||
|
#qr = QR("https://aufildeverre.ch/", 3)
|
||
|
#qr = QR("QR Code Symbol", 1)
|
||
|
#qr = QR("Attention !", 3)
|
||
|
#qr = QR("Lycacode", 0)
|
||
|
|
||
|
print(qr)
|
||
|
|
||
|
pygame.display.set_caption("QR Gen - Final")
|
||
|
qr.show(step=False)
|
||
|
input("Press Enter to quit")
|
||
|
events = pygame.event.get()
|
||
|
|
||
|
for event in events:
|
||
|
if event.type == pygame.KEYDOWN:
|
||
|
if event.key == pygame.K_s:
|
||
|
pygame.image.save(win, "/tmp/qr.jpg")
|