QR Code Generator
A simply python-based utility that generates a QR code image from data that is given via a GET or POST request.
Returns: An Image (PNG)
Calling Method(s): GET, POST, UPLOAD
Coded in: Python
Utility Code:
import os
import struct
import sys
import zlib
from urllib.parse import unquote_plus
if sys.platform == "win32":
import msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
# QR Code Version 1-40, Error Correction Level L
# size = module width/height (4*version + 17)
# data_codewords = total data codewords available for this version/level
# ec_codewords = EC codewords PER BLOCK
# blocks = list of (block_count, data_codewords_per_block) groups,
# in the order they must be read for interleaving
# align = alignment pattern center row/col coordinates (cross product,
# minus any that collide with a finder pattern)
VERSION_SPECS = {
1: {"size": 21, "data_codewords": 19, "ec_codewords": 7, "blocks": [(1, 19)], "align": []},
2: {"size": 25, "data_codewords": 34, "ec_codewords": 10, "blocks": [(1, 34)], "align": [6, 18]},
3: {"size": 29, "data_codewords": 55, "ec_codewords": 15, "blocks": [(1, 55)], "align": [6, 22]},
4: {"size": 33, "data_codewords": 80, "ec_codewords": 20, "blocks": [(1, 80)], "align": [6, 26]},
5: {"size": 37, "data_codewords": 108, "ec_codewords": 26, "blocks": [(1, 108)], "align": [6, 30]},
6: {"size": 41, "data_codewords": 136, "ec_codewords": 18, "blocks": [(2, 68)], "align": [6, 34]},
7: {"size": 45, "data_codewords": 156, "ec_codewords": 20, "blocks": [(2, 78)], "align": [6, 22, 38]},
8: {"size": 49, "data_codewords": 194, "ec_codewords": 24, "blocks": [(2, 97)], "align": [6, 24, 42]},
9: {"size": 53, "data_codewords": 232, "ec_codewords": 30, "blocks": [(2, 116)], "align": [6, 26, 46]},
10: {"size": 57, "data_codewords": 274, "ec_codewords": 18, "blocks": [(2, 68), (2, 69)], "align": [6, 28, 50]},
11: {"size": 61, "data_codewords": 324, "ec_codewords": 20, "blocks": [(4, 81)], "align": [6, 30, 54]},
12: {"size": 65, "data_codewords": 370, "ec_codewords": 24, "blocks": [(2, 92), (2, 93)], "align": [6, 32, 58]},
13: {"size": 69, "data_codewords": 428, "ec_codewords": 26, "blocks": [(4, 107)], "align": [6, 34, 62]},
14: {"size": 73, "data_codewords": 461, "ec_codewords": 30, "blocks": [(3, 115), (1, 116)], "align": [6, 26, 46, 66]},
15: {"size": 77, "data_codewords": 523, "ec_codewords": 22, "blocks": [(5, 87), (1, 88)], "align": [6, 26, 48, 70]},
16: {"size": 81, "data_codewords": 589, "ec_codewords": 24, "blocks": [(5, 98), (1, 99)], "align": [6, 26, 50, 74]},
17: {"size": 85, "data_codewords": 647, "ec_codewords": 28, "blocks": [(1, 107), (5, 108)], "align": [6, 30, 54, 78]},
18: {"size": 89, "data_codewords": 721, "ec_codewords": 30, "blocks": [(5, 120), (1, 121)], "align": [6, 30, 56, 82]},
19: {"size": 93, "data_codewords": 795, "ec_codewords": 28, "blocks": [(3, 113), (4, 114)], "align": [6, 30, 58, 86]},
20: {"size": 97, "data_codewords": 861, "ec_codewords": 28, "blocks": [(3, 107), (5, 108)], "align": [6, 34, 62, 90]},
21: {"size": 101, "data_codewords": 932, "ec_codewords": 28, "blocks": [(4, 116), (4, 117)], "align": [6, 28, 50, 72, 94]},
22: {"size": 105, "data_codewords": 1006, "ec_codewords": 28, "blocks": [(2, 111), (7, 112)], "align": [6, 26, 50, 74, 98]},
23: {"size": 109, "data_codewords": 1094, "ec_codewords": 30, "blocks": [(4, 121), (5, 122)], "align": [6, 30, 54, 78, 102]},
24: {"size": 113, "data_codewords": 1174, "ec_codewords": 30, "blocks": [(6, 117), (4, 118)], "align": [6, 28, 54, 80, 106]},
25: {"size": 117, "data_codewords": 1276, "ec_codewords": 26, "blocks": [(8, 106), (4, 107)], "align": [6, 32, 58, 84, 110]},
26: {"size": 121, "data_codewords": 1370, "ec_codewords": 28, "blocks": [(10, 114), (2, 115)], "align": [6, 30, 58, 86, 114]},
27: {"size": 125, "data_codewords": 1468, "ec_codewords": 30, "blocks": [(8, 122), (4, 123)], "align": [6, 34, 62, 90, 118]},
28: {"size": 129, "data_codewords": 1531, "ec_codewords": 30, "blocks": [(3, 117), (10, 118)], "align": [6, 26, 50, 74, 98, 122]},
29: {"size": 133, "data_codewords": 1631, "ec_codewords": 30, "blocks": [(7, 116), (7, 117)], "align": [6, 30, 54, 78, 102, 126]},
30: {"size": 137, "data_codewords": 1735, "ec_codewords": 30, "blocks": [(5, 115), (10, 116)], "align": [6, 26, 52, 78, 104, 130]},
31: {"size": 141, "data_codewords": 1843, "ec_codewords": 30, "blocks": [(13, 115), (3, 116)], "align": [6, 30, 56, 82, 108, 134]},
32: {"size": 145, "data_codewords": 1955, "ec_codewords": 30, "blocks": [(17, 115)], "align": [6, 34, 60, 86, 112, 138]},
33: {"size": 149, "data_codewords": 2071, "ec_codewords": 30, "blocks": [(17, 115), (1, 116)], "align": [6, 30, 58, 86, 114, 142]},
34: {"size": 153, "data_codewords": 2191, "ec_codewords": 30, "blocks": [(13, 115), (6, 116)], "align": [6, 34, 62, 90, 118, 146]},
35: {"size": 157, "data_codewords": 2306, "ec_codewords": 30, "blocks": [(12, 121), (7, 122)], "align": [6, 30, 54, 78, 102, 126, 150]},
36: {"size": 161, "data_codewords": 2434, "ec_codewords": 30, "blocks": [(6, 121), (14, 122)], "align": [6, 24, 50, 76, 102, 128, 154]},
37: {"size": 165, "data_codewords": 2566, "ec_codewords": 30, "blocks": [(17, 122), (4, 123)], "align": [6, 28, 54, 80, 106, 132, 158]},
38: {"size": 169, "data_codewords": 2702, "ec_codewords": 30, "blocks": [(4, 122), (18, 123)], "align": [6, 32, 58, 84, 110, 136, 162]},
39: {"size": 173, "data_codewords": 2812, "ec_codewords": 30, "blocks": [(20, 117), (4, 118)], "align": [6, 26, 54, 82, 110, 138, 166]},
40: {"size": 177, "data_codewords": 2956, "ec_codewords": 30, "blocks": [(19, 118), (6, 119)], "align": [6, 30, 58, 86, 114, 142, 170]},
}
MAX_VERSION = max(VERSION_SPECS)
MAX_BYTES = VERSION_SPECS[MAX_VERSION]["data_codewords"] - 2 # minus mode+len overhead, see below
# GF(256) tables using QR Code primitive polynomial 0x11D
EXP = [0] * 512
LOG = [0] * 256
x = 1
for i in range(255):
EXP[i] = x
EXP[i + 255] = x
LOG[x] = i
x <<= 1
if x & 0x100:
x ^= 0x11D
def gf_mul(a, b):
if a == 0 or b == 0:
return 0
return EXP[LOG[a] + LOG[b]]
def make_generator_polynomial(ec_count):
polynomial = [1]
for i in range(ec_count):
root = EXP[i]
new_polynomial = [0] * (len(polynomial) + 1)
for j, coefficient in enumerate(polynomial):
new_polynomial[j] ^= coefficient
new_polynomial[j + 1] ^= gf_mul(coefficient, root)
polynomial = new_polynomial
return polynomial
def reed_solomon_encode(data, ec_count):
generator = make_generator_polynomial(ec_count)[1:]
remainder = [0] * ec_count
for byte in data:
factor = byte ^ remainder[0]
remainder = remainder[1:] + [0]
if factor != 0:
log_factor = LOG[factor]
for i in range(ec_count):
remainder[i] ^= EXP[LOG[generator[i]] + log_factor]
return remainder
def make_format_bits():
# Error correction L = 01, mask 0 = 000
data = (0b01 << 3) | 0b000
value = data << 10
generator = 0x537
while value.bit_length() >= generator.bit_length():
value ^= generator << (value.bit_length() - generator.bit_length())
return ((data << 10) | value) ^ 0x5412
def make_version_bits(version):
value = version << 12
generator = 0x1F25
while value.bit_length() >= generator.bit_length():
value ^= generator << (value.bit_length() - generator.bit_length())
return (version << 12) | value
def make_data_codewords(data_bytes, version):
spec = VERSION_SPECS[version]
capacity_bits = spec["data_codewords"] * 8
count_bits = 8 if version <= 9 else 16
if len(data_bytes) >= (1 << count_bits):
raise ValueError("Input is too long for the byte-count field.")
bits = [0, 1, 0, 0] # Byte mode
bits.extend(
int(bit) for bit in f"{len(data_bytes):0{count_bits}b}"
)
for byte in data_bytes:
bits.extend(int(bit) for bit in f"{byte:08b}")
if len(bits) > capacity_bits:
raise ValueError(f"Input too long for Version {version}.")
bits.extend([0] * min(4, capacity_bits - len(bits)))
while len(bits) % 8 != 0:
bits.append(0)
pad_bytes = [0xEC, 0x11]
pad_index = 0
while len(bits) < capacity_bits:
bits.extend(int(bit) for bit in f"{pad_bytes[pad_index]:08b}")
pad_index ^= 1
return [
int("".join(map(str, bits[i:i + 8])), 2)
for i in range(0, len(bits), 8)
]
def make_final_codewords(data_codewords, version):
spec = VERSION_SPECS[version]
blocks = []
offset = 0
# Split data into Reed-Solomon blocks
for block_count, block_data_count in spec["blocks"]:
for _ in range(block_count):
block_data = data_codewords[offset:offset + block_data_count]
offset += block_data_count
blocks.append({
"data": block_data,
"ec": reed_solomon_encode(block_data, spec["ec_codewords"])
})
result = []
# Interleave data codewords
max_data_length = max(len(block["data"]) for block in blocks)
for i in range(max_data_length):
for block in blocks:
if i < len(block["data"]):
result.append(block["data"][i])
# Interleave error-correction codewords
max_ec_length = max(len(block["ec"]) for block in blocks)
for i in range(max_ec_length):
for block in blocks:
if i < len(block["ec"]):
result.append(block["ec"][i])
return result
def generate_qr_matrix(data: str):
raw_bytes = data.encode("utf-8")
version = None
# Select smallest version that fits (now searches the full 1-40 range)
for v in range(1, MAX_VERSION + 1):
spec = VERSION_SPECS[v]
count_bits = 8 if v <= 9 else 16
required_bits = 4 + count_bits + len(raw_bytes) * 8
if required_bits <= spec["data_codewords"] * 8:
version = v
break
if version is None:
max_spec = VERSION_SPECS[MAX_VERSION]
raise ValueError(
f"Input too long ({len(raw_bytes)} bytes). "
f"Maximum capacity for Version {MAX_VERSION}-L byte mode is "
f"{max_spec['data_codewords'] - 2} bytes."
)
spec = VERSION_SPECS[version]
SIZE = spec["size"]
grid = [[None for _ in range(SIZE)] for _ in range(SIZE)]
reserved = [[False for _ in range(SIZE)] for _ in range(SIZE)]
def set_module(r, c, value, is_reserved=True):
if 0 <= r < SIZE and 0 <= c < SIZE:
grid[r][c] = bool(value)
if is_reserved:
reserved[r][c] = True
# Finder patterns and separators
def place_finder(row, col):
for r in range(-1, 8):
for c in range(-1, 8):
rr = row + r
cc = col + c
if not (0 <= rr < SIZE and 0 <= cc < SIZE):
continue
if 0 <= r <= 6 and 0 <= c <= 6:
is_dark = (
r in (0, 6)
or c in (0, 6)
or (2 <= r <= 4 and 2 <= c <= 4)
)
set_module(rr, cc, is_dark)
else:
set_module(rr, cc, False)
place_finder(0, 0)
place_finder(0, SIZE - 7)
place_finder(SIZE - 7, 0)
# Alignment patterns
def place_alignment(row, col):
if grid[row][col] is not None:
return
for r in range(-2, 3):
for c in range(-2, 3):
rr = row + r
cc = col + c
if 0 <= rr < SIZE and 0 <= cc < SIZE:
if grid[rr][cc] is None:
is_dark = max(abs(r), abs(c)) != 1
set_module(rr, cc, is_dark)
for row in spec["align"]:
for col in spec["align"]:
place_alignment(row, col)
# Timing patterns
for i in range(SIZE):
if grid[6][i] is None:
set_module(6, i, i % 2 == 0)
if grid[i][6] is None:
set_module(i, 6, i % 2 == 0)
# Dark module
set_module(SIZE - 8, 8, True)
# Reserve format information
for i in range(9):
if not reserved[8][i]:
set_module(8, i, False)
if not reserved[i][8]:
set_module(i, 8, False)
for i in range(SIZE - 8, SIZE):
if not reserved[8][i]:
set_module(8, i, False)
if not reserved[i][8]:
set_module(i, 8, False)
# Reserve version information
if version >= 7:
for r in range(6):
for c in range(3):
set_module(r, SIZE - 11 + c, False)
for r in range(3):
for c in range(6):
set_module(SIZE - 11 + r, c, False)
# Encode data
data_codewords = make_data_codewords(raw_bytes, version)
all_codewords = make_final_codewords(data_codewords, version)
all_bits = []
for codeword in all_codewords:
all_bits.extend(int(bit) for bit in f"{codeword:08b}")
# Place data using mask 0
bit_idx = 0
direction_up = True
col = SIZE - 1
while col > 0:
if col == 6:
col -= 1
rows = range(SIZE - 1, -1, -1) if direction_up else range(SIZE)
for row in rows:
for current_col in (col, col - 1):
if not reserved[row][current_col]:
bit = all_bits[bit_idx] if bit_idx < len(all_bits) else 0
mask = (row + current_col) % 2 == 0
grid[row][current_col] = bool(bit ^ mask)
bit_idx += 1
direction_up = not direction_up
col -= 2
# Format information: Level L, Mask 0
format_value = make_format_bits()
format_bits = [(format_value >> (14 - i)) & 1 for i in range(15)]
tl_coords = [
(8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5),
(8, 7), (8, 8), (7, 8), (5, 8), (4, 8), (3, 8),
(2, 8), (1, 8), (0, 8)
]
for (r, c), bit in zip(tl_coords, format_bits):
grid[r][c] = bool(bit)
for i in range(7):
grid[SIZE - 1 - i][8] = bool(format_bits[i])
for i in range(8):
grid[8][SIZE - 8 + i] = bool(format_bits[7 + i])
# Version information (two 18-bit blocks: top-right is 6 rows x 3 cols,
# bottom-left is 3 rows x 6 cols). Per the spec, bit j (counted from the
# LSB) goes at row=j//3, col=j%3 in the top-right block, and at
# row=j%3, col=j//3 in the bottom-left block.
if version >= 7:
version_value = make_version_bits(version)
for j in range(18):
bit = bool((version_value >> j) & 1)
tr_row, tr_col = j // 3, j % 3
grid[tr_row][SIZE - 11 + tr_col] = bit
bl_row, bl_col = j % 3, j // 3
grid[SIZE - 11 + bl_row][bl_col] = bit
return grid
def generate_png_bytes(matrix, scale=8, border=4):
matrix_size = len(matrix)
img_size = (matrix_size + border * 2) * scale
def make_chunk(chunk_type: bytes, data: bytes) -> bytes:
chunk_head = chunk_type + data
crc = zlib.crc32(chunk_head) & 0xFFFFFFFF
return (
struct.pack(">I", len(data))
+ chunk_head
+ struct.pack(">I", crc)
)
png_signature = b"\x89PNG\r\n\x1a\n"
ihdr_chunk = make_chunk(
b"IHDR",
struct.pack(
">IIBBBBB",
img_size,
img_size,
8, # Bit depth
0, # Grayscale
0, # Compression
0, # Filter
0 # Interlace
)
)
raw_scanlines = bytearray()
for y in range(img_size):
r = y // scale - border
raw_scanlines.append(0)
for x in range(img_size):
c = x // scale - border
if 0 <= r < matrix_size and 0 <= c < matrix_size:
is_dark = matrix[r][c]
else:
is_dark = False
raw_scanlines.append(0 if is_dark else 255)
idat_chunk = make_chunk(
b"IDAT",
zlib.compress(bytes(raw_scanlines), level=9)
)
iend_chunk = make_chunk(b"IEND", b"")
return png_signature + ihdr_chunk + idat_chunk + iend_chunk
def read_input_data(argv):
"""
Resolve the raw string to encode.
- If called with '-' as the sole argument (or with no arguments while
stdin is piped/redirected), read the payload from stdin instead of
argv. This avoids OS command-line length limits (ARG_MAX) that make
very large payloads impossible to pass as CLI arguments.
- Otherwise, join argv as before.
In both cases the raw text is URL-decoded with unquote_plus, which
(unlike plain unquote) also converts '+' back into a space -- the
encoding produced by standard application/x-www-form-urlencoded data,
e.g. values coming from an HTML form or a query string.
"""
if len(argv) == 1 and argv[0] == "-":
raw = sys.stdin.read()
elif len(argv) == 0 and not sys.stdin.isatty():
raw = sys.stdin.read()
else:
raw = " ".join(argv)
return unquote_plus(raw)
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stdout.buffer.write(b"Usage: https://fech.space/qr/[data]")
sys.stdout.buffer.flush()
sys.exit(0)
user_data = read_input_data(sys.argv[1:])
try:
matrix = generate_qr_matrix(user_data)
png_bytes = generate_png_bytes(matrix, scale=8, border=4)
sys.stdout.buffer.write(png_bytes)
sys.stdout.buffer.flush()
except Exception as e:
sys.stdout.buffer.write(f"Error: {e}\n".encode("utf-8"))
sys.stdout.buffer.flush()
sys.exit(0)
Give Feedback
Community's Feedback on Utility
Utility Credits and Resources
(no credits mentioned for this utility)