"""Generate golden test data for the Rust brother-ql crate. For every case this writes the input image(s) as 1-bit PNG plus the output of Python ``brother_ql.conversion.convert`` (brother_ql_next 0.12) as ``.bin`` into ``crates/brother-ql/tests/golden/`` and lists the cases in ``manifest.txt``. ``tests/golden.rs`` re-runs the conversion in Rust and requires identical bytes. Run with a Python that has brother_ql_next, qrcode and pillow, e.g.: C:/dev/HPCS/.venv/Scripts/python.exe -W ignore crates/brother-ql/tools/golden.py ``render_label`` below is a verbatim copy of the HPCS ``label_printer.py`` function. Pass ``--reference path/to/label_printer.py`` (or set ``LABEL_PRINTER_PY``) to import the original instead and assert both agree. """ import argparse import importlib.util import os import random import sys from pathlib import Path import qrcode from PIL import Image, ImageDraw, ImageFont from brother_ql.conversion import convert from brother_ql.raster import BrotherQLRaster OUT = Path(__file__).resolve().parent.parent / "tests" / "golden" # ---- verbatim copy of label_printer.py (HPCS) ------------------------------- DOTS = (202, 202) TEXT_HEIGHT = 52 def _font(size): for name in ("arialbd.ttf", "arial.ttf", "DejaVuSans-Bold.ttf"): try: return ImageFont.truetype(name, size) except OSError: continue return ImageFont.load_default(size) def render_label(lamp_id: str) -> Image.Image: image = Image.new("1", DOTS, 1) qr = qrcode.QRCode( error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=1, border=2, ) qr.add_data(lamp_id.upper()) qr.make(fit=True) modules = len(qr.get_matrix()) qr_size = DOTS[1] - TEXT_HEIGHT qr.box_size = max(1, qr_size // modules) qr_img = qr.make_image().get_image().convert("1") image.paste(qr_img, ((DOTS[0] - qr_img.width) // 2, (qr_size - qr_img.height) // 2)) draw = ImageDraw.Draw(image) size = 44 while size > 10: font = _font(size) box = draw.textbbox((0, 0), lamp_id, font=font) if box[2] - box[0] <= DOTS[0] - 8: break size -= 2 draw.text( ((DOTS[0] - (box[2] - box[0])) // 2 - box[0], DOTS[1] - TEXT_HEIGHT + (TEXT_HEIGHT - (box[3] - box[1])) // 2 - box[1]), lamp_id, font=font, fill=0, ) return image # ----------------------------------------------------------------------------- def random_image(w, h, seed, p=0.5): rng = random.Random(seed) im = Image.new("1", (w, h), 1) px = im.load() for y in range(h): for x in range(w): if rng.random() < p: px[x, y] = 0 return im def synthetic(): w, h = 202, 202 imgs = { "white_202": Image.new("1", (w, h), 1), "black_202": Image.new("1", (w, h), 0), } chk = Image.new("1", (w, h), 1) px = chk.load() for y in range(h): for x in range(w): if (x + y) % 2: px[x, y] = 0 imgs["checker_202"] = chk for name, (x, y) in { "corner_tl": (0, 0), "corner_tr": (w - 1, 0), "corner_bl": (0, h - 1), "corner_br": (w - 1, h - 1), }.items(): im = Image.new("1", (w, h), 1) im.putpixel((x, y), 0) imgs[name] = im imgs["random_202"] = random_image(w, h, 1234) return imgs def main(): ap = argparse.ArgumentParser() ap.add_argument("--reference", default=os.environ.get("LABEL_PRINTER_PY")) args = ap.parse_args() reference = None if args.reference and Path(args.reference).exists(): spec = importlib.util.spec_from_file_location("label_printer_ref", args.reference) reference = importlib.util.module_from_spec(spec) spec.loader.exec_module(reference) OUT.mkdir(parents=True, exist_ok=True) for f in OUT.iterdir(): if f.suffix in (".png", ".bin") or f.name == "manifest.txt": f.unlink() images = {} for lamp_id in ("L0001", "L0006", "L0123", "L12345", "L9"): im = render_label(lamp_id) if reference is not None: ref = reference.render_label(lamp_id) assert ref.tobytes() == im.tobytes(), f"embedded render_label differs for {lamp_id}" images[f"label_{lamp_id}"] = im images.update(synthetic()) images["random_404"] = random_image(404, 404, 404) images["random_696x300"] = random_image(696, 300, 696) images["random_500x200"] = random_image(500, 200, 500) images["random_300x696"] = random_image(300, 696, 300) images["random_1392x150"] = random_image(1392, 150, 1392) images["random_306x300"] = random_image(306, 300, 306) images["random_271x696"] = random_image(271, 696, 2716) images["random_236"] = random_image(236, 236, 236) images["sparse_1164x301"] = random_image(1164, 301, 1164, p=0.01) images["random_696x271"] = random_image(696, 271, 271) images["sparse_696x301"] = random_image(696, 301, 301, p=0.01) for name, im in images.items(): im.save(OUT / f"{name}.png") cases = [] # (case name, model, label, [image names], kwargs) base = list(images)[:13] # 5 labels + 8 synthetic 202x202 images for name in base: for copies in (1, 2): cases.append((f"{name}_x{copies}", "QL-820NWB", "23x23", [name] * copies, {"cut": True})) q = "QL-820NWB" cases += [ ("label_L0006_nocut", q, "23x23", ["label_L0006"], {"cut": False}), ("label_L0006_compress_x2", q, "23x23", ["label_L0006"] * 2, {"compress": True}), ("random_202_compress", q, "23x23", ["random_202"], {"compress": True}), ("checker_202_compress", q, "23x23", ["checker_202"], {"compress": True}), ("black_202_compress", q, "23x23", ["black_202"], {"compress": True}), ("white_202_compress", q, "23x23", ["white_202"], {"compress": True}), ("corner_tl_rot0", q, "23x23", ["corner_tl"], {"rotate": 0}), ("corner_tl_rot90", q, "23x23", ["corner_tl"], {"rotate": 90}), ("corner_tl_rot180", q, "23x23", ["corner_tl"], {"rotate": 180}), ("corner_tl_rot270", q, "23x23", ["corner_tl"], {"rotate": 270}), ("corner_br_rot0", q, "23x23", ["corner_br"], {"rotate": 0}), ("random_202_thr100", q, "23x23", ["random_202"], {"threshold": 100}), ("random_202_thr0", q, "23x23", ["random_202"], {"threshold": 0}), ("random_202_thr50", q, "23x23", ["random_202"], {"threshold": 50}), ("random_202_lq", q, "23x23", ["random_202"], {"hq": False}), ("random_202_dither", q, "23x23", ["random_202"], {"dither": True}), ("random_202_dither_thr100", q, "23x23", ["random_202"], {"dither": True, "threshold": 100}), ("multi_labels", q, "23x23", ["label_L0001", "label_L0006", "label_L12345"], {"cut": True}), ("random_404_dpi600", q, "23x23", ["random_404"], {"dpi_600": True}), ("random_404_dpi600_compress", q, "23x23", ["random_404"], {"dpi_600": True, "compress": True}), ("endless62_696", q, "62", ["random_696x300"], {}), ("endless62_resize500", q, "62", ["random_500x200"], {}), ("endless62_rot90", q, "62", ["random_300x696"], {"rotate": 90}), ("endless62_dpi600", q, "62", ["random_1392x150"], {"dpi_600": True}), ("endless29_306", q, "29", ["random_306x300"], {"compress": True}), ("diecut62x29_auto_rotate", q, "62x29", ["random_271x696"], {}), ("round_d24", q, "d24", ["random_236"], {}), ("ql1100_102", "QL-1100", "102", ["sparse_1164x301"], {"compress": True}), ("ql1100_62", "QL-1100", "62", ["sparse_696x301"], {"compress": True}), ("ql700_62_compress_ignored", "QL-700", "62", ["random_696x300"], {"compress": True}), ("ql500_29", "QL-500", "29", ["random_306x300"], {}), ("ql570_62x29", "QL-570", "62x29", ["random_696x271"], {"cut": False}), ("ql800_23x23_x2", "QL-800", "23x23", ["label_L0006"] * 2, {"compress": True}), ] lines = ["# name model label images options... (generated by tools/golden.py)"] for name, model, label, imgs, kwargs in cases: inputs = [Image.open(OUT / f"{i}.png") for i in imgs] for im in inputs: assert im.mode == "1", (i, im.mode) data = convert(qlr=BrotherQLRaster(model), images=inputs, label=label, **kwargs) (OUT / f"{name}.bin").write_bytes(data) opts = " ".join(f"{k}={v}" for k, v in kwargs.items()) lines.append(f"{name} {model} {label} {','.join(i + '.png' for i in imgs)} {opts}".rstrip()) (OUT / "manifest.txt").write_bytes(("\n".join(lines) + "\n").encode("ascii")) total = sum(f.stat().st_size for f in OUT.iterdir()) print(f"{len(cases)} cases, {len(images)} images, {total} bytes in {OUT}") print("reference render_label checked" if reference else "reference render_label NOT checked") if __name__ == "__main__": main()