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") |