Last active
June 23, 2024 00:15
-
-
Save mralext20/03fbfbc33885ccb3a1a46629fd82a672 to your computer and use it in GitHub Desktop.
generates QR codes with letter and number pairs, saves to a PDF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from PIL import Image | |
from PIL import ImageDraw | |
from qrcode.main import QRCode | |
from qrcode.constants import ERROR_CORRECT_L | |
from PIL import ImageFont | |
from typing import List, Sequence, Generator, Tuple, TypeVar, Union | |
import math | |
import shutil | |
import time | |
import multiprocessing as mp | |
t = time.process_time() | |
_T = TypeVar("_T") | |
def grouper(iterable: Sequence[_T], n: int) -> Generator[Sequence[_T], None, None]: | |
""" | |
given a iterable, yield that iterable back in chunks of size n. last item will be any size. | |
""" | |
for i in range(math.ceil(len(iterable) / n)): | |
yield iterable[i * n : i * n + n] | |
FONT = ImageFont.truetype("font.ttf", 500) | |
requests: List[Tuple[str, int]] = [] | |
HEIGHT = 2.5 # inches | |
# aisles = {"MD1": 25, "GR1": 25, "GM1": 20, "FT1": 17, "FT2": 20, "BK1": 30} | |
# locators = (LETTER, (RANGE_START,RANGE_END), COUNT) | |
locators: List[Tuple[str, Tuple[int, int], int]] = [ | |
("A", (1, 4), 2), | |
("B", 1,12, 1) | |
] | |
for entry in locators: | |
letter = entry[0] | |
count = entry[1] | |
if len(entry) != 3: | |
entry = (letter, count, 1) | |
for code in range(entry[2]): | |
for number in range(count[0], count[1] + 1): | |
requests.append((letter, number)) | |
images = [] | |
cover = Image.new("RGB", (2550, 3300), color=(255, 255, 255)) | |
text = "" | |
for letter, locatorsRange, count in locators: | |
text += f"{letter}: {locatorsRange[0]}-{locatorsRange[1]} X {count}\n" | |
ImageDraw.Draw(cover).text((75, 75), f"{len(requests)} QR Codes\n{text}", font_size=100, fill="black") | |
images.append(cover) | |
cover.save("test.png") | |
# solve how many can i fit on a page? | |
# page dims are 8.5x11 inches, with a 0.25 inch margin | |
# 8.5 - 2*0.25 = 8 inches wide | |
# 11 - 2*0.25 = 10.5 inches tall | |
# we want each qr code to be HEIGHT inches tall, and 1.6 * HEIGHT inches wide. 1.6 is the aspect ratio of the qr code. | |
# so we can fit 10.5 / HEIGHT qr codes tall, and 8 / (1.6 * HEIGHT) qr codes wide | |
per_vert = math.floor(10.5 / HEIGHT) | |
per_horiz = math.floor(8 / (1.6 * HEIGHT)) | |
# we should check if we can fit more qr codes on the page by rotating the QR codes | |
extra_sideways = 0 | |
if per_horiz * (1.6 * HEIGHT) < 8 - HEIGHT: | |
# how many extra? | |
extra_sideways = math.floor((10.5 - HEIGHT) / HEIGHT) | |
def generate_qr(data: Tuple[str, int]) -> Image.Image: | |
single = Image.new("RGB", (1600, 1000), color=(255, 255, 255)) | |
letter = str(data[0]) | |
number = data[1] | |
qr = QRCode( | |
version=1, | |
error_correction=ERROR_CORRECT_L, | |
box_size=50, | |
border=0, | |
) | |
qr.add_data(f"{letter}-{number}") | |
qr.make(fit=True) | |
qr_img = qr.make_image(fill_color="black", back_color="white") | |
single.paste( | |
qr_img.resize((925, 925)), | |
(37, 37), | |
) | |
ImageDraw.Draw(single).rectangle( # Border line | |
((0, 0, 1600, 1000)), | |
fill=None, | |
outline="black", | |
width=5, | |
) | |
# # first letter | |
ImageDraw.Draw(single).text( | |
(1000, 0), | |
letter[-2] if len(letter) > 1 else "", | |
font=FONT, | |
fill="black", | |
) | |
# second letter | |
ImageDraw.Draw(single).text( | |
(1300, 0), | |
letter[-1], | |
font=FONT, | |
fill="black", | |
) # letter[-1] | |
# First Number | |
ImageDraw.Draw(single).text( | |
(1000, 425), | |
f"{(number // 10) % 10}", | |
font=FONT, | |
fill="black", # f"{(number // 10) % 10}" | |
) | |
# Second Number | |
ImageDraw.Draw(single).text( | |
(1300, 425), | |
f"{number % 10}", | |
font=FONT, | |
fill="black", # f"{number % 10}" | |
) | |
return single | |
def page_builder(group): | |
page = Image.new("RGB", (2550, 3300), color=(255, 255, 255)) | |
grid, extra = group[: per_vert * per_horiz], group[per_vert * per_horiz :] | |
for i, graphic in enumerate(grid): | |
x = ((i % per_horiz) * int(HEIGHT * 300 * 1.6)) + 75 | |
y = ((i // per_horiz) * int(HEIGHT * 300)) + 75 | |
page.paste( | |
graphic.resize((int(HEIGHT * 300 * 1.6), int(HEIGHT * 300))), | |
(x, y), | |
) | |
for i, graphic in enumerate(extra): | |
# starting from the right edge of the grid , go down the page, sideways | |
x = per_horiz * int(HEIGHT * 300 * 1.6) + 75 | |
y = (i * int(HEIGHT * 1.6 * 300)) + 75 | |
g = graphic.resize((int(HEIGHT * 300 * 1.6), int(HEIGHT * 300))) | |
g.show() | |
g = g.rotate(90, expand=True) | |
page.paste( | |
g, | |
(x, y), | |
) | |
return page | |
if __name__ == "__main__": | |
mp.freeze_support() | |
# we can fit a total of per_vert * per_horiz + extra_sideways qr codes on the page | |
print( | |
f"Height: {HEIGHT}, per_vert: {per_vert}, per_horiz: {per_horiz}, extra_sideways: {extra_sideways}, total: {per_vert * per_horiz + extra_sideways}/page, {math.ceil(len(requests)/(per_vert * (per_horiz + extra_sideways)))} total pages for {len(requests)} qr codes." | |
) | |
print(f"Time PRE CALC DONE: {time.process_time() - t}") | |
requested_graphics = mp.Pool().map(generate_qr, requests) | |
print(f"Time QR GENERATED: {time.process_time() - t}") | |
images = mp.Pool().map( | |
page_builder, | |
grouper(requested_graphics, per_vert * per_horiz + extra_sideways), | |
) | |
print(f"Time START PDF GEN: {time.process_time() - t}") | |
images[0].save( | |
"out_all.pdf", | |
save_all=True, | |
append_images=images[1:], | |
resolution=100.0, | |
) | |
shutil.make_archive("qrcodes", "zip", ".", "out_all.pdf") | |
print(f"Time: {time.process_time() - t}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment