Skip to main content

QR Codes as Polygons (TikZ/SVG)

Project description

🧩 PolyQR: QR Codes as Polygons

PolyQR is a small library that turns a message into a QR code where each contiguous black region is drawn as one merged polygon, not a grid of tiny squares. This eliminates the hideous hairline gaps between modules that appear with naïve approaches (an example is shown later) and also minimizes the number of points per polygon to keep the output compact.

PolyQR can generate:

  • TikZ code with full styling support (e.g. rounded corners)
  • SVG paths that are fully minimized to save space

The tests directory contains pytest-based tests for both TikZ and SVG.

🖼️ TikZ Output

PolyQR provides the command-line tool polyqr_tikz, which can be called like this:

polyqr_tikz "1mm" "rounded corners=0.25mm" "https://github.com/KurtBoehm/polyqr"

This prints a tikzpicture environment of the following form to stdout:

\begin{tikzpicture}[x=1mm, y=1mm, qrpoly/.style={fill=black, draw=none, even odd rule, rounded corners=0.25mm}]
  % \draw commands to draw a QR code representing https://github.com/KurtBoehm/polyqr
\end{tikzpicture}

Because each connected component is rendered as a single polygon, TikZ styles such as rounded corners apply only to the outer boundary of each contiguous region. This also eliminates visible gaps between modules, which you can see when comparing to a basic version that draws each module as a separate rectangle:

Basic PolyQR PolyQR with rounded corners
Basic TikZ PolyQR TikZ Rounded PolyQR TikZ

The LaTeX file used to generate these examples is at docs/tikz.tex.

You can also generate TikZ code directly from Python:

from polyqr import QrCodePainter

painter = QrCodePainter("https://github.com/KurtBoehm/polyqr")

print(painter.tikz(size="1mm", style="rounded corners=0.25mm"))

🖼️ SVG Output

PolyQR can also generate highly minimized SVG paths:

  • Either the entire QR code or each contiguous area becomes a single <path> element using fill-rule="evenodd" to handle holes.
  • All segments are axis-aligned and therefore encoded using only M, H, V, and Z commands.
  • For every move/line, absolute vs. relative commands are chosen based on which textual form is shorter.

SVG generation is available programmatically:

from polyqr import QrCodePainter

painter = QrCodePainter("https://github.com/KurtBoehm/polyqr")

# Full SVG document as a string
svg_doc = painter.svg

# Single <path> element covering the full QR code
svg_path = painter.svg_path

# Generator over <path> elements, one per contiguous region
for path in painter.svg_paths:
    print(path)

qrcode, which PolyQR uses to generate the underlying module matrix, can also output SVG via qrcode.svg.SvgPathImage (among others). SvgPathImage avoids gaps by collecting all modules into a single <path>, but its output is much larger.

For the message https://github.com/KurtBoehm/polyqr:

That is a size reduction of more than 70% with identical geometry.

🧠 Algorithm Overview

PolyQR converts a message into merged polygons in three main stages:

  1. QR code generation:
    • Uses qrcode to build a Boolean module matrix for the input message.
  2. Connected components and boundary extraction:
    • Runs a 4-neighbour BFS flood fill on the module grid to find connected black regions.
    • For each module in a component, its four unit-square edges are added to a Counter in canonical (sorted-endpoint) form.
    • Any edge seen exactly once lies on the region’s boundary (outer boundary or hole); interior edges shared by two modules cancel out.
  3. Cycle tracing and polygon simplification:
    • Builds an undirected adjacency graph from the remaining boundary edges.
    • For each connected component of this boundary graph, traces a single “wall-hugging” cycle:
      • At each step, the walk prefers non-collinear turns over going straight, producing visually pleasing outlines around holes when rounded corners are used.
      • If the initial cycle does not visit every vertex of the component, it is iteratively extended by following any remaining unused edges (again preferring turns) until the component is fully covered.
    • Each resulting cycle is simplified by removing collinear vertices.

The result is a small set of rectilinear polygons that exactly cover the QR modules.

🧪 Testing

PolyQR includes pytest-based tests for TikZ and SVG output, covering QR code generation, polygon extraction and simplification, and output formatting. The development dependencies can be installed via the dev optional group:

pip3 install .[dev]

All tests can then be run from the project root:

pytest

The TikZ tests are relatively slow, as they require pdflatex to compile a LaTeX document to PDF, which is then rasterized via PyMuPDF. To keep test times reasonable, the dev dependencies include pytest-xdist, so tests can be executed in parallel:

pytest -n 8  # or any other number of workers

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polyqr-1.0.0.tar.gz (10.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

polyqr-1.0.0-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file polyqr-1.0.0.tar.gz.

File metadata

  • Download URL: polyqr-1.0.0.tar.gz
  • Upload date:
  • Size: 10.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for polyqr-1.0.0.tar.gz
Algorithm Hash digest
SHA256 3395d2ca799b34008e2fcec69f21fa60b1edbc85aa2e5769600354dfe07bbb19
MD5 1f6e751808a339cc8ddbe55649bb4154
BLAKE2b-256 551592cca460c41b778226fb990d806fffa006888cf8b61e8b00615ae8f40347

See more details on using hashes here.

File details

Details for the file polyqr-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: polyqr-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 8.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for polyqr-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f91b1154038fb7b326bc55ac51592dbf7b776960db93ea06c3731a708b7db235
MD5 7ea8cef419260314a18cd587d9c95521
BLAKE2b-256 3bb67db36ea107e8a1eb690cc19206b3ecc0f7c18919a48cdcdea6edae6bde5a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page